Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: EmfMetricLoggingPublisher #5792

Merged
merged 14 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .brazil.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"aws-xml-protocol": { "packageName": "AwsJavaSdk-Core-AwsXmlProtocol" },
"smithy-rpcv2-protocol": { "packageName": "AwsJavaSdk-Core-SmithyRpcV2Protocol" },
"cloudwatch-metric-publisher": { "packageName": "AwsJavaSdk-MetricPublisher-CloudWatch" },
"emf-metric-logging-publisher": { "packageName": "AwsJavaSdk-MetricPublisher-Emf" },
"codegen": { "packageName": "AwsJavaSdk-Codegen" },
"dynamodb-enhanced": { "packageName": "AwsJavaSdk-DynamoDb-Enhanced" },
"http-client-spi": { "packageName": "AwsJavaSdk-HttpClient" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Emf Metric Logging Publisher",
"contributor": "",
"description": "Added a new EmfMetricLoggingPublisher class that transforms SdkMetricCollection to emf format string and logs it, which will be automatically collected by cloudwatch."
}
5 changes: 5 additions & 0 deletions aws-sdk-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,11 @@ Amazon AutoScaling, etc).</description>
<artifactId>cloudwatch-metric-publisher</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>emf-metric-logging-publisher</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>launchwizard</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@
<artifactId>cloudwatch-metric-publisher</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>emf-metric-logging-publisher</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest;
import software.amazon.awssdk.services.cloudwatch.model.StatisticSet;
import software.amazon.awssdk.utils.MetricValueNormalizer;

/**
* Aggregates {@link MetricCollection}s by: (1) the minute in which they occurred, and (2) the dimensions in the collection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import software.amazon.awssdk.metrics.SdkMetric;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.awssdk.utils.MetricValueNormalizer;

/**
* "Buckets" metrics by the minute in which they were collected. This allows all metric data for a given 1-minute period to be
Expand Down
78 changes: 78 additions & 0 deletions metric-publishers/emf-metric-logging-publisher/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
~
~ Licensed under the Apache License, Version 2.0 (the "License").
~ You may not use this file except in compliance with the License.
~ A copy of the License is located at
~
~ http://aws.amazon.com/apache2.0
~
~ or in the "license" file accompanying this file. This file is distributed
~ on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
~ express or implied. See the License for the specific language governing
~ permissions and limitations under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>software.amazon.awssdk</groupId>
<artifactId>metric-publishers</artifactId>
<version>2.30.3-SNAPSHOT</version>
</parent>

<artifactId>emf-metric-logging-publisher</artifactId>
<name>AWS Java SDK :: Metric Publishers :: Emf</name>
<packaging>jar</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>software.amazon.awssdk.metrics.publishers.emf</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>annotations</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>http-client-spi</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>json-utils</artifactId>
<version>${awsjavasdk.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sdk-core</artifactId>
<version>${awsjavasdk.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${json-schema-validator.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.metrics.publishers.emf;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import software.amazon.awssdk.annotations.Immutable;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.core.metrics.CoreMetric;
import software.amazon.awssdk.metrics.MetricCategory;
import software.amazon.awssdk.metrics.MetricCollection;
import software.amazon.awssdk.metrics.MetricLevel;
import software.amazon.awssdk.metrics.MetricPublisher;
import software.amazon.awssdk.metrics.SdkMetric;
import software.amazon.awssdk.metrics.publishers.emf.internal.EmfMetricConfiguration;
import software.amazon.awssdk.metrics.publishers.emf.internal.MetricEmfConverter;
import software.amazon.awssdk.utils.Logger;

/**
* A metric publisher implementation that converts metrics into CloudWatch Embedded Metric Format (EMF).
* EMF allows metrics to be published through CloudWatch Logs using a structured JSON format, which
* CloudWatch automatically extracts and processes into metrics.
*
* <p>
* This publisher is particularly well-suited for serverless environments like AWS Lambda and container
* environments like Amazon ECS that have built-in integration with CloudWatch Logs. Using EMF eliminates
* the need for separate metric publishing infrastructure as metrics are automatically extracted from
* log entries.
* </p>
*
* <p>
* The EMF publisher converts metric collections into JSON-formatted log entries that conform to the
* CloudWatch EMF specification. The logGroupName field is required for EMF to work.
* CloudWatch automatically processes these logs to generate corresponding metrics that can be used for
* monitoring and alerting.
* </p>
*
* @snippet
* // Create a EmfMetricLoggingPublisher using a custom namespace.
* MetricPublisher emfMetricLoggingPublisher = EmfMetricLoggingPublisher.builder()
* .logGroupName("myLogGroupName")
* .namespace("myApplication")
* .build();
*
* @see MetricPublisher The base interface for metric publishers
* @see MetricCollection For the collection of metrics to be published
* @see EmfMetricConfiguration For configuration options
* @see MetricEmfConverter For the conversion logic
*
*/

