Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f4af01e
SPI on aws-sdk v2.2
anahatAWS May 27, 2025
d784099
SPI on aws-sdk v2.2
anahatAWS May 29, 2025
8c6036d
SPI on aws-sdk v2.2
anahatAWS Jun 2, 2025
e9b26d1
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 3, 2025
8effb7a
updated to match ADOT v2.11
anahatAWS Jun 3, 2025
c7f59f7
updated to match ADOT v2.11
anahatAWS Jun 3, 2025
40d5d0b
Added comments to explain code and commented out debugger code
anahatAWS Jun 3, 2025
3061c4a
Removed debugger v2 patching script and debugger comments
anahatAWS Jun 4, 2025
1e54bc2
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 4, 2025
f405de6
new TracingExecutionInterceptor implementation
anahatAWS Jun 5, 2025
bf1b5db
Simplified SPI and removed unused classes
anahatAWS Jun 6, 2025
837a63c
fixes AwsSdkRequest
anahatAWS Jun 6, 2025
e53795f
fixes AwsSdkRequest
anahatAWS Jun 6, 2025
82da476
fixes AwsSdkRequest
anahatAWS Jun 7, 2025
0cfcf97
fixes AwsSdkRequest
anahatAWS Jun 7, 2025
1d72356
fixes AwsSdkRequest
anahatAWS Jun 7, 2025
8b30ec1
fixes AwsSdkRequest
anahatAWS Jun 7, 2025
44ec960
debugging statemets added
anahatAWS Jun 8, 2025
e79bfea
debugging statemets added
anahatAWS Jun 8, 2025
c4bad5a
modified tracing execution interceptor to hook onto Execution Interce…
anahatAWS Jun 8, 2025
de877d3
modified tracing execution interceptor and test for V2
anahatAWS Jun 9, 2025
96bbdcd
modified tracing execution interceptor
anahatAWS Jun 9, 2025
81c95b2
modified tracing execution interceptor
anahatAWS Jun 11, 2025
0bcd848
added instrumenter factory and fixed order of interception with upstream
anahatAWS Jun 11, 2025
b721ecd
removed instrumenter factory and removed unused code
anahatAWS Jun 12, 2025
4d45e7f
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 12, 2025
494e0f6
Merge branch 'aws-observability:main' into testing-spi
anahatAWS Jun 12, 2025
56620b4
added response attribute modifications
anahatAWS Jun 12, 2025
85ab778
Merge remote-tracking branch 'origin/testing-spi' into testing-spi
anahatAWS Jun 12, 2025
9d92388
added response attribute modifications
anahatAWS Jun 12, 2025
d06b996
added response attribute modifications
anahatAWS Jun 12, 2025
16a73ca
added response attribute modifications
anahatAWS Jun 12, 2025
a4c2d9d
added response attribute modifications
anahatAWS Jun 12, 2025
1bc2c1d
added response attribute modifications
anahatAWS Jun 12, 2025
e4f6b8c
added response attribute modifications
anahatAWS Jun 12, 2025
9f2ffc7
added response attribute modifications
anahatAWS Jun 12, 2025
e9b2fc3
fixed formatting
anahatAWS Jun 12, 2025
69cfec5
fixed formatting
anahatAWS Jun 13, 2025
f3dee27
fixed formatting
anahatAWS Jun 13, 2025
6b3f471
fixed formatting
anahatAWS Jun 13, 2025
0d54bca
fixed formatting
anahatAWS Jun 13, 2025
2c4c7bf
added README.md for aws-sdk
anahatAWS Jun 13, 2025
a0fa5c9
added comments to instrumentation module and tracing execution interc…
anahatAWS Jun 13, 2025
f3f4f37
added comments for clarity
anahatAWS Jun 13, 2025
c2e53a4
Merge branch 'aws-observability:main' into testing-spi
anahatAWS Jun 16, 2025
c204c42
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 16, 2025
88a1fad
removed instrumenter factory and it's dependencies
anahatAWS Jun 16, 2025
ddf3e6d
Merge remote-tracking branch 'origin/testing-spi' into testing-spi
anahatAWS Jun 16, 2025
8cb3cc6
added comments for clarity and cleaned up build files
anahatAWS Jun 16, 2025
0b44af3
Updated aws-sdk v2.2 instrumentation and added README.md file
anahatAWS Jun 16, 2025
0749dfa
Changed from afterUnmarshalling to modifyResponse, updated README.md,…
anahatAWS Jun 20, 2025
2e1c8f0
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 20, 2025
b3b4b18
Merge branch 'aws-observability:main' into spi
anahatAWS Jun 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,445 changes: 1 addition & 1,444 deletions .github/patches/opentelemetry-java-instrumentation.patch

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/scripts/patch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if [[ -f "$OTEL_JAVA_PATCH" ]]; then
git commit -a -m "ADOT Patch release"
cd -
else
echo "Skiping patching opentelemetry-java"
echo "Skipping patching opentelemetry-java"
fi


