Skip to content

Commit b30602f

Browse files
authored
Emit AWS SDK exceptions as logs under the opt-in preview (#18894)
1 parent 611e2e5 commit b30602f

6 files changed

Lines changed: 107 additions & 25 deletions

File tree

instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ tasks {
5858
jvmArgs("-Dotel.semconv-stability.opt-in=database")
5959
}
6060

61+
val testExceptionSignalLogs by registering(Test::class) {
62+
testClassesDirs = sourceSets.test.get().output.classesDirs
63+
classpath = sourceSets.test.get().runtimeClasspath
64+
jvmArgs("-Dotel.semconv.exception.signal.preview=logs")
65+
}
66+
6167
check {
62-
dependsOn(testing.suites, testStableSemconv)
68+
dependsOn(testing.suites, testStableSemconv, testExceptionSignalLogs)
6369
}
6470
}

instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/AwsSdkInstrumenterFactory.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55

66
package io.opentelemetry.instrumentation.awssdk.v1_11.internal;
77

8+
import static io.opentelemetry.instrumentation.api.incubator.semconv.db.internal.DbExceptionEventExtractors.setDbClientExceptionEventExtractor;
9+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor;
10+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor;
11+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor;
12+
import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor;
813
import static java.util.Arrays.asList;
9-
import static java.util.Collections.emptyList;
1014
import static java.util.Collections.singletonList;
1115

1216
import com.amazonaws.Request;
@@ -81,7 +85,7 @@ public Instrumenter<Request<?>, Response<?>> requestInstrumenter() {
8185
new AwsSdkSpanNameExtractor(),
8286
SpanKindExtractor.alwaysClient(),
8387
attributesExtractors(),
84-
emptyList(),
88+
builder -> setRpcClientExceptionEventExtractor(builder),
8589
true);
8690
}
8791

@@ -110,6 +114,7 @@ public Instrumenter<SqsReceiveRequest, Response<?>> consumerReceiveInstrumenter(
110114
SpanKindExtractor.alwaysConsumer(),
111115
toSqsRequestExtractors(attributesExtractors()),
112116
singletonList(messagingAttributeExtractor),
117+
builder -> setMessagingReceiveExceptionEventExtractor(builder),
113118
messagingReceiveInstrumentationEnabled);
114119
}
115120

@@ -126,6 +131,7 @@ public Instrumenter<SqsProcessRequest, Response<?>> consumerProcessInstrumenter(
126131
MessagingSpanNameExtractor.create(getter, operation))
127132
.addAttributesExtractors(toSqsRequestExtractors(attributesExtractors()))
128133
.addAttributesExtractor(messagingAttributeExtractor);
134+
setMessagingProcessExceptionEventExtractor(builder);
129135

130136
if (messagingReceiveInstrumentationEnabled) {
131137
builder.addSpanLinksExtractor(
@@ -178,6 +184,7 @@ public Instrumenter<Request<?>, Response<?>> producerInstrumenter() {
178184
SpanKindExtractor.alwaysProducer(),
179185
attributesExtractors(),
180186
singletonList(messagingAttributeExtractor),
187+
builder -> setMessagingSendExceptionEventExtractor(builder),
181188
true);
182189
}
183190

@@ -187,10 +194,12 @@ public Instrumenter<Request<?>, Response<?>> dynamoDbInstrumenter() {
187194
new AwsSdkSpanNameExtractor(),
188195
SpanKindExtractor.alwaysClient(),
189196
attributesExtractors(),
190-
builder ->
191-
builder
192-
.addAttributesExtractor(new DynamoDbAttributesExtractor())
193-
.addOperationMetrics(DbClientMetrics.get()),
197+
builder -> {
198+
builder
199+
.addAttributesExtractor(new DynamoDbAttributesExtractor())
200+
.addOperationMetrics(DbClientMetrics.get());
201+
setDbClientExceptionEventExtractor(builder);
202+
},
194203
true);
195204
}
196205

