Skip to content

Base of AWS SDK v1.11 SPI Implementation #1115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
26 changes: 25 additions & 1 deletion instrumentation/aws-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
### Overview
The aws-sdk instrumentation is an SPI-based implementation that extends the upstream OpenTelemetry AWS Java SDK instrumentation.

_Initialization Workflow_
_Example: v2.2 Initialization Workflow_

1. OpenTelemetry Agent starts
- Loads default instrumentations
Expand All @@ -13,6 +13,30 @@ _Initialization Workflow_
- Finds ADOT’s **AdotAwsSdkInstrumentationModule**
- Registers **AdotAwsSdkTracingExecutionInterceptor** (order > 0)

### AWS SDK v1 Instrumentation Summary
The AdotAwsSdkInstrumentationModule uses the instrumentation (specified in AdotAwsClientInstrumentation) to register the AdotAwsSdkTracingRequestHandler through `typeInstrumentations`.

Key aspects of handler registration:
- `order` method ensures ADOT instrumentation runs after OpenTelemetry's base instrumentation. It is set to 99 as precaution, in case upstream aws-sdk registers more handlers.
- `AdotAwsSdkClientInstrumentation` class
- `AdotAwsSdkClientAdvice` registers our handler only if the upstream aws-sdk span is enabled (i.e. it checks if the upstream handler is present when an AWS SDK client is
initialized).
- Ensures the OpenTelemetry handler is registered first.

**AdotAwsSdkTracingRequestHandler**

The AdotAwsSdkTracingRequestHandler 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.

1. `beforeRequest`: the latest point where the SDK request can be obtained after it is modified by the upstream aws-sdk v1.11 handler
2. `afterAttempt`: the latest point to access the SDK response before the span closes in the upstream afterResponse/afterError methods

All the span lifecycle hooks provided by AWS SDK RequestHandler2 can be found [here.](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/handlers/RequestHandler2.html#beforeMarshalling-com.amazonaws.AmazonWebServiceRequest)

_**Important Notes:**_
- The upstream interceptor's last point of request modification occurs in [beforeRequest](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L58).
- The upstream interceptor closes the span in [afterResponse](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L116) and/or [afterError](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L131). These hooks are inaccessible for span modification.
`afterAttempt` is our final hook point, giving us access to both the fully processed response and active span.

### AWS SDK v2 Instrumentation Summary

**AdotAwsSdkInstrumentationModule**
Expand Down
1 change: 1 addition & 0 deletions instrumentation/aws-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ base.archivesBaseName = "aws-instrumentation-aws-sdk"

dependencies {
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
compileOnly("com.amazonaws:aws-java-sdk-core:1.11.0")
compileOnly("software.amazon.awssdk:aws-core:2.2.0")
compileOnly("net.bytebuddy:byte-buddy")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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_v1_11;

import static net.bytebuddy.matcher.ElementMatchers.*;

import com.amazonaws.handlers.RequestHandler2;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.List;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class AdotAwsSdkClientInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
// AmazonWebServiceClient is the base interface for all AWS SDK clients.
// Type matching against it ensures our interceptor is injected as soon as any AWS SDK client is
// initialized.
return named("com.amazonaws.AmazonWebServiceClient")
.and(declaresField(named("requestHandler2s")));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isConstructor(),
AdotAwsSdkClientInstrumentation.class.getName() + "$AdotAwsSdkClientAdvice");
}

@SuppressWarnings("unused")
public static class AdotAwsSdkClientAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void addHandler(
@Advice.FieldValue(value = "requestHandler2s", readOnly = false)
List<RequestHandler2> handlers) {

if (handlers == null) {
return;
}

boolean hasOtelHandler = false;
boolean hasAdotHandler = false;

// Checks if aws-sdk spans are enabled
for (RequestHandler2 handler : handlers) {
if (handler
.toString()
.contains(
"io.opentelemetry.javaagent.instrumentation.awssdk.v1_11.TracingRequestHandler")) {
hasOtelHandler = true;
}
if (handler instanceof AdotAwsSdkTracingRequestHandler) {
hasAdotHandler = true;
break;
}
}

// Only adds our handler if aws-sdk spans are enabled. This also ensures upstream
// instrumentation is applied first.
if (hasOtelHandler && !hasAdotHandler) {
handlers.add(new AdotAwsSdkTracingRequestHandler());
}
}
}
}
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.
*/

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

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;

import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

public class AdotAwsSdkInstrumentationModule extends InstrumentationModule {

public AdotAwsSdkInstrumentationModule() {
super("aws-sdk-adot", "aws-sdk-1.11-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_v1_11.AdotAwsSdkTracingRequestHandler");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("com.amazonaws.AmazonWebServiceClient");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new AdotAwsSdkClientInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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_v1_11;

import com.amazonaws.Request;
import com.amazonaws.handlers.HandlerAfterAttemptContext;
import com.amazonaws.handlers.RequestHandler2;

public class AdotAwsSdkTracingRequestHandler extends RequestHandler2 {

public AdotAwsSdkTracingRequestHandler() {}

/**
* This is the latest point we can obtain the Sdk Request after it is modified by the upstream
* TracingInterceptor. It ensures upstream handles the request and applies its changes first.
*
* <p>Upstream's last Sdk Request modification: @see <a
* href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L58">reference</a>
*/
@Override
public void beforeRequest(Request<?> request) {}

/**
* This is the latest point to access the sdk response before the span closes in the upstream
* afterResponse/afterError methods. This ensures we capture attributes from the final, fully
* modified response after all upstream interceptors have processed it.
*
* <p>Upstream's last Sdk Response modification before span closure: @see <a
* href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L116">reference</a>
*
* @see <a
* href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java#L131">reference</a>
*/
@Override
public void afterAttempt(HandlerAfterAttemptContext context) {}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotAwsSdkInstrumentationModule
software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.AdotAwsSdkInstrumentationModule
software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AdotAwsSdkInstrumentationModule