Skip to content

Commit c4bc215

Browse files
authored
feat: Add Application Signals Dimensions to EMF exporter (#1264)
*Description of changes:* - Added support for Application Signals dimensions (Service and Environment) in EMF exporters via the `OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS` environment variable. When enabled, the exporters automatically add Service and Environment dimensions. Defaults to `UnknownService` and `generic:default` respectively based on resource attributes and cloud platform. - Application Signals dimensions are enabled by default when `OTEL_METRICS_EXPORTER` is set to `awsemf`. Users can opt out or disable this feature by explicitly setting `OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS` to `false`. - chore: add Builder class for `AwsEmf` and `Console` exporters <img width="966" height="499" alt="image" src="https://github.com/user-attachments/assets/20174c2d-9dfa-438b-af8e-afc64e8f649d" /> By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 108bcb0 commit c4bc215

File tree

12 files changed

+484
-74
lines changed

12 files changed

+484
-74
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t
1515

1616
### Enhancements
1717

18+
- Add Application Signals Dimensions to EMF exporter
19+
([#1264](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1264))
1820
- Configure EMF and CompactLog Exporters for Lambda Environment
1921
([#1222](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1222))
2022
- feat: [Java] EMF Exporter Implementation

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ public final class AwsApplicationSignalsCustomizerProvider
118118
private static final String DEPRECATED_APP_SIGNALS_ENABLED_CONFIG =
119119
"otel.aws.app.signals.enabled";
120120
static final String APPLICATION_SIGNALS_ENABLED_CONFIG = "otel.aws.application.signals.enabled";
121+
static final String OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS =
122+
"otel.metrics.add.application.signals.dimensions";
121123

122124
private static final String OTEL_RESOURCE_PROVIDERS_AWS_ENABLED =
123125
"otel.resource.providers.aws.enabled";
@@ -185,8 +187,8 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
185187
autoConfiguration.addMetricExporterCustomizer(this::customizeMetricExporter);
186188
}
187189

188-
static boolean isLambdaEnvironment(ConfigProperties props) {
189-
return props.getString(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG) != null;
190+
static boolean shouldAddApplicationSignalsDimensionsEnabled(ConfigProperties props) {
191+
return props.getBoolean(OTEL_METRICS_ADD_APPLICATION_SIGNALS_DIMENSIONS, true);
190192
}
191193

192194
private static Optional<String> getAwsRegionFromConfig(ConfigProperties configProps) {
@@ -197,6 +199,10 @@ private static Optional<String> getAwsRegionFromConfig(ConfigProperties configPr
197199
return Optional.ofNullable(configProps.getString(AWS_DEFAULT_REGION));
198200
}
199201

202+
static boolean isLambdaEnvironment(ConfigProperties props) {
203+
return props.getString(AWS_LAMBDA_FUNCTION_NAME_PROP_CONFIG) != null;
204+
}
205+
200206
static boolean isLambdaEnvironment() {
201207
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) != null;
202208
}
@@ -551,6 +557,9 @@ MetricExporter customizeMetricExporter(
551557
MetricExporter metricExporter, ConfigProperties configProps) {
552558

553559
if (isEmfExporterEnabled) {
560+
boolean shouldAddApplicationSignalsDimensions =
561+
AwsApplicationSignalsCustomizerProvider.shouldAddApplicationSignalsDimensionsEnabled(
562+
configProps);
554563
Map<String, String> headers =
555564
AwsApplicationSignalsConfigUtils.parseOtlpHeaders(
556565
configProps.getString(OTEL_EXPORTER_OTLP_LOGS_HEADERS));
@@ -562,11 +571,20 @@ MetricExporter customizeMetricExporter(
562571
&& headers.containsKey(AWS_OTLP_LOGS_STREAM_HEADER)) {
563572
String logGroup = headers.get(AWS_OTLP_LOGS_GROUP_HEADER);
564573
String logStream = headers.get(AWS_OTLP_LOGS_STREAM_HEADER);
565-
return new AwsCloudWatchEmfExporter(namespace, logGroup, logStream, awsRegion.get());
574+
return AwsCloudWatchEmfExporter.builder()
575+
.setNamespace(namespace)
576+
.setLogGroupName(logGroup)
577+
.setLogStreamName(logStream)
578+
.setAwsRegion(awsRegion.get())
579+
.setShouldAddApplicationSignalsDimensions(shouldAddApplicationSignalsDimensions)
580+
.build();
566581
}
567582

568583
if (isLambdaEnvironment(configProps)) {
569-
return new ConsoleEmfExporter(namespace);
584+
return ConsoleEmfExporter.builder()
585+
.setNamespace(namespace)
586+
.setShouldAddApplicationSignalsDimensions(shouldAddApplicationSignalsDimensions)
587+
.build();
570588
}
571589
logger.warning(
572590
String.format(

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/metrics/AwsCloudWatchEmfExporter.java

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.logging.Logger;
2020
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
2121
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.BaseEmfExporter;
22-
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.CloudWatchLogsClientEmitter;
2322
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.LogEventEmitter;
2423

2524
/**
@@ -34,27 +33,22 @@
3433
public class AwsCloudWatchEmfExporter extends BaseEmfExporter<CloudWatchLogsClient> {
3534
private static final Logger logger = Logger.getLogger(AwsCloudWatchEmfExporter.class.getName());
3635

37-
/**
38-
* Initialize the CloudWatch EMF exporter.
39-
*
40-
* @param namespace CloudWatch namespace for metrics (default: "default")
41-
* @param logGroupName CloudWatch log group name
42-
* @param logStreamName CloudWatch log stream name (auto-generated if null)
43-
* @param awsRegion AWS region
44-
*/
45-
public AwsCloudWatchEmfExporter(
46-
String namespace, String logGroupName, String logStreamName, String awsRegion) {
47-
super(namespace, new CloudWatchLogsClientEmitter(logGroupName, logStreamName, awsRegion));
36+
public static AwsCloudWatchEmfExporterBuilder builder() {
37+
return new AwsCloudWatchEmfExporterBuilder();
4838
}
4939

50-
/**
51-
* Initialize the CloudWatch EMF exporter with a custom emitter.
52-
*
53-
* @param namespace CloudWatch namespace for metrics
54-
* @param emitter Custom log emitter
55-
*/
56-
public AwsCloudWatchEmfExporter(String namespace, LogEventEmitter<CloudWatchLogsClient> emitter) {
57-
super(namespace, emitter);
40+
static AwsCloudWatchEmfExporter create(
41+
String namespace,
42+
LogEventEmitter<CloudWatchLogsClient> emitter,
43+
boolean shouldAddApplicationSignalsDimensions) {
44+
return new AwsCloudWatchEmfExporter(namespace, emitter, shouldAddApplicationSignalsDimensions);
45+
}
46+
47+
private AwsCloudWatchEmfExporter(
48+
String namespace,
49+
LogEventEmitter<CloudWatchLogsClient> emitter,
50+
boolean shouldAddApplicationSignalsDimensions) {
51+
super(namespace, emitter, shouldAddApplicationSignalsDimensions);
5852
}
5953

6054
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics;
17+
18+
import static java.util.Objects.requireNonNull;
19+
20+
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
21+
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.CloudWatchLogsClientEmitter;
22+
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.LogEventEmitter;
23+
24+
public class AwsCloudWatchEmfExporterBuilder {
25+
private String namespace;
26+
private String logGroupName;
27+
private String logStreamName;
28+
private String awsRegion;
29+
private LogEventEmitter<CloudWatchLogsClient> emitter;
30+
private boolean shouldAddApplicationSignalsDimensions = false;
31+
32+
public AwsCloudWatchEmfExporterBuilder setNamespace(String namespace) {
33+
this.namespace = namespace;
34+
return this;
35+
}
36+
37+
public AwsCloudWatchEmfExporterBuilder setLogGroupName(String logGroupName) {
38+
this.logGroupName = logGroupName;
39+
return this;
40+
}
41+
42+
public AwsCloudWatchEmfExporterBuilder setLogStreamName(String logStreamName) {
43+
this.logStreamName = logStreamName;
44+
return this;
45+
}
46+
47+
public AwsCloudWatchEmfExporterBuilder setAwsRegion(String awsRegion) {
48+
this.awsRegion = awsRegion;
49+
return this;
50+
}
51+
52+
public AwsCloudWatchEmfExporterBuilder setEmitter(LogEventEmitter<CloudWatchLogsClient> emitter) {
53+
this.emitter = emitter;
54+
return this;
55+
}
56+
57+
public AwsCloudWatchEmfExporterBuilder setShouldAddApplicationSignalsDimensions(
58+
boolean shouldAddApplicationSignalsDimensions) {
59+
this.shouldAddApplicationSignalsDimensions = shouldAddApplicationSignalsDimensions;
60+
return this;
61+
}
62+
63+
public AwsCloudWatchEmfExporter build() {
64+
65+
if (this.emitter == null) {
66+
requireNonNull(logGroupName, "Must set logGroupName when emitter is not provided");
67+
requireNonNull(logStreamName, "Must set logStreamName when emitter is not provided");
68+
requireNonNull(awsRegion, "Must set awsRegion when emitter is not provided");
69+
this.emitter = new CloudWatchLogsClientEmitter(logGroupName, logStreamName, awsRegion);
70+
}
71+
return AwsCloudWatchEmfExporter.create(
72+
this.namespace, this.emitter, this.shouldAddApplicationSignalsDimensions);
73+
}
74+
}

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/metrics/ConsoleEmfExporter.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,28 @@
2020
import java.util.logging.Level;
2121
import java.util.logging.Logger;
2222
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.BaseEmfExporter;
23-
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.ConsoleEmitter;
2423
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.LogEventEmitter;
2524

2625
/** EMF metrics exporter for printing data to Standard Out. */
2726
public class ConsoleEmfExporter extends BaseEmfExporter<PrintStream> {
2827
private static final Logger logger = Logger.getLogger(ConsoleEmfExporter.class.getName());
2928

30-
/**
31-
* Initialize the Console EMF exporter.
32-
*
33-
* @param namespace CloudWatch namespace for metrics (defaults to "default")
34-
*/
35-
public ConsoleEmfExporter(String namespace) {
36-
super(namespace, new ConsoleEmitter());
29+
public static ConsoleEmfExporterBuilder builder() {
30+
return new ConsoleEmfExporterBuilder();
3731
}
3832

39-
/**
40-
* Initialize the Console EMF exporter with custom emitter.
41-
*
42-
* @param namespace CloudWatch namespace for metrics
43-
* @param emitter Custom emitter
44-
*/
45-
public ConsoleEmfExporter(String namespace, LogEventEmitter<PrintStream> emitter) {
46-
super(namespace, emitter);
33+
static ConsoleEmfExporter create(
34+
String namespace,
35+
LogEventEmitter<PrintStream> emitter,
36+
boolean shouldAddApplicationSignalsDimensions) {
37+
return new ConsoleEmfExporter(namespace, emitter, shouldAddApplicationSignalsDimensions);
38+
}
39+
40+
private ConsoleEmfExporter(
41+
String namespace,
42+
LogEventEmitter<PrintStream> emitter,
43+
boolean shouldAddApplicationSignalsDimensions) {
44+
super(namespace, emitter, shouldAddApplicationSignalsDimensions);
4745
}
4846

4947
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics;
17+
18+
import java.io.PrintStream;
19+
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.ConsoleEmitter;
20+
import software.amazon.opentelemetry.javaagent.providers.exporter.aws.metrics.common.emitter.LogEventEmitter;
21+
22+
public class ConsoleEmfExporterBuilder {
23+
private String namespace;
24+
private LogEventEmitter<PrintStream> emitter;
25+
private boolean shouldAddApplicationSignalsDimensions = false;
26+
27+
public ConsoleEmfExporterBuilder setNamespace(String namespace) {
28+
this.namespace = namespace;
29+
return this;
30+
}
31+
32+
public ConsoleEmfExporterBuilder setEmitter(LogEventEmitter<PrintStream> emitter) {
33+
this.emitter = emitter;
34+
return this;
35+
}
36+
37+
public ConsoleEmfExporterBuilder setShouldAddApplicationSignalsDimensions(
38+
boolean shouldAddApplicationSignalsDimensions) {
39+
this.shouldAddApplicationSignalsDimensions = shouldAddApplicationSignalsDimensions;
40+
return this;
41+
}
42+
43+
public ConsoleEmfExporter build() {
44+
if (this.emitter == null) {
45+
this.emitter = new ConsoleEmitter();
46+
}
47+
return ConsoleEmfExporter.create(
48+
this.namespace, this.emitter, this.shouldAddApplicationSignalsDimensions);
49+
}
50+
}

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/exporter/aws/metrics/common/BaseEmfExporter.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,26 @@
4747
public abstract class BaseEmfExporter<T> implements MetricExporter {
4848
private static final Logger logger = Logger.getLogger(BaseEmfExporter.class.getName());
4949
private final String namespace;
50+
private final boolean shouldAddApplicationSignalsDimensions;
5051
protected final LogEventEmitter<T> emitter;
5152

5253
/**
53-
* Creates a new EMF exporter with the specified namespace and log emitter.
54+
* Creates a new EMF exporter with the specified namespace, log emitter, and Application Signals
55+
* flag.
5456
*
5557
* @param namespace the CloudWatch metric namespace, defaults to "default" if null
5658
* @param emitter the log event emitter for sending EMF logs
59+
* @param shouldAddApplicationSignalsDimensions whether Application Signals dimensions should be
60+
* added
5761
*/
58-
protected BaseEmfExporter(String namespace, LogEventEmitter<T> emitter) {
59-
this.namespace = namespace != null ? namespace : "default";
62+
protected BaseEmfExporter(
63+
String namespace, LogEventEmitter<T> emitter, boolean shouldAddApplicationSignalsDimensions) {
64+
if (emitter == null) {
65+
throw new IllegalArgumentException("Given emitter must not be null");
66+
}
67+
this.namespace = namespace == null || namespace.isEmpty() ? "default" : namespace;
6068
this.emitter = emitter;
69+
this.shouldAddApplicationSignalsDimensions = shouldAddApplicationSignalsDimensions;
6170
}
6271

6372
@Override
@@ -117,7 +126,8 @@ record =
117126
records,
118127
firstRecord.getResourceAttributes(),
119128
this.namespace,
120-
firstRecord.getTimestamp());
129+
firstRecord.getTimestamp(),
130+
this.shouldAddApplicationSignalsDimensions);
121131

122132
Map<String, Object> logEvent = new HashMap<>();
123133
logEvent.put("message", new ObjectMapper().writeValueAsString(emfLog));

0 commit comments

Comments
 (0)