@@ -200,13 +209,17 @@ private static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> createInstrum
200209
SpanKindExtractor<REQUEST> spanKindExtractor,
201210
List<? extends AttributesExtractor<? super REQUEST, ? super RESPONSE>> attributeExtractors,
202211
List<AttributesExtractor<REQUEST, RESPONSE>> additionalAttributeExtractors,
212+
Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> exceptionEventCustomizer,
203213
boolean enabled) {
204214
return createInstrumenter(
205215
openTelemetry,
206216
spanNameExtractor,
207217
spanKindExtractor,
208218
attributeExtractors,
209-
builder -> builder.addAttributesExtractors(additionalAttributeExtractors),
219+
builder -> {
220+
builder.addAttributesExtractors(additionalAttributeExtractors);
221+
exceptionEventCustomizer.accept(builder);
222+
},
210223
enabled);
211224
}
212225

instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractS3ClientTest.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import static io.opentelemetry.api.common.AttributeKey.stringKey;
99
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
10+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs;
11+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsSpanEvents;
1012
import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT;
1113
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
1214
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;
@@ -29,6 +31,7 @@
2931
import com.amazonaws.retry.PredefinedRetryPolicies;
3032
import com.amazonaws.services.s3.AmazonS3;
3133
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
34+
import io.opentelemetry.api.logs.Severity;
3235
import io.opentelemetry.api.trace.Span;
3336
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
3437
import io.opentelemetry.sdk.trace.data.StatusData;
@@ -120,7 +123,7 @@ void testSendRequestToClosedPort() {
120123
span.hasName("S3.GetObject")
121124
.hasKind(CLIENT)
122125
.hasStatus(StatusData.error())
123-
.hasException(caught)
126+
.hasException(emitExceptionAsSpanEvents() ? caught : null)
124127
.hasNoParent()
125128
.hasAttributesSatisfyingExactly(
126129
equalTo(URL_FULL, "http://127.0.0.1:" + UNUSABLE_PORT),
@@ -133,6 +136,17 @@ void testSendRequestToClosedPort() {
133136
equalTo(stringKey("aws.agent"), "java-aws-sdk"),
134137
equalTo(AWS_S3_BUCKET, "someBucket"),
135138
equalTo(ERROR_TYPE, SdkClientException.class.getName()))));
139+
140+
if (emitExceptionAsLogs()) {
141+
testing()
142+
.waitAndAssertLogRecords(
143+
logRecord ->
144+
logRecord
145+
.hasSeverity(Severity.WARN)
146+
.hasEventName("rpc.client.call.exception")
147+
.hasException(caught)
148+
.hasTotalAttributeCount(3));
149+
}
136150
}
137151

138152
@Test
@@ -154,6 +168,9 @@ void testTimeoutAndRetryErrorsNotCaptured() {
154168
Throwable caught = catchThrowable(() -> client.getObject("someBucket", "someKey"));
155169
assertThat(caught).isInstanceOf(AmazonClientException.class);
156170
assertThat(Span.current().getSpanContext().isValid()).isFalse();
171+
SdkClientException error =
172+
new SdkClientException(
173+
"Unable to execute HTTP request: Request did not complete before the request timeout configuration.");
157174

158175
testing()
159176
.waitAndAssertTraces(
@@ -164,9 +181,7 @@ void testTimeoutAndRetryErrorsNotCaptured() {
164181
.hasKind(CLIENT)
165182
.hasStatus(StatusData.error())
166183
.hasNoParent()
167-
.hasException(
168-
new SdkClientException(
169-
"Unable to execute HTTP request: Request did not complete before the request timeout configuration."))
184+
.hasException(emitExceptionAsSpanEvents() ? error : null)
170185
.hasAttributesSatisfyingExactly(
171186
equalTo(URL_FULL, server.httpUri().toString()),
172187
equalTo(HTTP_REQUEST_METHOD, "GET"),
@@ -178,5 +193,16 @@ void testTimeoutAndRetryErrorsNotCaptured() {
178193
equalTo(stringKey("aws.agent"), "java-aws-sdk"),
179194
equalTo(AWS_S3_BUCKET, "someBucket"),
180195
equalTo(ERROR_TYPE, SdkClientException.class.getName()))));
196+
197+
if (emitExceptionAsLogs()) {
198+
testing()
199+
.waitAndAssertLogRecords(
200+
logRecord ->
201+
logRecord
202+
.hasSeverity(Severity.WARN)
203+
.hasEventName("rpc.client.call.exception")
204+
.hasException(error)
205+
.hasTotalAttributeCount(3));
206+
}
181207
}
182208
}

instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,14 @@ tasks {
8282
jvmArgs("-Dotel.semconv-stability.opt-in=database")
8383
}
8484

85+
val testExceptionSignalLogs by registering(Test::class) {
86+
testClassesDirs = sourceSets.test.get().output.classesDirs
87+
classpath = sourceSets.test.get().runtimeClasspath
88+
89+
jvmArgs("-Dotel.semconv.exception.signal.preview=logs")
90+
}
91+
8592
check {
86-
dependsOn(testing.suites, testStableSemconv)
93+
dependsOn(testing.suites, testStableSemconv, testExceptionSignalLogs)
8794
}
8895
}

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55

66
package io.opentelemetry.instrumentation.awssdk.v2_2.internal;
77

8+
import static io.opentelemetry.instrumentation.api.incubator.semconv.db.internal.DbExceptionEventExtractors.setDbClientExceptionEventExtractor;
9+
import static io.opentelemetry.instrumentation.api.incubator.semconv.genai.internal.GenAiExceptionEventExtractors.setGenAiClientExceptionEventExtractor;
10+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingProcessExceptionEventExtractor;
11+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingReceiveExceptionEventExtractor;
12+
import static io.opentelemetry.instrumentation.api.incubator.semconv.messaging.internal.MessagingExceptionEventExtractors.setMessagingSendExceptionEventExtractor;
13+
import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.internal.RpcExceptionEventExtractors.setRpcClientExceptionEventExtractor;
814
import static java.util.Arrays.asList;
9-
import static java.util.Collections.emptyList;
1015
import static java.util.Collections.singletonList;
1116

1217
import io.opentelemetry.api.OpenTelemetry;
@@ -103,7 +108,7 @@ public Instrumenter<ExecutionAttributes, Response> requestInstrumenter() {
103108
AwsSdkInstrumenterFactory::spanName,
104109
SpanKindExtractor.alwaysClient(),
105110
attributesExtractors(),
106-
emptyList(),
111+
builder -> setRpcClientExceptionEventExtractor(builder),
107112
true);
108113
}
109114

@@ -138,6 +143,7 @@ public Instrumenter<SqsReceiveRequest, Response> consumerReceiveInstrumenter() {
138143
SpanKindExtractor.alwaysConsumer(),
139144
toSqsRequestExtractors(consumerAttributesExtractors()),
140145
singletonList(messagingAttributeExtractor),
146+
builder -> setMessagingReceiveExceptionEventExtractor(builder),
141147
messagingReceiveInstrumentationEnabled);
142148
}
143149

@@ -152,6 +158,7 @@ public Instrumenter<SqsProcessRequest, Response> consumerProcessInstrumenter() {
152158
MessagingSpanNameExtractor.create(getter, operation))
153159
.addAttributesExtractors(toSqsRequestExtractors(consumerAttributesExtractors()))
154160
.addAttributesExtractor(messagingAttributesExtractor(getter, operation));
161+
setMessagingProcessExceptionEventExtractor(builder);
155162

156163
if (messagingReceiveInstrumentationEnabled) {
157164
builder.addSpanLinksExtractor(
@@ -205,6 +212,7 @@ public Instrumenter<ExecutionAttributes, Response> producerInstrumenter() {
205212
SpanKindExtractor.alwaysProducer(),
206213
attributesExtractors(),
207214
singletonList(messagingAttributeExtractor),
215+
builder -> setMessagingSendExceptionEventExtractor(builder),
208216
true);
209217
}
210218

@@ -214,10 +222,12 @@ public Instrumenter<ExecutionAttributes, Response> dynamoDbInstrumenter() {
214222
AwsSdkInstrumenterFactory::spanName,
215223
SpanKindExtractor.alwaysClient(),
216224
attributesExtractors(),
217-
builder ->
218-
builder
219-
.addAttributesExtractor(new DynamoDbAttributesExtractor())
220-
.addOperationMetrics(DbClientMetrics.get()),
225+
builder -> {
226+
builder
227+
.addAttributesExtractor(new DynamoDbAttributesExtractor())
228+
.addOperationMetrics(DbClientMetrics.get());
229+
setDbClientExceptionEventExtractor(builder);
230+
},
221231
true);
222232
}
223233

