1616package software .amazon .opentelemetry .javaagent .providers ;
1717
1818import 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 ;
1921import static io .opentelemetry .semconv .SemanticAttributes .DB_OPERATION ;
2022import static io .opentelemetry .semconv .SemanticAttributes .DB_STATEMENT ;
2123import static io .opentelemetry .semconv .SemanticAttributes .DB_SYSTEM ;
3436import static io .opentelemetry .semconv .SemanticAttributes .PEER_SERVICE ;
3537import static io .opentelemetry .semconv .SemanticAttributes .RPC_METHOD ;
3638import 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 ;
3743import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_BUCKET_NAME ;
3844import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_OPERATION ;
3945import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_SERVICE ;
5258import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .UNKNOWN_REMOTE_OPERATION ;
5359import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .UNKNOWN_REMOTE_SERVICE ;
5460import 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 ;
5563import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .isKeyPresent ;
5664
5765import io .opentelemetry .api .common .AttributeKey ;
6674import io .opentelemetry .semconv .SemanticAttributes ;
6775import java .lang .reflect .Method ;
6876import java .net .MalformedURLException ;
77+ import java .net .URI ;
78+ import java .net .URISyntaxException ;
6979import java .net .URL ;
7080import java .util .HashMap ;
7181import 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 ();
0 commit comments