Skip to content

Commit 572215e

Browse files
authored
Base of AWS SDK v2.2 SPI Implementation (#1111)
Note: this is not the complete SPI implementation ### Issue The current ADOT Java SDK implementation relies on a combination of OpenTelemetry SPI and Git patches to extend the OTel SDK functionality. This approach presents several challenges: - Reduced modularity and maintainability - Increased risk of errors during OTel SDK version upgrades - Manual intervention required for patch management - Limited ecosystem compatibility with upstream OpenTelemetry - Difficulty in extending functionality for users This is the skeleton set up for the SPI, which aims to remove the [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch) for aws-sdk v2.2 by using OTel's InstrumentationModule SPI extension. This instrumentation is essentially complementing the upstream java agent. It is completely separate from upstream and instruments after the Otel agent. ### Description of Changes This PR sets up the foundational structure for AWS SDK v2.2 Instrumentation, moving away from the current patching approach. It doesn't modify current ADOT functionality or the upstream span. It just registers the ADOT SPI implementation and sets up the interceptor hooks. #### Core Components 1. **AdotAwsSdkInstrumentationModule** - Extends OpenTelemetry's `InstrumentationModule` SPI - Registers custom interceptors in specific order: 1. Upstream AWS SDK execution interceptor 2. ADOT custom interceptor - Ensures proper instrumentation sequencing through careful resource registration 2. **AdotTracingExecutionInterceptor** - Extends AWS SDK's `ExecutionInterceptor` - Hooks into key SDK lifecycle points: - `beforeTransmission`: Captures final SDK request after upstream modifications - `modifyResponse`: Processes response before span closure in upstream - Will be used to enriches spans - Acts as central coordinator for all the awssdk_v2_2 components 3. **Resources Folder** - Registers the AdotAwsSdkInstrumentationModule into OTel's SPI extension classpath in META-INF/services - Registers AdotTracingExecutionInterceptor into AWS SDK's interceptor classpath in software.amazon.awssdk.global.handlers ### Key Design Decisions 1. **Instrumentation Ordering** - Deliberately structured to run after upstream OTel agent - Ensures all upstream modifications are captured - Maintains compatibility with existing instrumentation 2. **Lifecycle Hook Points** - `beforeTransmission`: Last point to access modified request - `modifyResponse`: Final opportunity to enrich span before closure - Carefully chosen to ensure complete attribute capture ### Testing - Verified existing functionality remains unchanged and contract tests pass (all contract tests pass after following the steps [here](https://github.com/aws-observability/aws-otel-java-instrumentation/tree/main/appsignals-tests)) - Confirmed build success with new structure ### Benefits of using SPI - Improved Maintainability: Clear separation between OTel core and AWS-specific instrumentation - Better Extensibility: Users can more easily extend or modify AWS-specific behavior - Reduced Risk: Eliminates manual patching during OTel upgrades - Enhanced Compatibility: Better alignment with OpenTelemetry's extension mechanisms - Clearer Code Organization: More intuitive structure for future contributions By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 2f48b77 commit 572215e

File tree

8 files changed

+195
-0
lines changed

8 files changed

+195
-0
lines changed

instrumentation/aws-sdk/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## ADOT AWS SDK Instrumentation
2+
3+
### Overview
4+
The aws-sdk instrumentation is an SPI-based implementation that extends the upstream OpenTelemetry AWS Java SDK instrumentation.
5+
6+
_Initialization Workflow_
7+
8+
1. OpenTelemetry Agent starts
9+
- Loads default instrumentations
10+
- Loads aws-sdk instrumentation from opentelemetry-java-instrumentation
11+
- Registers **TracingExecutionInterceptor** (order = 0)
12+
2. Scans for other SPI implementations
13+
- Finds ADOT’s **AdotAwsSdkInstrumentationModule**
14+
- Registers **AdotAwsSdkTracingExecutionInterceptor** (order > 0)
15+
16+
### AWS SDK v2 Instrumentation Summary
17+
18+
**AdotAwsSdkInstrumentationModule**
19+
20+
The AdotAwsSdkInstrumentationModule registers the AdotAwsSdkTracingExecutionInterceptor in `registerHelperResources`.
21+
22+
Key aspects of interceptor registration:
23+
- AWS SDK's ExecutionInterceptor loads global interceptors from files named '/software/amazon/awssdk/global/handlers/execution.interceptors' in the classpath
24+
- Interceptors are executed in the order they appear in the classpath - earlier entries run first
25+
- `order` method ensures ADOT instrumentation runs after OpenTelemetry's base instrumentation, maintaining proper sequence of interceptor registration in AWS SDK classpath
26+
27+
**AdotAwsSdkTracingExecutionInterceptor**
28+
29+
The AdotAwsSdkTracingExecutionInterceptor hooks onto OpenTelemetry's spans during specific phases of the SDK request and response life cycle. These hooks are strategically chosen to ensure proper ordering of attribute injection.
30+
31+
1. `beforeTransmission`: the latest point where the SDK request can be obtained after it is modified by the upstream's interceptor
32+
2. `modifyResponse`: the latest point to access the SDK response before the span closes in the upstream afterExecution method
33+
34+
_**Important Note:**_
35+
The upstream interceptor closes the span in `afterExecution`. That hook is inaccessible for span modification.
36+
`modifyResponse` is our final hook point, giving us access to both the fully processed response and active span.
37+
38+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
plugins {
17+
java
18+
id("com.gradleup.shadow")
19+
}
20+
21+
base.archivesBaseName = "aws-instrumentation-aws-sdk"
22+
23+
dependencies {
24+
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
25+
compileOnly("software.amazon.awssdk:aws-core:2.2.0")
26+
compileOnly("net.bytebuddy:byte-buddy")
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2;
17+
18+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
19+
import static net.bytebuddy.matcher.ElementMatchers.named;
20+
21+
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
22+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
23+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
24+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
25+
import java.util.Arrays;
26+
import java.util.Collections;
27+
import java.util.List;
28+
import net.bytebuddy.description.type.TypeDescription;
29+
import net.bytebuddy.matcher.ElementMatcher;
30+
31+
public class AdotAwsSdkInstrumentationModule extends InstrumentationModule {
32+
33+
public AdotAwsSdkInstrumentationModule() {
34+
super("aws-sdk-adot", "aws-sdk-2.2-adot");
35+
}
36+
37+
@Override
38+
public int order() {
39+
// Ensure this runs after OTel (> 0)
40+
return 99;
41+
}
42+
43+
@Override
44+
public List<String> getAdditionalHelperClassNames() {
45+
return Arrays.asList(
46+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotAwsSdkTracingExecutionInterceptor");
47+
}
48+
49+
/**
50+
* Registers resource file containing reference to our {@link
51+
* AdotAwsSdkTracingExecutionInterceptor} with SDK's service loading mechanism. The 'order' method
52+
* ensures this interceptor is registered after upstream. Interceptors are executed in the order
53+
* they appear in the classpath.
54+
*
55+
* @see <a
56+
* href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/release/v2.11.x/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java#L27">reference</a>
57+
*/
58+
@Override
59+
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
60+
helperResourceBuilder.register(
61+
"software/amazon/awssdk/global/handlers/execution.interceptors",
62+
"software/amazon/awssdk/global/handlers/execution.interceptors.adot");
63+
}
64+
65+
@Override
66+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
67+
return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor");
68+
}
69+
70+
@Override
71+
public List<TypeInstrumentation> typeInstrumentations() {
72+
return Collections.singletonList(new ResourceInjectingTypeInstrumentation());
73+
}
74+
75+
public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation {
76+
@Override
77+
public ElementMatcher<TypeDescription> typeMatcher() {
78+
// SdkClient is the base interface for all AWS SDK clients. Type matching against it ensures
79+
// our interceptor is injected as soon as any AWS SDK client is initialized.
80+
return named("software.amazon.awssdk.core.SdkClient");
81+
}
82+
83+
@Override
84+
public void transform(TypeTransformer transformer) {
85+
// Empty as we use ExecutionInterceptor
86+
}
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2;
17+
18+
import software.amazon.awssdk.core.SdkResponse;
19+
import software.amazon.awssdk.core.interceptor.*;
20+
21+
public class AdotAwsSdkTracingExecutionInterceptor implements ExecutionInterceptor {
22+
23+
// This is the latest point we can obtain the Sdk Request after it is modified by the upstream
24+
// TracingInterceptor. It ensures upstream handles the request and applies its changes first.
25+
@Override
26+
public void beforeTransmission(
27+
Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {}
28+
29+
// This is the latest point we can obtain the Sdk Response before span completion in upstream's
30+
// afterExecution. This ensures we capture attributes from the final, fully modified response
31+
// after all upstream interceptors have processed it.
32+
@Override
33+
public SdkResponse modifyResponse(
34+
Context.ModifyResponse context, ExecutionAttributes executionAttributes) {
35+
36+
return context.response();
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotAwsSdkInstrumentationModule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotAwsSdkTracingExecutionInterceptor

otelagent/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ dependencies {
6060

6161
javaagentLibs(project(":awsagentprovider"))
6262
javaagentLibs(project(":instrumentation:log4j-2.13.2"))
63+
javaagentLibs(project(":instrumentation:aws-sdk"))
6364
javaagentLibs(project(":instrumentation:logback-1.0"))
6465
javaagentLibs(project(":instrumentation:jmx-metrics"))
6566
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ include(":dependencyManagement")
4444
include(":instrumentation:logback-1.0")
4545
include(":instrumentation:log4j-2.13.2")
4646
include(":instrumentation:jmx-metrics")
47+
include("instrumentation:aws-sdk")
4748
include(":otelagent")
4849
include(":smoke-tests:fakebackend")
4950
include(":smoke-tests:runner")

0 commit comments

Comments
 (0)