diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts index a383ff0fa7fc..38a337eb35fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts @@ -58,7 +58,13 @@ tasks { jvmArgs("-Dotel.semconv-stability.opt-in=database") } + val testExceptionSignalLogs by registering(Test::class) { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + jvmArgs("-Dotel.semconv.exception.signal.preview=logs") + } + check { - dependsOn(testing.suites, testStableSemconv) + dependsOn(testing.suites, testStableSemconv, testExceptionSignalLogs) } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java index 5ee047d35c32..3a96403c8843 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java @@ -5,8 +5,12 @@ package io.opentelemetry.instrumentation.awssdk.v1_11.internal; +import static io.opentelemetry.instrumentation.api.incubator.semconv.db.internal.DbExceptionEventExtractors.setDbClientExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import com.amazonaws.Request; @@ -81,7 +85,7 @@ public Instrumenter, Response> requestInstrumenter() { new AwsSdkSpanNameExtractor(), SpanKindExtractor.alwaysClient(), attributesExtractors(), - emptyList(), + builder -> setRpcClientExceptionEventExtractor(builder), true); } @@ -110,6 +114,7 @@ public Instrumenter> consumerReceiveInstrumenter( SpanKindExtractor.alwaysConsumer(), toSqsRequestExtractors(attributesExtractors()), singletonList(messagingAttributeExtractor), + builder -> setMessagingReceiveExceptionEventExtractor(builder), messagingReceiveInstrumentationEnabled); } @@ -126,6 +131,7 @@ public Instrumenter> consumerProcessInstrumenter( MessagingSpanNameExtractor.create(getter, operation)) .addAttributesExtractors(toSqsRequestExtractors(attributesExtractors())) .addAttributesExtractor(messagingAttributeExtractor); + setMessagingProcessExceptionEventExtractor(builder); if (messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( @@ -178,6 +184,7 @@ public Instrumenter, Response> producerInstrumenter() { SpanKindExtractor.alwaysProducer(), attributesExtractors(), singletonList(messagingAttributeExtractor), + builder -> setMessagingSendExceptionEventExtractor(builder), true); } @@ -187,10 +194,12 @@ public Instrumenter, Response> dynamoDbInstrumenter() { new AwsSdkSpanNameExtractor(), SpanKindExtractor.alwaysClient(), attributesExtractors(), - builder -> - builder - .addAttributesExtractor(new DynamoDbAttributesExtractor()) - .addOperationMetrics(DbClientMetrics.get()), + builder -> { + builder + .addAttributesExtractor(new DynamoDbAttributesExtractor()) + .addOperationMetrics(DbClientMetrics.get()); + setDbClientExceptionEventExtractor(builder); + }, true); } @@ -200,13 +209,17 @@ private static Instrumenter createInstrum SpanKindExtractor spanKindExtractor, List> attributeExtractors, List> additionalAttributeExtractors, + Consumer> exceptionEventCustomizer, boolean enabled) { return createInstrumenter( openTelemetry, spanNameExtractor, spanKindExtractor, attributeExtractors, - builder -> builder.addAttributesExtractors(additionalAttributeExtractors), + builder -> { + builder.addAttributesExtractors(additionalAttributeExtractors); + exceptionEventCustomizer.accept(builder); + }, enabled); } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java index 61bfd7efd767..a12a649fa5e9 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java @@ -7,6 +7,8 @@ import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs; +import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsSpanEvents; import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; @@ -29,6 +31,7 @@ import com.amazonaws.retry.PredefinedRetryPolicies; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.trace.data.StatusData; @@ -120,7 +123,7 @@ void testSendRequestToClosedPort() { span.hasName("S3.GetObject") .hasKind(CLIENT) .hasStatus(StatusData.error()) - .hasException(caught) + .hasException(emitExceptionAsSpanEvents() ? caught : null) .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(URL_FULL, "http://127.0.0.1:" + UNUSABLE_PORT), @@ -133,6 +136,17 @@ void testSendRequestToClosedPort() { equalTo(stringKey("aws.agent"), "java-aws-sdk"), equalTo(AWS_S3_BUCKET, "someBucket"), equalTo(ERROR_TYPE, SdkClientException.class.getName())))); + + if (emitExceptionAsLogs()) { + testing() + .waitAndAssertLogRecords( + logRecord -> + logRecord + .hasSeverity(Severity.WARN) + .hasEventName("rpc.client.call.exception") + .hasException(caught) + .hasTotalAttributeCount(3)); + } } @Test @@ -154,6 +168,9 @@ void testTimeoutAndRetryErrorsNotCaptured() { Throwable caught = catchThrowable(() -> client.getObject("someBucket", "someKey")); assertThat(caught).isInstanceOf(AmazonClientException.class); assertThat(Span.current().getSpanContext().isValid()).isFalse(); + SdkClientException error = + new SdkClientException( + "Unable to execute HTTP request: Request did not complete before the request timeout configuration."); testing() .waitAndAssertTraces( @@ -164,9 +181,7 @@ void testTimeoutAndRetryErrorsNotCaptured() { .hasKind(CLIENT) .hasStatus(StatusData.error()) .hasNoParent() - .hasException( - new SdkClientException( - "Unable to execute HTTP request: Request did not complete before the request timeout configuration.")) + .hasException(emitExceptionAsSpanEvents() ? error : null) .hasAttributesSatisfyingExactly( equalTo(URL_FULL, server.httpUri().toString()), equalTo(HTTP_REQUEST_METHOD, "GET"), @@ -178,5 +193,16 @@ void testTimeoutAndRetryErrorsNotCaptured() { equalTo(stringKey("aws.agent"), "java-aws-sdk"), equalTo(AWS_S3_BUCKET, "someBucket"), equalTo(ERROR_TYPE, SdkClientException.class.getName())))); + + if (emitExceptionAsLogs()) { + testing() + .waitAndAssertLogRecords( + logRecord -> + logRecord + .hasSeverity(Severity.WARN) + .hasEventName("rpc.client.call.exception") + .hasException(error) + .hasTotalAttributeCount(3)); + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index f4ff61e2af88..fd2f8e1925c6 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -82,7 +82,14 @@ tasks { jvmArgs("-Dotel.semconv-stability.opt-in=database") } + val testExceptionSignalLogs by registering(Test::class) { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + + jvmArgs("-Dotel.semconv.exception.signal.preview=logs") + } + check { - dependsOn(testing.suites, testStableSemconv) + dependsOn(testing.suites, testStableSemconv, testExceptionSignalLogs) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java index 5456b6c11355..c3b154e26a4f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java @@ -5,8 +5,13 @@ package io.opentelemetry.instrumentation.awssdk.v2_2.internal; +import static io.opentelemetry.instrumentation.api.incubator.semconv.db.internal.DbExceptionEventExtractors.setDbClientExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.internal.GenAiExceptionEventExtractors.setGenAiClientExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor; +import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import io.opentelemetry.api.OpenTelemetry; @@ -103,7 +108,7 @@ public Instrumenter requestInstrumenter() { AwsSdkInstrumenterFactory::spanName, SpanKindExtractor.alwaysClient(), attributesExtractors(), - emptyList(), + builder -> setRpcClientExceptionEventExtractor(builder), true); } @@ -138,6 +143,7 @@ public Instrumenter consumerReceiveInstrumenter() { SpanKindExtractor.alwaysConsumer(), toSqsRequestExtractors(consumerAttributesExtractors()), singletonList(messagingAttributeExtractor), + builder -> setMessagingReceiveExceptionEventExtractor(builder), messagingReceiveInstrumentationEnabled); } @@ -152,6 +158,7 @@ public Instrumenter consumerProcessInstrumenter() { MessagingSpanNameExtractor.create(getter, operation)) .addAttributesExtractors(toSqsRequestExtractors(consumerAttributesExtractors())) .addAttributesExtractor(messagingAttributesExtractor(getter, operation)); + setMessagingProcessExceptionEventExtractor(builder); if (messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( @@ -205,6 +212,7 @@ public Instrumenter producerInstrumenter() { SpanKindExtractor.alwaysProducer(), attributesExtractors(), singletonList(messagingAttributeExtractor), + builder -> setMessagingSendExceptionEventExtractor(builder), true); } @@ -214,10 +222,12 @@ public Instrumenter dynamoDbInstrumenter() { AwsSdkInstrumenterFactory::spanName, SpanKindExtractor.alwaysClient(), attributesExtractors(), - builder -> - builder - .addAttributesExtractor(new DynamoDbAttributesExtractor()) - .addOperationMetrics(DbClientMetrics.get()), + builder -> { + builder + .addAttributesExtractor(new DynamoDbAttributesExtractor()) + .addOperationMetrics(DbClientMetrics.get()); + setDbClientExceptionEventExtractor(builder); + }, true); } @@ -228,10 +238,12 @@ public Instrumenter bedrockRuntimeInstrumenter() GenAiSpanNameExtractor.create(getter), SpanKindExtractor.alwaysClient(), attributesExtractors(), - builder -> - builder - .addAttributesExtractor(GenAiAttributesExtractor.create(getter)) - .addOperationMetrics(GenAiClientMetrics.get()), + builder -> { + builder + .addAttributesExtractor(GenAiAttributesExtractor.create(getter)) + .addOperationMetrics(GenAiClientMetrics.get()); + setGenAiClientExceptionEventExtractor(builder); + }, true); } @@ -245,6 +257,7 @@ private static Instrumenter createInstrum SpanKindExtractor spanKindExtractor, List> attributeExtractors, List> additionalAttributeExtractors, + Consumer> exceptionEventCustomizer, boolean enabled) { return createInstrumenter( @@ -252,7 +265,10 @@ private static Instrumenter createInstrum spanNameExtractor, spanKindExtractor, attributeExtractors, - builder -> builder.addAttributesExtractors(additionalAttributeExtractors), + builder -> { + builder.addAttributesExtractors(additionalAttributeExtractors); + exceptionEventCustomizer.accept(builder); + }, enabled); } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.java index 60598311785b..992a5982b622 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.java @@ -6,6 +6,8 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs; +import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsSpanEvents; import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -37,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; +import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; @@ -891,7 +894,7 @@ void testTimeoutAndRetryErrorsAreNotCaptured() { span.hasName("S3.GetObject") .hasKind(SpanKind.CLIENT) .hasStatus(StatusData.error()) - .hasException(thrown) + .hasException(emitExceptionAsSpanEvents() ? thrown : null) .hasNoParent() .hasAttributesSatisfyingExactly( // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the @@ -920,6 +923,17 @@ void testTimeoutAndRetryErrorsAreNotCaptured() { equalTo(RPC_METHOD, "GetObject"), equalTo(stringKey("aws.agent"), "java-aws-sdk"), equalTo(AWS_S3_BUCKET, "somebucket")))); + + if (emitExceptionAsLogs()) { + getTesting() + .waitAndAssertLogRecords( + logRecord -> + logRecord + .hasSeverity(Severity.WARN) + .hasEventName("rpc.client.call.exception") + .hasException(thrown) + .hasTotalAttributeCount(3)); + } } // regression test for