Expand Down
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ plugins {
id("com.gradleup.shadow") apply false
}

configurations {
/*
We create a separate gradle configuration to grab a published Otel instrumentation agent.
We don't need the agent during development of this extension module.
This agent is used only during integration test.
*/
create("otel") // Explicitly create the 'otel' configuration
}

release {
defaultVersionStrategy = Strategies.getSNAPSHOT()
}
Expand Down
58 changes: 58 additions & 0 deletions instrumentation/aws-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## ADOT AWS SDK Instrumentation

### Overview
The aws-sdk instrumentation is an SPI-based implementation to extend the AWS SDK within the upstream OpenTelemetry Instrumentation for Java.
This instrumentation:

1. Leverages OpenTelemetry's InstrumentationModule extension mechanism
2. Ensures AWS-specific instrumentation runs after core OpenTelemetry instrumentation
3. Maintains all existing functionality while improving ADOT maintainability

Benefits:

- **Better Extensibility:** Users can easily extend or modify AWS-specific behavior
- **Enhanced Compatibility:** Better alignment with OpenTelemetry's extension mechanisms
- **Clearer Code Organization:** More intuitive structure for future contributions

### AWS SDK v2 Instrumentation Summary
The AdotAwsSdkInstrumentationModule extends the InstrumentationModule from OpenTelemetry's SPI extensions and registers
the AdotTracingExecutionInterceptor to provide custom instrumentation which runs after the upstream OTel agent runs.
- **registerHelperResources** (within AdotAwsSdkInstrumentationModule) registers our resource (AdotTracingExecutionInterceptor) to inject into the user's class loader. The order the names are registered impactes the oder of instrumentation. It is key to first register the upstream aws-sdk execution interceptor, and then this interceptor.

The AdotTracingExecutionInterceptor extends the AWS SDK ExecutionInterceptor. It uses the **beforeTransmission** and **modifyResponse** methods
to hook onto the OTel spans during specific phases of the SDK request and response life cycle. This allows it to enrich the
upstream spans with custom **AwsExperimentalAttributes**. These points in the life cycle are crucial as they impact the order in which attributes
are added to the OTel span.

- **beforeTransmission** is the latest point where the sdk request can be obtained after it is modified by the upstream aws-sdk v2.2 interceptor. This ensures the upstream handles the request and applies its changes first.
- **modifyResponse** is the latest point to access the sdk response before the span closes in the upstream afterExecution method. This ensures we capture attributes from the final, fully modified response after all upstream interceptors have processed it.

The AdotTracingExecutionInterceptor is the main point which connects the other classes within awssdk_v2_2
through the fieldMapper variable.

