Skip to content

Commit 287139b

Browse files
committed
Metric Schema and config name changes
In this commit, we are removing RemoteTarget and replacing with RemoteResourceIdentifier and RemoteResourceType. Further, we are formatting RemoteService, and the content of the RemoteResource attributes such that they align with AWS Cloud Control resource names. In addition to these changes, we are modifying the config names used for enabling and configuring Application Signals to use the full feature name, "application.signals".
1 parent a7d7adf commit 287139b

File tree

11 files changed

+470
-479
lines changed

11 files changed

+470
-479
lines changed

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java

Lines changed: 217 additions & 108 deletions
Large diffs are not rendered by default.

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,6 @@ protected String getKinesisSpanNamePrefix() {
5757
return "Kinesis";
5858
}
5959

60-
@Override
61-
protected String getS3ServiceName() {
62-
return "AWS.SDK.Amazon S3";
63-
}
64-
65-
@Override
66-
protected String getDynamoDbServiceName() {
67-
return "AWS.SDK.AmazonDynamoDBv2";
68-
}
69-
70-
@Override
71-
protected String getSqsServiceName() {
72-
return "AWS.SDK.AmazonSQS";
73-
}
74-
75-
@Override
76-
protected String getKinesisServiceName() {
77-
return "AWS.SDK.AmazonKinesis";
78-
}
79-
8060
protected String getS3RpcServiceName() {
8161
return "Amazon S3";
8262
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,6 @@ protected String getKinesisSpanNamePrefix() {
5656
return "Kinesis";
5757
}
5858

59-
@Override
60-
protected String getS3ServiceName() {
61-
return "AWS.SDK.S3";
62-
}
63-
64-
@Override
65-
protected String getDynamoDbServiceName() {
66-
return "AWS.SDK.DynamoDb";
67-
}
68-
69-
@Override
70-
protected String getSqsServiceName() {
71-
return "AWS.SDK.Sqs";
72-
}
73-
74-
protected String getKinesisServiceName() {
75-
return "AWS.SDK.Kinesis";
76-
}
77-
7859
@Override
7960
protected String getS3RpcServiceName() {
8061
return "S3";

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/base/ContractTestBase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ public abstract class ContractTestBase {
7878
.waitingFor(getApplicationWaitCondition())
7979
.withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/opentelemetry-javaagent-all.jar")
8080
.withEnv("OTEL_METRIC_EXPORT_INTERVAL", "100") // 100 ms
81-
.withEnv("OTEL_AWS_APP_SIGNALS_ENABLED", "true")
81+
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
8282
.withEnv("OTEL_METRICS_EXPORTER", "none")
8383
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "0") // Don't wait to export spans to the collector
8484
.withEnv(
85-
"OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT",
85+
"OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
8686
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT)
8787
.withEnv(
8888
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class AppSignalsConstants {
2929
public static final String AWS_LOCAL_OPERATION = "aws.local.operation";
3030
public static final String AWS_REMOTE_SERVICE = "aws.remote.service";
3131
public static final String AWS_REMOTE_OPERATION = "aws.remote.operation";
32-
public static final String AWS_REMOTE_TARGET = "aws.remote.target";
32+
public static final String AWS_REMOTE_RESOURCE_TYPE = "aws.remote.resource.type";
33+
public static final String AWS_REMOTE_RESOURCE_IDENTIFIER = "aws.remote.resource.identifier";
3334
public static final String AWS_SPAN_KIND = "aws.span.kind";
3435
}

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,24 @@
5252
* </ul>
5353
*
5454
* <p>You can control when these customizations are applied using the property
55-
* otel.aws.app.signals.enabled or the environment variable OTEL_AWS_APP_SIGNALS_ENABLED. This flag
56-
* is disabled by default.
55+
* otel.aws.application.signals.enabled or the environment variable
56+
* OTEL_AWS_APPLICATION_SIGNALS_ENABLED. This flag is disabled by default.
5757
*/
5858
public class AwsAppSignalsCustomizerProvider implements AutoConfigurationCustomizerProvider {
5959
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
6060
private static final Logger logger =
6161
Logger.getLogger(AwsAppSignalsCustomizerProvider.class.getName());
6262

63+
private static final String SMP_ENABLED_CONFIG = "otel.smp.enabled";
64+
private static final String APP_SIGNALS_ENABLED_CONFIG = "otel.aws.app.signals.enabled";
65+
private static final String APPLICATION_SIGNALS_ENABLED_CONFIG =
66+
"otel.aws.application.signals.enabled";
67+
private static final String SMP_EXPORTER_ENDPOINT_CONFIG = "otel.aws.smp.exporter.endpoint";
68+
private static final String APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG =
69+
"otel.aws.app.signals.exporter.endpoint";
70+
private static final String APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG =
71+
"otel.aws.application.signals.exporter.endpoint";
72+
6373
public void customize(AutoConfigurationCustomizer autoConfiguration) {
6474
autoConfiguration.addSamplerCustomizer(this::customizeSampler);
6575
autoConfiguration.addTracerProviderCustomizer(this::customizeTracerProviderBuilder);
@@ -68,7 +78,9 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
6878

6979
private boolean isAppSignalsEnabled(ConfigProperties configProps) {
7080
return configProps.getBoolean(
71-
"otel.aws.app.signals.enabled", configProps.getBoolean("otel.smp.enabled", false));
81+
APPLICATION_SIGNALS_ENABLED_CONFIG,
82+
configProps.getBoolean(
83+
APP_SIGNALS_ENABLED_CONFIG, configProps.getBoolean(SMP_ENABLED_CONFIG, false)));
7284
}
7385

7486
private Sampler customizeSampler(Sampler sampler, ConfigProperties configProps) {
@@ -141,9 +153,10 @@ public MetricExporter createExporter(ConfigProperties configProps) {
141153
if (protocol.equals(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF)) {
142154
appSignalsEndpoint =
143155
configProps.getString(
144-
"otel.aws.app.signals.exporter.endpoint",
156+
APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
145157
configProps.getString(
146-
"otel.aws.smp.exporter.endpoint", "http://localhost:4316/v1/metrics"));
158+
APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
159+
configProps.getString(SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4316/v1/metrics")));
147160
logger.log(Level.FINE, String.format("AppSignals export endpoint: %s", appSignalsEndpoint));
148161
return OtlpHttpMetricExporter.builder()
149162
.setEndpoint(appSignalsEndpoint)
@@ -153,8 +166,10 @@ public MetricExporter createExporter(ConfigProperties configProps) {
153166
} else if (protocol.equals(OtlpConfigUtil.PROTOCOL_GRPC)) {
154167
appSignalsEndpoint =
155168
configProps.getString(
156-
"otel.aws.app.signals.exporter.endpoint",
157-
configProps.getString("otel.aws.smp.exporter.endpoint", "http://localhost:4315"));
169+
APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
170+
configProps.getString(
171+
APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
172+
configProps.getString(SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4315")));
158173
logger.log(Level.FINE, String.format("AppSignals export endpoint: %s", appSignalsEndpoint));
159174
return OtlpGrpcMetricExporter.builder()
160175
.setEndpoint(appSignalsEndpoint)

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,25 @@ private AwsAttributeKeys() {}
3535
static final AttributeKey<String> AWS_REMOTE_OPERATION =
3636
AttributeKey.stringKey("aws.remote.operation");
3737

38-
static final AttributeKey<String> AWS_REMOTE_TARGET = AttributeKey.stringKey("aws.remote.target");
38+
static final AttributeKey<String> AWS_REMOTE_RESOURCE_IDENTIFIER =
39+
AttributeKey.stringKey("aws.remote.resource.identifier");
40+
41+
static final AttributeKey<String> AWS_REMOTE_RESOURCE_TYPE =
42+
AttributeKey.stringKey("aws.remote.resource.type");
3943

4044
static final AttributeKey<String> AWS_SDK_DESCENDANT =
4145
AttributeKey.stringKey("aws.sdk.descendant");
4246

47+
static final AttributeKey<String> AWS_CONSUMER_PARENT_SPAN_KIND =
48+
AttributeKey.stringKey("aws.consumer.parent.span.kind");
49+
4350
// use the same AWS Resource attribute name defined by OTel java auto-instr for aws_sdk_v_1_1
4451
// TODO: all AWS specific attributes should be defined in semconv package and reused cross all
4552
// otel packages. Related sim -
4653
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8710
47-
4854
static final AttributeKey<String> AWS_BUCKET_NAME = AttributeKey.stringKey("aws.bucket.name");
4955
static final AttributeKey<String> AWS_QUEUE_URL = AttributeKey.stringKey("aws.queue.url");
5056
static final AttributeKey<String> AWS_QUEUE_NAME = AttributeKey.stringKey("aws.queue.name");
5157
static final AttributeKey<String> AWS_STREAM_NAME = AttributeKey.stringKey("aws.stream.name");
5258
static final AttributeKey<String> AWS_TABLE_NAME = AttributeKey.stringKey("aws.table.name");
53-
static final AttributeKey<String> AWS_CONSUMER_PARENT_SPAN_KIND =
54-
AttributeKey.stringKey("aws.consumer.parent.span.kind");
5559
}

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

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@
4040
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
4141
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_URL;
4242
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
43+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
44+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
4345
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE;
44-
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_TARGET;
4546
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
4647
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
4748
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
@@ -88,6 +89,12 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
8889
private static final Logger logger =
8990
Logger.getLogger(AwsMetricAttributeGenerator.class.getName());
9091

92+
// Normalized remote service names for supported AWS services
93+
private static final String NORMALIZED_DYNAMO_DB_SERVICE_NAME = "AWS::DynamoDB";
94+
private static final String NORMALIZED_KINESIS_SERVICE_NAME = "AWS::Kinesis";
95+
private static final String NORMALIZED_S3_SERVICE_NAME = "AWS::S3";
96+
private static final String NORMALIZED_SQS_SERVICE_NAME = "AWS::SQS";
97+
9198
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
9299
private static final String GRAPHQL = "graphql";
93100

@@ -129,51 +136,13 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re
129136
setService(resource, span, builder);
130137
setEgressOperation(span, builder);
131138
setRemoteServiceAndOperation(span, builder);
132-
setRemoteTarget(span, builder);
139+
setRemoteResourceTypeAndIdentifier(span, builder);
133140
setSpanKindForDependency(span, builder);
134141
setHttpStatus(span, builder);
135142

136143
return builder.build();
137144
}
138145

139-
private static void setRemoteTarget(SpanData span, AttributesBuilder builder) {
140-
Optional<String> remoteTarget = getRemoteTarget(span);
141-
remoteTarget.ifPresent(s -> builder.put(AWS_REMOTE_TARGET, s));
142-
}
143-
144-
/**
145-
* RemoteTarget attribute {@link AwsAttributeKeys#AWS_REMOTE_TARGET} is used to store the resource
146-
* name of the remote invokes, such as S3 bucket name, mysql table name, etc. TODO: currently only
147-
* support AWS resource name, will be extended to support the general remote targets, such as
148-
* ActiveMQ name, etc.
149-
*/
150-
private static Optional<String> getRemoteTarget(SpanData span) {
151-
if (isKeyPresent(span, AWS_BUCKET_NAME)) {
152-
return Optional.ofNullable("::s3:::" + span.getAttributes().get(AWS_BUCKET_NAME));
153-
}
154-
155-
if (isKeyPresent(span, AWS_QUEUE_URL)) {
156-
String arn = SqsUrlParser.getSqsRemoteTarget(span.getAttributes().get(AWS_QUEUE_URL));
157-
158-
if (arn != null) {
159-
return Optional.ofNullable(arn);
160-
}
161-
}
162-
163-
if (isKeyPresent(span, AWS_QUEUE_NAME)) {
164-
return Optional.ofNullable("::sqs:::" + span.getAttributes().get(AWS_QUEUE_NAME));
165-
}
166-
167-
if (isKeyPresent(span, AWS_STREAM_NAME)) {
168-
return Optional.ofNullable("::kinesis:::stream/" + span.getAttributes().get(AWS_STREAM_NAME));
169-
}
170-
171-
if (isKeyPresent(span, AWS_TABLE_NAME)) {
172-
return Optional.ofNullable("::dynamodb:::table/" + span.getAttributes().get(AWS_TABLE_NAME));
173-
}
174-
return Optional.empty();
175-
}
176-
177146
/** Service is always derived from {@link ResourceAttributes#SERVICE_NAME} */
178147
private static void setService(Resource resource, SpanData span, AttributesBuilder builder) {
179148
String service = resource.getAttribute(SERVICE_NAME);
@@ -213,14 +182,6 @@ private static void setEgressOperation(SpanData span, AttributesBuilder builder)
213182
builder.put(AWS_LOCAL_OPERATION, operation);
214183
}
215184

216-
// add `AWS.SDK.` as prefix to indicate the metrics resulted from current span is from AWS SDK
217-
private static String normalizeServiceName(SpanData span, String serviceName) {
218-
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
219-
return "AWS.SDK." + serviceName;
220-
}
221-
return serviceName;
222-
}
223-
224185
/**
225186
* Remote attributes (only for Client and Producer spans) are generated based on low-cardinality
226187
* span attributes, in priority order.
@@ -265,7 +226,7 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
265226
remoteService = getRemoteService(span, AWS_REMOTE_SERVICE);
266227
remoteOperation = getRemoteOperation(span, AWS_REMOTE_OPERATION);
267228
} else if (isKeyPresent(span, RPC_SERVICE) || isKeyPresent(span, RPC_METHOD)) {
268-
remoteService = normalizeServiceName(span, getRemoteService(span, RPC_SERVICE));
229+
remoteService = normalizeRemoteServiceName(span, getRemoteService(span, RPC_SERVICE));
269230
remoteOperation = getRemoteOperation(span, RPC_METHOD);
270231
} else if (isKeyPresent(span, DB_SYSTEM)
271232
|| isKeyPresent(span, DB_OPERATION)
@@ -365,6 +326,72 @@ private static String generateRemoteService(SpanData span) {
365326
return remoteService;
366327
}
367328

329+
/**
330+
* If the span is an AWS SDK span, normalize the name to align with <a
331+
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
332+
* Cloud Control resource format</a> as much as possible, with special attention to services we
333+
* can detect remote resource information for. Long term, we would like to normalize service name
334+
* in the upstream.
335+
*/
336+
private static String normalizeRemoteServiceName(SpanData span, String serviceName) {
337+
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
338+
switch (serviceName) {
339+
case "AmazonDynamoDBv2": // AWS SDK v1
340+
case "DynamoDb": // AWS SDK v2
341+
return NORMALIZED_DYNAMO_DB_SERVICE_NAME;
342+
case "AmazonKinesis": // AWS SDK v1
343+
case "Kinesis": // AWS SDK v2
344+
return NORMALIZED_KINESIS_SERVICE_NAME;
345+
case "Amazon S3": // AWS SDK v1
346+
case "S3": // AWS SDK v2
347+
return NORMALIZED_S3_SERVICE_NAME;
348+
case "AmazonSQS": // AWS SDK v1
349+
case "Sqs": // AWS SDK v2
350+
return NORMALIZED_SQS_SERVICE_NAME;
351+
default:
352+
return "AWS::" + serviceName;
353+
}
354+
}
355+
return serviceName;
356+
}
357+
358+
/**
359+
* Remote resource attributes {@link AwsAttributeKeys#AWS_REMOTE_RESOURCE_TYPE} and {@link
360+
* AwsAttributeKeys#AWS_REMOTE_RESOURCE_IDENTIFIER} are used to store information about the
361+
* resource associated with the remote invocation, such as S3 bucket name, etc. We should only
362+
* ever set both type and identifier or neither.
363+
*
364+
* <p>AWS resources type and identifier adhere to <a
365+
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
366+
* Cloud Control resource format</a>.
367+
*/
368+
private static void setRemoteResourceTypeAndIdentifier(SpanData span, AttributesBuilder builder) {
369+
Optional<String> remoteResourceType = Optional.empty();
370+
Optional<String> remoteResourceIdentifier = Optional.empty();
371+
372+
if (isKeyPresent(span, AWS_TABLE_NAME)) {
373+
remoteResourceType = Optional.of(NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table");
374+
remoteResourceIdentifier = Optional.ofNullable(span.getAttributes().get(AWS_TABLE_NAME));
375+
} else if (isKeyPresent(span, AWS_STREAM_NAME)) {
376+
remoteResourceType = Optional.of(NORMALIZED_KINESIS_SERVICE_NAME + "::Stream");
377+
remoteResourceIdentifier = Optional.ofNullable(span.getAttributes().get(AWS_STREAM_NAME));
378+
} else if (isKeyPresent(span, AWS_BUCKET_NAME)) {
379+
remoteResourceType = Optional.of(NORMALIZED_S3_SERVICE_NAME + "::Bucket");
380+
remoteResourceIdentifier = Optional.ofNullable(span.getAttributes().get(AWS_BUCKET_NAME));
381+
} else if (isKeyPresent(span, AWS_QUEUE_NAME)) {
382+
remoteResourceType = Optional.of(NORMALIZED_SQS_SERVICE_NAME + "::Queue");
383+
remoteResourceIdentifier = Optional.ofNullable(span.getAttributes().get(AWS_QUEUE_NAME));
384+
} else if (isKeyPresent(span, AWS_QUEUE_URL)) {
385+
remoteResourceType = Optional.of(NORMALIZED_SQS_SERVICE_NAME + "::Queue");
386+
remoteResourceIdentifier = SqsUrlParser.getQueueName(span.getAttributes().get(AWS_QUEUE_URL));
387+
}
388+
389+
if (remoteResourceType.isPresent() && remoteResourceIdentifier.isPresent()) {
390+
builder.put(AWS_REMOTE_RESOURCE_TYPE, remoteResourceType.get());
391+
builder.put(AWS_REMOTE_RESOURCE_IDENTIFIER, remoteResourceIdentifier.get());
392+
}
393+
}
394+
368395
/** Span kind is needed for differentiating metrics in the EMF exporter */
369396
private static void setSpanKindForService(SpanData span, AttributesBuilder builder) {
370397
String spanKind = span.getKind().name();

0 commit comments

Comments
 (0)