Skip to content

Commit e709054

Browse files
committed
Add support on Database RemoteResourceType and RemoteResourceIdentifier
1 parent fe6a8f3 commit e709054

File tree

3 files changed

+303
-20
lines changed

3 files changed

+303
-20
lines changed

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

Lines changed: 124 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package software.amazon.opentelemetry.javaagent.providers;
1717

1818
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
19+
import static io.opentelemetry.semconv.SemanticAttributes.DB_CONNECTION_STRING;
20+
import static io.opentelemetry.semconv.SemanticAttributes.DB_NAME;
1921
import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION;
2022
import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT;
2123
import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM;
@@ -34,6 +36,10 @@
3436
import static io.opentelemetry.semconv.SemanticAttributes.PEER_SERVICE;
3537
import static io.opentelemetry.semconv.SemanticAttributes.RPC_METHOD;
3638
import static io.opentelemetry.semconv.SemanticAttributes.RPC_SERVICE;
39+
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_ADDRESS;
40+
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT;
41+
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_ADDRESS;
42+
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT;
3743
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
3844
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
3945
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
@@ -52,6 +58,8 @@
5258
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION;
5359
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
5460
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_SERVICE;
61+
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isAwsSDKSpan;
62+
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan;
5563
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent;
5664

5765
import io.opentelemetry.api.common.AttributeKey;
@@ -66,6 +74,8 @@
6674
import io.opentelemetry.semconv.SemanticAttributes;
6775
import java.lang.reflect.Method;
6876
import java.net.MalformedURLException;
77+
import java.net.URI;
78+
import java.net.URISyntaxException;
6979
import java.net.URL;
7080
import java.util.HashMap;
7181
import java.util.Map;
@@ -98,6 +108,8 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
98108
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
99109
private static final String GRAPHQL = "graphql";
100110

111+
private static final String DB_CONNECTION_RESOURCE_TYPE = "DB::Connection";
112+
101113
// As per
102114
// https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#opentelemetry-resource
103115
// If service name is not specified, SDK defaults the service name to unknown_service:java
@@ -222,15 +234,15 @@ private static void setEgressOperation(SpanData span, AttributesBuilder builder)
222234
private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilder builder) {
223235
String remoteService = UNKNOWN_REMOTE_SERVICE;
224236
String remoteOperation = UNKNOWN_REMOTE_OPERATION;
237+
225238
if (isKeyPresent(span, AWS_REMOTE_SERVICE) || isKeyPresent(span, AWS_REMOTE_OPERATION)) {
226239
remoteService = getRemoteService(span, AWS_REMOTE_SERVICE);
227240
remoteOperation = getRemoteOperation(span, AWS_REMOTE_OPERATION);
228241
} else if (isKeyPresent(span, RPC_SERVICE) || isKeyPresent(span, RPC_METHOD)) {
229242
remoteService = normalizeRemoteServiceName(span, getRemoteService(span, RPC_SERVICE));
230243
remoteOperation = getRemoteOperation(span, RPC_METHOD);
231-
} else if (isKeyPresent(span, DB_SYSTEM)
232-
|| isKeyPresent(span, DB_OPERATION)
233-
|| isKeyPresent(span, DB_STATEMENT)) {
244+
245+
} else if (isDBSpan(span)) {
234246
remoteService = getRemoteService(span, DB_SYSTEM);
235247
if (isKeyPresent(span, DB_OPERATION)) {
236248
remoteOperation = getRemoteOperation(span, DB_OPERATION);
@@ -359,7 +371,8 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa
359371
* Remote resource attributes {@link AwsAttributeKeys#AWS_REMOTE_RESOURCE_TYPE} and {@link
360372
* AwsAttributeKeys#AWS_REMOTE_RESOURCE_IDENTIFIER} are used to store information about the
361373
* resource associated with the remote invocation, such as S3 bucket name, etc. We should only
362-
* ever set both type and identifier or neither.
374+
* ever set both type and identifier or neither. If any identifier value contains | or ^ , they
375+
* will be replaced with ^| or ^^.
363376
*
364377
* <p>AWS resources type and identifier adhere to <a
365378
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
@@ -369,21 +382,31 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
369382
Optional<String> remoteResourceType = Optional.empty();
370383
Optional<String> remoteResourceIdentifier = Optional.empty();
371384

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));
385+
if (isAwsSDKSpan(span)) {
386+
if (isKeyPresent(span, AWS_TABLE_NAME)) {
387+
remoteResourceType = Optional.of(NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table");
388+
remoteResourceIdentifier =
389+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_TABLE_NAME)));
390+
} else if (isKeyPresent(span, AWS_STREAM_NAME)) {
391+
remoteResourceType = Optional.of(NORMALIZED_KINESIS_SERVICE_NAME + "::Stream");
392+
remoteResourceIdentifier =
393+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_STREAM_NAME)));
394+
} else if (isKeyPresent(span, AWS_BUCKET_NAME)) {
395+
remoteResourceType = Optional.of(NORMALIZED_S3_SERVICE_NAME + "::Bucket");
396+
remoteResourceIdentifier =
397+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_BUCKET_NAME)));
398+
} else if (isKeyPresent(span, AWS_QUEUE_NAME)) {
399+
remoteResourceType = Optional.of(NORMALIZED_SQS_SERVICE_NAME + "::Queue");
400+
remoteResourceIdentifier =
401+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_QUEUE_NAME)));
402+
} else if (isKeyPresent(span, AWS_QUEUE_URL)) {
403+
remoteResourceType = Optional.of(NORMALIZED_SQS_SERVICE_NAME + "::Queue");
404+
remoteResourceIdentifier =
405+
SqsUrlParser.getQueueName(escapeDelimiters(span.getAttributes().get(AWS_QUEUE_URL)));
406+
}
407+
} else if (isDBSpan(span)) {
408+
remoteResourceType = Optional.of(DB_CONNECTION_RESOURCE_TYPE);
409+
remoteResourceIdentifier = getDbConnection(span);
387410
}
388411

