Skip to content

Commit 8a3b772

Browse files
anahatAWSthpierce
andauthored
AWS SDK v1.11 Patch Migration (#1117)
Note: This is a continuation of #1115 ### Description of Changes This implementation builds on the foundation established in PR #1115, transforming the structural setup into a fully functional SPI-based solution that will replace our current patching approach. This PR does not change the current ADOT functionality because patches have not been removed. The next/final PR for v1.11 will remove the patches for aws-sdk-1.11 and have unit tests to ensure correct SPI functionality and behaviour. The final PR will also pass all the contract-tests once patches are removed. #### Changes include: - Migration of patched files into proper package structure: NOTE: We are not copying entire files from upstream. Instead, we only migrated the new components that were added by our patches and the methods that use these AWS-specific components. I deliberately removed any code that was untouched by our patches to avoid duplicating upstream instrumentation code. This selective migration ensures we maintain only our AWS-specific additions while letting OTel handle its base functionality. - `AwsBedrockResourceType` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L224) creates new class - `AwsExperimentalAttributes` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L362) on [this otel file](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/release/v2.11.x/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java) - `AwsSdkExperimentalAttributesExtractor` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L408) on [this otel file](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/release/v2.11.x/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java) - `BedrockJsonParser` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L655) creates new class - `RequestAccess` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L927) on [this otel file](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/release/v2.11.x/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java) - `BedrockJsonParserTest` - [patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch#L1461) creates new class These added files: - Access and modify span attributes - Provide consistent formatting tools for span attributes - Can be updated at our convenience if needed ### Testing - Existing functionality verified - Contract tests passing - Build successful ### Related - Skeleton PR for aws-sdk v1.11: #1115 - Replaces patch: [current patch](https://github.com/aws-observability/aws-otel-java-instrumentation/blob/main/.github/patches/opentelemetry-java-instrumentation.patch) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Thomas Pierce <thp@amazon.com>
1 parent 9a76dda commit 8a3b772

File tree

11 files changed

+1489
-6
lines changed

11 files changed

+1489
-6
lines changed

instrumentation/aws-sdk/README.md

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ The aws-sdk instrumentation is an SPI-based implementation that extends the upst
2727
- Finds ADOT’s **AdotAwsSdkInstrumentationModule**
2828
- Registers **AdotAwsSdkTracingExecutionInterceptor** (order > 0)
2929

30+
#### _Note on Attribute Collection:_
31+
AWS SDK v1.11 and v2.2 handle attribute collection differently:
32+
33+
**V1.11:**
34+
- Maintains a separate AttributesBuilder during request/response lifecycle
35+
- Collects ADOT-specific attributes alongside upstream processing without interference
36+
- Injects collected attributes into span at the end of the request and response lifecycle hooks
37+
38+
39+
**V2.2:**
40+
- FieldMapper directly modifies spans during request/response processing
41+
- Attributes are added to spans immediately when discovered
42+
- Direct integration with span lifecycle
43+
44+
This architectural difference exists due to upstream AWS SDK injecting attributes into spans differently for v1.11 and v2.2
45+
3046
### AWS SDK v1 Instrumentation Summary
3147
The AdotAwsSdkInstrumentationModule uses the instrumentation (specified in AdotAwsClientInstrumentation) to register the AdotAwsSdkTracingRequestHandler through `typeInstrumentations`.
3248

@@ -61,6 +77,28 @@ _**Important Notes:**_
6177
- 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.
6278
`afterAttempt` is our final hook point, giving us access to both the fully processed response and active span.
6379

80+
**High-Level Sequence Diagram:**
81+
![img.png](sequence-diagram-1.11.png)
82+
83+
_Class Functionalities:_
84+
- `AdotAwsSdkTracingRequestHandler`
85+
- Hooks into AWS SDK request/response lifecycle
86+
- Adds ADOT-specific attributes to spans extracted by AwsSdkExperimentalAttributesExtractor
87+
- `AwsSdkExperimentalAttributesExtractor`
88+
- Extracts attributes from AWS requests/responses and enriches spans
89+
- Uses RequestAccess to get field values
90+
- Special handling for Bedrock services
91+
- `RequestAccess`
92+
- Provides access to AWS SDK object fields
93+
- Caches method handles for performance
94+
- Uses BedrockJsonParser for parsing LLM payloads
95+
- `BedrockJsonParser`
96+
- Custom JSON parser for Bedrock payloads
97+
- Handles different LLM model formats
98+
- `AwsBedrockResourceType`
99+
- Maps Bedrock class names to resource types
100+
- Provides attribute keys and accessors for each type
101+
64102
### AWS SDK v2 Instrumentation Summary
65103

66104
**AdotAwsSdkInstrumentationModule**
@@ -87,7 +125,6 @@ _**Important Notes:**_
87125
`modifyResponse` is our final hook point, giving us access to both the fully processed response and active span.
88126

89127
**High-Level Sequence Diagram:**
90-
91128
![img.png](sequence-diagram-2.2.png)
92129

93130
_Class Functionalities:_
@@ -111,4 +148,16 @@ _Class Functionalities:_
111148
- Uses reflection to access internal SDK classes
112149
- Caches method handles for performance
113150
- `BedrockJasonParser`
114-
- Parses and extracts specific attributes from Bedrock LLM responses for GenAI telemetry
151+
- Parses and extracts specific attributes from Bedrock LLM responses for GenAI telemetry
152+
153+
### Commands for Running Groovy Tests
154+
155+
To run the BedrockJsonParserTest for aws-sdk v1.11:
156+
````
157+
./gradlew :instrumentation:aws-sdk:test --tests "software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.BedrockJsonParserTest"
158+
````
159+
160+
To run the BedrockJsonParserTest for aws-sdk v2.2:
161+
````
162+
./gradlew :instrumentation:aws-sdk:test --tests "software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v2_2.BedrockJsonParserTest"
163+
````

instrumentation/aws-sdk/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ plugins {
2222
base.archivesBaseName = "aws-instrumentation-aws-sdk"
2323

2424
dependencies {
25+
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
2526
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
2627
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
2728
compileOnly("com.amazonaws:aws-java-sdk-core:1.11.0")
@@ -37,4 +38,6 @@ dependencies {
3738
testImplementation("com.amazonaws:aws-java-sdk-core:1.11.0")
3839
testImplementation("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
3940
testImplementation("org.mockito:mockito-core:5.14.2")
41+
testImplementation("com.google.guava:guava")
42+
testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common")
4043
}
Loading

instrumentation/aws-sdk/src/main/java/software/amazon/opentelemetry/javaagent/instrumentation/awssdk_v1_11/AdotAwsSdkInstrumentationModule.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11;
1717

1818
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
19+
import static net.bytebuddy.matcher.ElementMatchers.*;
1920

2021
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
2122
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
@@ -46,7 +47,17 @@ public int order() {
4647
@Override
4748
public List<String> getAdditionalHelperClassNames() {
4849
return Arrays.asList(
49-
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AdotAwsSdkTracingRequestHandler");
50+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AdotAwsSdkTracingRequestHandler",
51+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsSdkExperimentalAttributesExtractor",
52+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsExperimentalAttributes",
53+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsBedrockResourceType",
54+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsBedrockResourceType$AwsBedrockResourceTypeMap",
55+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.BedrockJsonParser",
56+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.BedrockJsonParser$JsonParser",
57+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.BedrockJsonParser$LlmJson",
58+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.BedrockJsonParser$JsonPathResolver",
59+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.RequestAccess",
60+
"software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.RequestAccess$1");
5061
}
5162

5263
@Override

instrumentation/aws-sdk/src/main/java/software/amazon/opentelemetry/javaagent/instrumentation/awssdk_v1_11/AdotAwsSdkTracingRequestHandler.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,51 @@
1515

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

18+
import static io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil.getBoolean;
19+
1820
import com.amazonaws.Request;
21+
import com.amazonaws.Response;
1922
import com.amazonaws.handlers.HandlerAfterAttemptContext;
2023
import com.amazonaws.handlers.RequestHandler2;
24+
import io.opentelemetry.api.common.Attributes;
25+
import io.opentelemetry.api.common.AttributesBuilder;
26+
import io.opentelemetry.api.trace.Span;
27+
import io.opentelemetry.context.Context;
2128

2229
/**
30+
* This handler extends the AWS SDK v1.11 request handling chain to add ADOT-specific span
31+
* attributes. It operates at two key points in the request lifecycle:
32+
*
33+
* <p>1. Request Phase (beforeRequest):
34+
*
35+
* <ul>
36+
* <li>Intercepts the request after upstream modifications
37+
* <li>Extracts experimental attributes from the request in a separate AttributesBuilder
38+
* <li>Adds these attributes to the current span
39+
* </ul>
40+
*
41+
* <p>2. Response/Error Phase (afterAttempt):
42+
*
43+
* <ul>
44+
* <li>Captures final state after all upstream handlers
45+
* <li>Extracts attributes from both request and response/error in a separate AttributesBuilder
46+
* <li>Adds these attributes to the current span before it closes
47+
* </ul>
48+
*
2349
* Based on OpenTelemetry Java Instrumentation's AWS SDK v1.11 TracingRequestHandler
2450
* (release/v2.11.x). Adapts the base instrumentation pattern to add ADOT-specific functionality.
2551
*
2652
* <p>Source: <a
2753
* href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/release/v2.11.x/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java">...</a>
2854
*/
2955
public class AdotAwsSdkTracingRequestHandler extends RequestHandler2 {
56+
private final AwsSdkExperimentalAttributesExtractor experimentalAttributesExtractor;
57+
private final boolean captureExperimentalSpanAttributes =
58+
getBoolean("otel.instrumentation.aws-sdk.experimental-span-attributes", true);
3059

31-
public AdotAwsSdkTracingRequestHandler() {}
60+
public AdotAwsSdkTracingRequestHandler() {
61+
this.experimentalAttributesExtractor = new AwsSdkExperimentalAttributesExtractor();
62+
}
3263

3364
/**
3465
* This is the latest point we can obtain the Sdk Request after it is modified by the upstream
@@ -38,7 +69,20 @@ public AdotAwsSdkTracingRequestHandler() {}
3869
* 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>
3970
*/
4071
@Override
41-
public void beforeRequest(Request<?> request) {}
72+
public void beforeRequest(Request<?> request) {
73+
Span currentSpan = Span.current();
74+
75+
if (captureExperimentalSpanAttributes
76+
&& currentSpan != null
77+
&& currentSpan.getSpanContext().isValid()) {
78+
AttributesBuilder attributes = Attributes.builder();
79+
experimentalAttributesExtractor.onStart(attributes, Context.current(), request);
80+
81+
attributes
82+
.build()
83+
.forEach((key, value) -> currentSpan.setAttribute(key.getKey(), value.toString()));
84+
}
85+
}
4286

4387
/**
4488
* This is the latest point to access the sdk response before the span closes in the upstream
@@ -52,5 +96,23 @@ public void beforeRequest(Request<?> request) {}
5296
* 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>
5397
*/
5498
@Override
55-
public void afterAttempt(HandlerAfterAttemptContext context) {}
99+
public void afterAttempt(HandlerAfterAttemptContext context) {
100+
Span currentSpan = Span.current();
101+
102+
if (captureExperimentalSpanAttributes
103+
&& currentSpan != null
104+
&& currentSpan.getSpanContext().isValid()) {
105+
Request<?> request = context.getRequest();
106+
Response<?> response = context.getResponse();
107+
Exception exception = context.getException();
108+
109+
AttributesBuilder attributes = Attributes.builder();
110+
experimentalAttributesExtractor.onEnd(
111+
attributes, Context.current(), request, response, exception);
112+
113+
attributes
114+
.build()
115+
.forEach((key, value) -> currentSpan.setAttribute(key.getKey(), value.toString()));
116+
}
117+
}
56118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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_v1_11;
17+
18+
import static software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsExperimentalAttributes.AWS_AGENT_ID;
19+
import static software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsExperimentalAttributes.AWS_DATA_SOURCE_ID;
20+
import static software.amazon.opentelemetry.javaagent.instrumentation.awssdk_v1_11.AwsExperimentalAttributes.AWS_KNOWLEDGE_BASE_ID;
21+
22+
import io.opentelemetry.api.common.AttributeKey;
23+
import java.util.Arrays;
24+
import java.util.HashMap;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.function.Function;
28+
29+
enum AwsBedrockResourceType {
30+
AGENT_TYPE(AWS_AGENT_ID, RequestAccess::getAgentId),
31+
DATA_SOURCE_TYPE(AWS_DATA_SOURCE_ID, RequestAccess::getDataSourceId),
32+
KNOWLEDGE_BASE_TYPE(AWS_KNOWLEDGE_BASE_ID, RequestAccess::getKnowledgeBaseId);
33+
34+
@SuppressWarnings("ImmutableEnumChecker")
35+
private final AttributeKey<String> keyAttribute;
36+
37+
@SuppressWarnings("ImmutableEnumChecker")
38+
private final Function<Object, String> attributeValueAccessor;
39+
40+
AwsBedrockResourceType(
41+
AttributeKey<String> keyAttribute, Function<Object, String> attributeValueAccessor) {
42+
this.keyAttribute = keyAttribute;
43+
this.attributeValueAccessor = attributeValueAccessor;
44+
}
45+
46+
public AttributeKey<String> getKeyAttribute() {
47+
return keyAttribute;
48+
}
49+
50+
public Function<Object, String> getAttributeValueAccessor() {
51+
return attributeValueAccessor;
52+
}
53+
54+
public static AwsBedrockResourceType getRequestType(String requestClass) {
55+
return AwsBedrockResourceTypeMap.BEDROCK_REQUEST_MAP.get(requestClass);
56+
}
57+
58+
public static AwsBedrockResourceType getResponseType(String responseClass) {
59+
return AwsBedrockResourceTypeMap.BEDROCK_RESPONSE_MAP.get(responseClass);
60+
}
61+
62+
private static class AwsBedrockResourceTypeMap {
63+
private static final Map<String, AwsBedrockResourceType> BEDROCK_REQUEST_MAP = new HashMap<>();
64+
private static final Map<String, AwsBedrockResourceType> BEDROCK_RESPONSE_MAP = new HashMap<>();
65+
66+
// Bedrock request/response mapping
67+
// We only support operations that are related to the resource and where the context contains
68+
// the AgentID/DataSourceID/KnowledgeBaseID.
69+
// AgentID
70+
private static final List<String> agentRequestClasses =
71+
Arrays.asList(
72+
"CreateAgentActionGroupRequest",
73+
"CreateAgentAliasRequest",
74+
"DeleteAgentActionGroupRequest",
75+
"DeleteAgentAliasRequest",
76+
"DeleteAgentRequest",
77+
"DeleteAgentVersionRequest",
78+
"GetAgentActionGroupRequest",
79+
"GetAgentAliasRequest",
80+
"GetAgentRequest",
81+
"GetAgentVersionRequest",
82+
"ListAgentActionGroupsRequest",
83+
"ListAgentAliasesRequest",
84+
"ListAgentKnowledgeBasesRequest",
85+
"ListAgentVersionsRequest",
86+
"PrepareAgentRequest",
87+
"UpdateAgentActionGroupRequest",
88+
"UpdateAgentAliasRequest",
89+
"UpdateAgentRequest");
90+
private static final List<String> agentResponseClasses =
91+
Arrays.asList(
92+
"DeleteAgentAliasResult",
93+
"DeleteAgentResult",
94+
"DeleteAgentVersionResult",
95+
"PrepareAgentResult");
96+
// DataSourceID
97+
private static final List<String> dataSourceRequestClasses =
98+
Arrays.asList("DeleteDataSourceRequest", "GetDataSourceRequest", "UpdateDataSourceRequest");
99+
private static final List<String> dataSourceResponseClasses =
100+
Arrays.asList("DeleteDataSourceResult");
101+
// KnowledgeBaseID
102+
private static final List<String> knowledgeBaseRequestClasses =
103+
Arrays.asList(
104+
"AssociateAgentKnowledgeBaseRequest",
105+
"CreateDataSourceRequest",
106+
"DeleteKnowledgeBaseRequest",
107+
"DisassociateAgentKnowledgeBaseRequest",
108+
"GetAgentKnowledgeBaseRequest",
109+
"GetKnowledgeBaseRequest",
110+
"ListDataSourcesRequest",
111+
"UpdateAgentKnowledgeBaseRequest");
112+
private static final List<String> knowledgeBaseResponseClasses =
113+
Arrays.asList("DeleteKnowledgeBaseResult");
114+
115+
private AwsBedrockResourceTypeMap() {}
116+
117+
static {
118+
// Populate the BEDROCK_REQUEST_MAP
119+
for (String agentRequestClass : agentRequestClasses) {
120+
BEDROCK_REQUEST_MAP.put(agentRequestClass, AwsBedrockResourceType.AGENT_TYPE);
121+
}
122+
for (String dataSourceRequestClass : dataSourceRequestClasses) {
123+
BEDROCK_REQUEST_MAP.put(dataSourceRequestClass, AwsBedrockResourceType.DATA_SOURCE_TYPE);
124+
}
125+
for (String knowledgeBaseRequestClass : knowledgeBaseRequestClasses) {
126+
BEDROCK_REQUEST_MAP.put(
127+
knowledgeBaseRequestClass, AwsBedrockResourceType.KNOWLEDGE_BASE_TYPE);
128+
}
129+
130+
// Populate the BEDROCK_RESPONSE_MAP
131+
for (String agentResponseClass : agentResponseClasses) {
132+
BEDROCK_REQUEST_MAP.put(agentResponseClass, AwsBedrockResourceType.AGENT_TYPE);
133+
}
134+
for (String dataSourceResponseClass : dataSourceResponseClasses) {
135+
BEDROCK_REQUEST_MAP.put(dataSourceResponseClass, AwsBedrockResourceType.DATA_SOURCE_TYPE);
136+
}
137+
for (String knowledgeBaseResponseClass : knowledgeBaseResponseClasses) {
138+
BEDROCK_REQUEST_MAP.put(
139+
knowledgeBaseResponseClass, AwsBedrockResourceType.KNOWLEDGE_BASE_TYPE);
140+
}
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)