@@ -228,10 +238,12 @@ public Instrumenter<ExecutionAttributes, Response> bedrockRuntimeInstrumenter()
228238
GenAiSpanNameExtractor.create(getter),
229239
SpanKindExtractor.alwaysClient(),
230240
attributesExtractors(),
231-
builder ->
232-
builder
233-
.addAttributesExtractor(GenAiAttributesExtractor.create(getter))
234-
.addOperationMetrics(GenAiClientMetrics.get()),
241+
builder -> {
242+
builder
243+
.addAttributesExtractor(GenAiAttributesExtractor.create(getter))
244+
.addOperationMetrics(GenAiClientMetrics.get());
245+
setGenAiClientExceptionEventExtractor(builder);
246+
},
235247
true);
236248
}
237249

@@ -245,14 +257,18 @@ private static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> createInstrum
245257
SpanKindExtractor<REQUEST> spanKindExtractor,
246258
List<? extends AttributesExtractor<? super REQUEST, ? super RESPONSE>> attributeExtractors,
247259
List<AttributesExtractor<REQUEST, RESPONSE>> additionalAttributeExtractors,
260+
Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> exceptionEventCustomizer,
248261
boolean enabled) {
249262

250263
return createInstrumenter(
251264
openTelemetry,
252265
spanNameExtractor,
253266
spanKindExtractor,
254267
attributeExtractors,
255-
builder -> builder.addAttributesExtractors(additionalAttributeExtractors),
268+
builder -> {
269+
builder.addAttributesExtractors(additionalAttributeExtractors);
270+
exceptionEventCustomizer.accept(builder);
271+
},
256272
enabled);
257273
}
258274

instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package io.opentelemetry.instrumentation.awssdk.v2_2;
77

88
import static io.opentelemetry.api.common.AttributeKey.stringKey;
9+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsLogs;
10+
import static io.opentelemetry.instrumentation.api.internal.SemconvExceptionSignal.emitExceptionAsSpanEvents;
911
import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps;
1012
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
1113
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
@@ -37,6 +39,7 @@
3739
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3840
import static org.assertj.core.api.Assertions.catchThrowable;
3941

42+
import io.opentelemetry.api.logs.Severity;
4043
import io.opentelemetry.api.trace.SpanKind;
4144
import io.opentelemetry.context.Context;
4245
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
@@ -891,7 +894,7 @@ void testTimeoutAndRetryErrorsAreNotCaptured() {
891894
span.hasName("S3.GetObject")
892895
.hasKind(SpanKind.CLIENT)
893896
.hasStatus(StatusData.error())
894-
.hasException(thrown)
897+
.hasException(emitExceptionAsSpanEvents() ? thrown : null)
895898
.hasNoParent()
896899
.hasAttributesSatisfyingExactly(
897900
// Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the
@@ -920,6 +923,17 @@ void testTimeoutAndRetryErrorsAreNotCaptured() {
920923
equalTo(RPC_METHOD, "GetObject"),
921924
equalTo(stringKey("aws.agent"), "java-aws-sdk"),
922925
equalTo(AWS_S3_BUCKET, "somebucket"))));
926+
927+
if (emitExceptionAsLogs()) {
928+
getTesting()
929+
.waitAndAssertLogRecords(
930+
logRecord ->
931+
logRecord
932+
.hasSeverity(Severity.WARN)
933+
.hasEventName("rpc.client.call.exception")
934+
.hasException(thrown)
935+
.hasTotalAttributeCount(3));
936+
}
923937
}
924938

925939
// regression test for

0 commit comments

Comments
 (0)