@ThreadSafe
@Immutable
@SdkPublicApi
public final class EmfMetricLoggingPublisher implements MetricPublisher {

private static final Logger logger = Logger.loggerFor(EmfMetricLoggingPublisher.class);
private final MetricEmfConverter metricConverter;


private EmfMetricLoggingPublisher(Builder builder) {
EmfMetricConfiguration config = new EmfMetricConfiguration.Builder()
.namespace(builder.namespace)
.logGroupName(builder.logGroupName)
.dimensions(builder.dimensions)
.metricLevel(builder.metricLevel)
.metricCategories(builder.metricCategories)
.build();

this.metricConverter = new MetricEmfConverter(config);
}


public static Builder builder() {
return new Builder();
}


@Override
public void publish(MetricCollection metricCollection) {
if (metricCollection == null) {
logger.warn(() -> "Null metric collection passed to the publisher");
return;
}
try {
List<String> emfStrings = metricConverter.convertMetricCollectionToEmf(metricCollection);
for (String emfString : emfStrings) {
logger.info(() -> emfString);
}
} catch (Exception e) {
logger.error(() -> "Failed to log metrics in EMF format", e);
}
}

/**
* Closes this metric publisher. This implementation is empty as the EMF metric logging publisher
* does not maintain any resources that require explicit cleanup.
*/
@Override
public void close() {
}

public static final class Builder {
private String namespace;
private String logGroupName;
private Collection<SdkMetric<String>> dimensions;
private Collection<MetricCategory> metricCategories;
private MetricLevel metricLevel;

private Builder() {
}

/**
* Configure the namespace that will be put into the emf log to this publisher.
*
* <p>If this is not specified, {@code AwsSdk/JavaSdk2} will be used.
*/
public Builder namespace(String namespace) {
this.namespace = namespace;
return this;
}

/**
* Configure the {@link SdkMetric} that are used to define the Dimension Set Array that will be put into the emf log to
* this
* publisher.
*
* <p>If this is not specified, {@link CoreMetric#SERVICE_ID} and {@link CoreMetric#OPERATION_NAME} will be used.
*/
public Builder dimensions(Collection<SdkMetric<String>> dimensions) {
this.dimensions = new ArrayList<>(dimensions);
return this;
}

/**
* @see #dimensions(SdkMetric[])
*/
@SafeVarargs
public final Builder dimensions(SdkMetric<String>... dimensions) {
return dimensions(Arrays.asList(dimensions));
}


/**
* Configure the {@link MetricCategory}s that should be uploaded to CloudWatch.
*
* <p>If this is not specified, {@link MetricCategory#ALL} is used.
*
* <p>All {@link SdkMetric}s are associated with at least one {@code MetricCategory}. This setting determines which
* category of metrics uploaded to CloudWatch. Any metrics {@link #publish(MetricCollection)}ed that do not fall under
* these configured categories are ignored.
*
* <p>Note: If there are {@link #dimensions(Collection)} configured that do not fall under these {@code MetricCategory}
* values, the dimensions will NOT be ignored. In other words, the metric category configuration only affects which
* metrics are uploaded to CloudWatch, not which values can be used for {@code dimensions}.
*/
public Builder metricCategories(Collection<MetricCategory> metricCategories) {
this.metricCategories = new ArrayList<>(metricCategories);
return this;
}

/**
* @see #metricCategories(Collection)
*/
public Builder metricCategories(MetricCategory... metricCategories) {
return metricCategories(Arrays.asList(metricCategories));
}

/**
* Configure the LogGroupName key that will be put into the emf log to this publisher. This is required when using
* the CloudWatch agent to send embedded metric format logs that tells the agent which log
* group to use.
*
* <p> If this is not specified, for AWS lambda environments, {@code AWS_LAMBDA_LOG_GROUP_NAME}
* is used.
* This field is required and must not be null or empty for non-lambda environments.
* @throws NullPointerException if non-lambda environment and logGroupName is null
*/
public Builder logGroupName(String logGroupName) {
this.logGroupName = logGroupName;
return this;
}

/**
* Configure the {@link MetricLevel} that should be uploaded to CloudWatch.
*
* <p>If this is not specified, {@link MetricLevel#INFO} is used.
*
* <p>All {@link SdkMetric}s are associated with one {@code MetricLevel}. This setting determines which level of metrics
* uploaded to CloudWatch. Any metrics {@link #publish(MetricCollection)}ed that do not fall under these configured
* categories are ignored.
*
* <p>Note: If there are {@link #dimensions(Collection)} configured that do not fall under this {@code MetricLevel}
* values, the dimensions will NOT be ignored. In other words, the metric category configuration only affects which
* metrics are uploaded to CloudWatch, not which values can be used for {@code dimensions}.
*/
public Builder metricLevel(MetricLevel metricLevel) {
this.metricLevel = metricLevel;
return this;
}


/**
* Build a {@link EmfMetricLoggingPublisher} using the configuration currently configured on this publisher.
*/
public EmfMetricLoggingPublisher build() {
return new EmfMetricLoggingPublisher(this);
}

}
}
Loading
Loading