_**Important Note:**_
Since the upstream interceptor closes the span in afterExecution, that method and any subsequent points are inaccessible for span modification.
We use modifyResponse as our final interception point, as it provides access to both the complete SDK response (after upstream applies it's modifications first) and the still-active span.
This ensures our attributes are added before span completion while still capturing the final state of the response after OTel.

_Initialization Workflow_

1. OpenTelemetry Agent starts
- Loads default instrumentations
- Loads aws-sdk instrumentation from opentelemetry-java-instrumentation
- Registers **TracingExecutionInterceptor** (order = 0)
2. Scans for other SPI implementations
- Finds ADOT’s **AdotAwsSdkInstrumentationModule**
- Registers **AdotTracingExecutionInterceptor** (order > 0)

<ins>Note:</ins> The files that enhance OTel functionality are:
* AwsExperimentalAttributes
* AwsSdkRequest
* AwsSdkRequestType
* BedrockJsonParser
* FieldMapper
* Serializer
* AdotTracingExecutionInterceptor
* BedrockJsonParserTest
* AbstractAws2ClientTest

54 changes: 54 additions & 0 deletions instrumentation/aws-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

plugins {
java
id("groovy")
id("com.gradleup.shadow")
}

base.archivesBaseName = "aws-instrumentation-awssdk"

dependencies {

compileOnly("software.amazon.awssdk:json-utils:2.17.0")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, lines 26 and 27 are needed for AwsJsonProtocolFactoryAccess and BedrockJsonParser


// OpenTelemetry dependencies
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")

// AWS SDK dependencies for version 2.2.0
compileOnly("software.amazon.awssdk:aws-core:2.2.0")
compileOnly("software.amazon.awssdk:lambda:2.2.0")
compileOnly("software.amazon.awssdk:aws-json-protocol:2.2.0")
compileOnly("software.amazon.awssdk:sfn:2.2.0")
compileOnly("software.amazon.awssdk:secretsmanager:2.2.0")

// Test dependencies
testImplementation(project(":instrumentation:aws-sdk:testing"))
testImplementation("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")

// AWS SDK test dependencies for version 2.2.0
testImplementation("software.amazon.awssdk:dynamodb:2.2.0")
testImplementation("software.amazon.awssdk:ec2:2.2.0")
testImplementation("software.amazon.awssdk:kinesis:2.2.0")
testImplementation("software.amazon.awssdk:rds:2.2.0")
testImplementation("software.amazon.awssdk:s3:2.2.0")
testImplementation("software.amazon.awssdk:ses:2.2.0")
testImplementation("software.amazon.awssdk:sfn:2.2.0")
testImplementation("software.amazon.awssdk:secretsmanager:2.2.0")
testImplementation("software.amazon.awssdk:lambda:2.2.0")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review the dependencies to remove the non-effective ones. Thanks.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class AdotAwsSdkInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {

public AdotAwsSdkInstrumentationModule() {
super("aws-sdk-adot", "aws-sdk-2.2-adot");
}

@Override
public int order() {
// Ensure this runs after OTel (> 0)
return 99;
}

@Override
public List<String> getAdditionalHelperClassNames() {
return Arrays.asList(
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotTracingExecutionInterceptor",
// other helper classes as needed
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotTracingExecutionInterceptor$RequestSpanFinisher",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsSdkRequest",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsSdkRequestType",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.FieldMapper",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.FieldMapping",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.FieldMapping$Type",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsExperimentalAttributes",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.BedrockJsonParser",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.BedrockJsonParser$JsonPathResolver",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.BedrockJsonParser$LlmJson",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.BedrockJsonParser$JsonParser",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.MethodHandleFactory",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.MethodHandleFactory$1",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.Serializer",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsJsonProtocolFactoryAccess",
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsSdkRequestType$AttributeKeys");
}

// This registers the upstream execution interceptor first, and then the
// AdotTracingExecutionInterceptor. This ensures the upstream interceptor runs before ADOT's
// interceptor and maintains order.
@Override
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
helperResourceBuilder.register(
"software/amazon/awssdk/global/handlers/execution.interceptors",
"software/amazon/awssdk/global/handlers/execution.interceptors.adot");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor");
}

@Override
public void injectClasses(final ClassInjector injector) {
injector
.proxyBuilder(
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotTracingExecutionInterceptor")
.inject(InjectionMode.CLASS_ONLY);
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new ResourceInjectingTypeInstrumentation());
}

public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("software.amazon.awssdk.core.SdkClient");
}

@Override
public void transform(TypeTransformer transformer) {
// Empty as we use ExecutionInterceptor
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2;

import static software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsExperimentalAttributes.GEN_AI_SYSTEM;
import static software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AwsSdkRequestType.BEDROCKRUNTIME;

import io.opentelemetry.api.trace.Span;
import software.amazon.awssdk.core.SdkRequest;
import software.amazon.awssdk.core.SdkResponse;
import software.amazon.awssdk.core.interceptor.*;

public class AdotTracingExecutionInterceptor implements ExecutionInterceptor {

private static final String GEN_AI_SYSTEM_BEDROCK = "aws.bedrock";

private static final ExecutionAttribute<AwsSdkRequest> AWS_SDK_REQUEST_ATTRIBUTE =
new ExecutionAttribute<>(AdotTracingExecutionInterceptor.class.getName() + ".AwsSdkRequest");

private final FieldMapper fieldMapper = new FieldMapper();

// This is the latest point we can obtain the Sdk Request after it is modified by the upstream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this may not be the right understanding and we can double check with Ping.

My understanding: The upstream instrumentation precedes our patching, as defined by the sequence in AdotAwsSdkInstrumentationModule. Within the AWS SDK request lifecycle, our patching intercepts the beforeTransmission and afterUnmarshalling events to inject AWS attributes according to the established instrumentation order.

// TracingInterceptor. It ensures upstream handles the request and applies its changes first.
@Override
public void beforeTransmission(
Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {

SdkRequest request = context.request();
Span currentSpan = Span.current();

try {
// Skip injection if Otel span is invalid
if (currentSpan != null && currentSpan.getSpanContext().isValid()) {
AwsSdkRequest awsSdkRequest = AwsSdkRequest.ofSdkRequest(request);
if (awsSdkRequest != null) {
executionAttributes.putAttribute(AWS_SDK_REQUEST_ATTRIBUTE, awsSdkRequest);
fieldMapper.mapToAttributes(request, awsSdkRequest, currentSpan);
if (awsSdkRequest.type() == BEDROCKRUNTIME) {
currentSpan.setAttribute(GEN_AI_SYSTEM, GEN_AI_SYSTEM_BEDROCK);
}
}
}
} catch (Throwable throwable) {
// ignore
}
}

// This is the latest point we can obtain the Sdk Response before span completion in upstream's
// afterExecution. This ensures we capture attributes from the final, fully modified response
// after all upstream interceptors have processed it.
@Override
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this action to afterExecution as done by upstream so that our changes/updates come after upstream? So far, we don't modify attributes set by OTel, so the order doesn't matter too much. But if we did, based on this implementation, the modified attributes in afterUnmarshalling will be overwritten by OTel in afterExecution step.

Update:
In Otel interceptor, it clearsAWS_SDK_REQUEST_ATTRIBUTE in executionAttributes duringafterExecution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had tried to instrument afterExecution initially but the upstream clears all the execution attributes. I think using afterUnmarshalling ensures that the interceptor that put in the execution attributes last (i.e. AdotTracingExecutionInterceptor) should be the first to handle the execution attributes. This also ensures the otel span is available to modify at this point of interception.

public SdkResponse modifyResponse(
Context.ModifyResponse context, ExecutionAttributes executionAttributes) {
Span currentSpan = Span.current();
AwsSdkRequest sdkRequest = executionAttributes.getAttribute(AWS_SDK_REQUEST_ATTRIBUTE);

if (sdkRequest != null) {
fieldMapper.mapToAttributes(context.response(), sdkRequest, currentSpan);
executionAttributes.putAttribute(AWS_SDK_REQUEST_ATTRIBUTE, null);
}
return context.response();
}
}
Loading