Skip to content

Commit

Permalink
Feature: EmfMetricLoggingPublisher (#5792)
Browse files Browse the repository at this point in the history
* EmfMetricPublisher class added (#5752)

* EmfMetricPublisher class with basic unit test added

* Edge cases handled

* javadoc for class added

* Java doc for methods added

* minor unit test changes

* Change from using jacksonCore directly to using jsonWriter

* EmfMetricConfiguration class added

* MetricEmfConverter class added

* new module checklist done

* checkStyle fixed

* minor build problem fixed

* internal package added

* fixed the issue passing raw objects

* added java clock to unit test

* added unit test for publish method

* minor changes

* config class to builder pattern

* config class to builder pattern

* minor change

* End to end test passed

* added valid case in WARN_LOG allowlist

* unit tests adjusted

* Change logGroupName to required field

* move MetricValueNormalizer class to utils package

* converter implementation changed

* schemaConformTest added

* SchemaConformTest adjusted

* minor change

* emf metric publisher performance test (#5775)

* Benchmark test for emfMetricPublisher added

* input name changed

* add emfBenchmarkTest to BenchmarkRunner

* enabled METRIC_BENCHMARKS

* checkStyle fixed

* Changed the classname to EmfMetricLoggingPublisher (#5783)

* change class name and artifactId

* test-coverage pom changed

* javadoc fixed

* Cloudwatch and Logging benchmark test added / Changed logGroupName config (#5790)

* more benchmark test added/changed logGroupName config

* teardown method overrided

* logGroupName access method changed

* Snapshot version changed

* LogGroupName in lambda unit test added

* suppression added for system

* changelog added

* changeLog modified

* minor fix

* snapshot version fixed

* Snapshot version changed
  • Loading branch information
Fred1155 authored Jan 21, 2025
1 parent 74f7529 commit 9a31343
Show file tree
Hide file tree
Showing 28 changed files with 1,542 additions and 14 deletions.
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

0 comments on commit 9a31343

Please sign in to comment.