16
16
package software .amazon .opentelemetry .javaagent .providers ;
17
17
18
18
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 ;
19
21
import static io .opentelemetry .semconv .SemanticAttributes .DB_OPERATION ;
20
22
import static io .opentelemetry .semconv .SemanticAttributes .DB_STATEMENT ;
21
23
import static io .opentelemetry .semconv .SemanticAttributes .DB_SYSTEM ;
34
36
import static io .opentelemetry .semconv .SemanticAttributes .PEER_SERVICE ;
35
37
import static io .opentelemetry .semconv .SemanticAttributes .RPC_METHOD ;
36
38
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 ;
37
43
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_BUCKET_NAME ;
38
44
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_OPERATION ;
39
45
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_SERVICE ;
52
58
import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .UNKNOWN_REMOTE_OPERATION ;
53
59
import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .UNKNOWN_REMOTE_SERVICE ;
54
60
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 ;
55
63
import static software .amazon .opentelemetry .javaagent .providers .AwsSpanProcessingUtil .isKeyPresent ;
56
64
57
65
import io .opentelemetry .api .common .AttributeKey ;
66
74
import io .opentelemetry .semconv .SemanticAttributes ;
67
75
import java .lang .reflect .Method ;
68
76
import java .net .MalformedURLException ;
77
+ import java .net .URI ;
78
+ import java .net .URISyntaxException ;
69
79
import java .net .URL ;
70
80
import java .util .HashMap ;
71
81
import java .util .Map ;
@@ -98,6 +108,8 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
98
108
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
99
109
private static final String GRAPHQL = "graphql" ;
100
110
111
+ private static final String DB_CONNECTION_RESOURCE_TYPE = "DB::Connection" ;
112
+
101
113
// As per
102
114
// https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#opentelemetry-resource
103
115
// 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)
222
234
private static void setRemoteServiceAndOperation (SpanData span , AttributesBuilder builder ) {
223
235
String remoteService = UNKNOWN_REMOTE_SERVICE ;
224
236
String remoteOperation = UNKNOWN_REMOTE_OPERATION ;
237
+
225
238
if (isKeyPresent (span , AWS_REMOTE_SERVICE ) || isKeyPresent (span , AWS_REMOTE_OPERATION )) {
226
239
remoteService = getRemoteService (span , AWS_REMOTE_SERVICE );
227
240
remoteOperation = getRemoteOperation (span , AWS_REMOTE_OPERATION );
228
241
} else if (isKeyPresent (span , RPC_SERVICE ) || isKeyPresent (span , RPC_METHOD )) {
229
242
remoteService = normalizeRemoteServiceName (span , getRemoteService (span , RPC_SERVICE ));
230
243
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 )) {
234
246
remoteService = getRemoteService (span , DB_SYSTEM );
235
247
if (isKeyPresent (span , DB_OPERATION )) {
236
248
remoteOperation = getRemoteOperation (span , DB_OPERATION );
@@ -359,7 +371,8 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa
359
371
* Remote resource attributes {@link AwsAttributeKeys#AWS_REMOTE_RESOURCE_TYPE} and {@link
360
372
* AwsAttributeKeys#AWS_REMOTE_RESOURCE_IDENTIFIER} are used to store information about the
361
373
* 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 ^^.
363
376
*
364
377
* <p>AWS resources type and identifier adhere to <a
365
378
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
@@ -369,21 +382,31 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
369
382
Optional <String > remoteResourceType = Optional .empty ();
370
383
Optional <String > remoteResourceIdentifier = Optional .empty ();
371
384
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 );
387
410
}
388
411
389
412
if (remoteResourceType .isPresent () && remoteResourceIdentifier .isPresent ()) {
@@ -392,6 +415,88 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
392
415
}
393
416
}
394
417
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
+
395
500
/** Span kind is needed for differentiating metrics in the EMF exporter */
396
501
private static void setSpanKindForService (SpanData span , AttributesBuilder builder ) {
397
502
String spanKind = span .getKind ().name ();
0 commit comments