389412
if (remoteResourceType.isPresent() && remoteResourceIdentifier.isPresent()) {
@@ -392,6 +415,88 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
392415
}
393416
}
394417

418+
/**
419+
* RemoteResourceIdentifier is populated with rule <code>
420+
* ^[{db.name}|]?{address}[|{port}]?
421+
* </code>
422+
*
423+
* <pre>
424+
* {address} attribute is retrieved in priority order:
425+
* - {@link SemanticAttributes#SERVER_ADDRESS},
426+
* - {@link SemanticAttributes#NET_PEER_NAME},
427+
* - {@link SemanticAttributes#SERVER_SOCKET_ADDRESS}
428+
* - {@link SemanticAttributes#DB_CONNECTION_STRING}-Hostname
429+
* </pre>
430+
*
431+
* <pre>
432+
* {port} attribute is retrieved in priority order:
433+
* - {@link SemanticAttributes#SERVER_PORT},
434+
* - {@link SemanticAttributes#NET_PEER_PORT},
435+
* - {@link SemanticAttributes#SERVER_SOCKET_PORT}
436+
* - {@link SemanticAttributes#DB_CONNECTION_STRING}-Port
437+
* </pre>
438+
*
439+
* If address is not present, neither RemoteResourceType nor RemoteResourceIdentifier will be
440+
* provided.
441+
*/
442+
private static Optional<String> getDbConnection(SpanData span) {
443+
String dbName = span.getAttributes().get(DB_NAME);
444+
Optional<String> dbConnection = Optional.empty();
445+
446+
if (isKeyPresent(span, SERVER_ADDRESS)) {
447+
String serverAddress = span.getAttributes().get(SERVER_ADDRESS);
448+
Long serverPort = span.getAttributes().get(SERVER_PORT);
449+
dbConnection = buildDbConnection(serverAddress, serverPort);
450+
} else if (isKeyPresent(span, NET_PEER_NAME)) {
451+
String networkPeerAddress = span.getAttributes().get(NET_PEER_NAME);
452+
Long networkPeerPort = span.getAttributes().get(NET_PEER_PORT);
453+
dbConnection = buildDbConnection(networkPeerAddress, networkPeerPort);
454+
} else if (isKeyPresent(span, SERVER_SOCKET_ADDRESS)) {
455+
String serverSocketAddress = span.getAttributes().get(SERVER_SOCKET_ADDRESS);
456+
Long serverSocketPort = span.getAttributes().get(SERVER_SOCKET_PORT);
457+
dbConnection = buildDbConnection(serverSocketAddress, serverSocketPort);
458+
} else if (isKeyPresent(span, DB_CONNECTION_STRING)) {
459+
String connectionString = span.getAttributes().get(DB_CONNECTION_STRING);
460+
dbConnection = buildDbConnection(connectionString);
461+
}
462+
463+
// return empty resource identifier if db server is not found
464+
if (dbConnection.isPresent() && dbName != null) {
465+
return Optional.of(escapeDelimiters(dbName) + "|" + dbConnection.get());
466+
}
467+
return dbConnection;
468+
}
469+
470+
private static Optional<String> buildDbConnection(String address, Long port) {
471+
return Optional.of(escapeDelimiters(address) + (port != null ? "|" + port : ""));
472+
}
473+
474+
private static Optional<String> buildDbConnection(String connectionString) {
475+
URI uri;
476+
String address;
477+
int port;
478+
try {
479+
uri = new URI(connectionString);
480+
address = uri.getHost();
481+
port = uri.getPort();
482+
} catch (URISyntaxException e) {
483+
logger.log(Level.FINEST, "invalid DB ConnectionString: ", connectionString);
484+
return Optional.empty();
485+
}
486+
487+
if (address == null) {
488+
return Optional.empty();
489+
}
490+
return Optional.of(escapeDelimiters(address) + (port != -1 ? "|" + port : ""));
491+
}
492+
493+
private static String escapeDelimiters(String input) {
494+
if (input == null) {
495+
return null;
496+
}
497+
return input.replace("^", "^^").replace("|", "^|");
498+
}
499+
395500
/** Span kind is needed for differentiating metrics in the EMF exporter */
396501
private static void setSpanKindForService(SpanData span, AttributesBuilder builder) {
397502
String spanKind = span.getKind().name();

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
package software.amazon.opentelemetry.javaagent.providers;
1717

18+
import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION;
19+
import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT;
20+
import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM;
1821
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD;
1922
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_TARGET;
2023
import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION;
@@ -218,4 +221,11 @@ private static String generateIngressOperation(SpanData span) {
218221
}
219222
return operation;
220223
}
224+
225+
// Check if the current Span adheres to database semantic conventions
226+
static boolean isDBSpan(SpanData span) {
227+
return isKeyPresent(span, DB_SYSTEM)
228+
|| isKeyPresent(span, DB_OPERATION)
229+
|| isKeyPresent(span, DB_STATEMENT);
230+
}
221231
}

0 commit comments

Comments
 (0)