Make sure AWS SDK spans suppress client spans all the time. (#1837)
* Make sure AWS SDK spans suppress client spans all the time. * Make more consistent with other instrumentation * Update instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/client/NettyHttpClientTracer.java Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com> * more dragons * Grammar * README Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
parent
7199595730
commit
c5a257619a
|
@ -9,9 +9,16 @@ muzzle {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':instrumentation:aws-sdk:aws-sdk-2.2:library')
|
implementation(project(':instrumentation:aws-sdk:aws-sdk-2.2:library')) {
|
||||||
|
exclude group: 'io.opentelemetry', module: 'opentelemetry-extension-trace-propagators'
|
||||||
|
}
|
||||||
|
|
||||||
library group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
library group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
||||||
|
|
||||||
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
||||||
|
// Make sure these don't add HTTP headers
|
||||||
|
testImplementation project(':instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent')
|
||||||
|
testImplementation project(':instrumentation:netty:netty-4.1:javaagent')
|
||||||
|
|
||||||
|
testImplementation deps.opentelemetryTraceProps
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
|
|
||||||
|
|
||||||
import static io.opentelemetry.javaagent.instrumentation.awssdk.v2_2.TracingExecutionInterceptor.ScopeHolder.CURRENT;
|
|
||||||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
|
||||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
|
||||||
import static io.opentelemetry.javaagent.tooling.matcher.NameMatchers.namedOneOf;
|
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
|
||||||
|
|
||||||
import io.opentelemetry.context.Scope;
|
|
||||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
|
||||||
import java.util.Map;
|
|
||||||
import net.bytebuddy.asm.Advice;
|
|
||||||
import net.bytebuddy.description.method.MethodDescription;
|
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
|
||||||
import net.bytebuddy.matcher.ElementMatcher;
|
|
||||||
import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeAsyncHttpRequestStage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Separate instrumentation class to close aws request scope right after request has been submitted
|
|
||||||
* for execution for Sync clients.
|
|
||||||
*/
|
|
||||||
public class AwsHttpClientInstrumentation implements TypeInstrumentation {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
|
||||||
return hasClassesNamed(
|
|
||||||
"software.amazon.awssdk.core.internal.http.pipeline.stages.MakeHttpRequestStage");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
|
||||||
return nameStartsWith("software.amazon.awssdk.")
|
|
||||||
.and(
|
|
||||||
extendsClass(
|
|
||||||
namedOneOf(
|
|
||||||
"software.amazon.awssdk.core.internal.http.pipeline.stages.MakeHttpRequestStage",
|
|
||||||
"software.amazon.awssdk.core.internal.http.pipeline.stages.MakeAsyncHttpRequestStage")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
|
||||||
return singletonMap(
|
|
||||||
isMethod().and(isPublic()).and(named("execute")),
|
|
||||||
AwsHttpClientInstrumentation.class.getName() + "$AwsHttpClientAdvice");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AwsHttpClientAdvice {
|
|
||||||
// scope.close here doesn't actually close the span.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FIXME: This is a hack to prevent netty instrumentation from messing things up.
|
|
||||||
*
|
|
||||||
* <p>Currently netty instrumentation cannot handle way AWS SDK makes http requests. If AWS SDK
|
|
||||||
* make a netty call with active scope then continuation will be created that would never be
|
|
||||||
* closed preventing whole trace from reporting. This happens because netty switches channels
|
|
||||||
* between connection and request stages and netty instrumentation cannot find continuation
|
|
||||||
* stored in channel attributes.
|
|
||||||
*/
|
|
||||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
|
||||||
public static boolean methodEnter(@Advice.This Object thiz) {
|
|
||||||
if (thiz instanceof MakeAsyncHttpRequestStage) {
|
|
||||||
Scope scope = CURRENT.get();
|
|
||||||
if (scope != null) {
|
|
||||||
CURRENT.set(null);
|
|
||||||
scope.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
|
||||||
public static void methodExit(@Advice.Enter boolean scopeAlreadyClosed) {
|
|
||||||
if (!scopeAlreadyClosed) {
|
|
||||||
Scope scope = CURRENT.get();
|
|
||||||
if (scope != null) {
|
|
||||||
CURRENT.set(null);
|
|
||||||
scope.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is to make muzzle think we need TracingExecutionInterceptor to make sure we do not apply
|
|
||||||
* this instrumentation when TracingExecutionInterceptor would not work.
|
|
||||||
*/
|
|
||||||
public static void muzzleCheck() {
|
|
||||||
TracingExecutionInterceptor.muzzleCheck();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNa
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
|
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
|
||||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import net.bytebuddy.matcher.ElementMatcher;
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ public class AwsSdkInstrumentationModule extends InstrumentationModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TypeInstrumentation> typeInstrumentations() {
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
return Arrays.asList(
|
return Collections.singletonList(new AwsSdkInitializationInstrumentation());
|
||||||
new AwsHttpClientInstrumentation(), new AwsSdkInitializationInstrumentation());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
|
package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2;
|
||||||
|
|
||||||
import io.opentelemetry.context.Context;
|
|
||||||
import io.opentelemetry.context.Scope;
|
|
||||||
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk;
|
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -41,10 +39,6 @@ import software.amazon.awssdk.http.SdkHttpResponse;
|
||||||
*/
|
*/
|
||||||
public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
|
|
||||||
public static class ScopeHolder {
|
|
||||||
public static final ThreadLocal<Scope> CURRENT = new ThreadLocal<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ExecutionInterceptor delegate;
|
private final ExecutionInterceptor delegate;
|
||||||
|
|
||||||
public TracingExecutionInterceptor() {
|
public TracingExecutionInterceptor() {
|
||||||
|
@ -98,12 +92,6 @@ public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
public void beforeTransmission(
|
public void beforeTransmission(
|
||||||
BeforeTransmission context, ExecutionAttributes executionAttributes) {
|
BeforeTransmission context, ExecutionAttributes executionAttributes) {
|
||||||
delegate.beforeTransmission(context, executionAttributes);
|
delegate.beforeTransmission(context, executionAttributes);
|
||||||
Context parentContext = AwsSdk.getContextFromAttributes(executionAttributes);
|
|
||||||
if (parentContext != null) {
|
|
||||||
// This scope will be closed by AwsHttpClientInstrumentation since ExecutionInterceptor API
|
|
||||||
// doesn't provide a way to run code in the same thread after transmission has been scheduled.
|
|
||||||
ScopeHolder.CURRENT.set(parentContext.makeCurrent());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# AWS Java SDK v2 Instrumentation
|
||||||
|
|
||||||
|
Instrumentation for [AWS Java SDK v2](https://github.com/aws/aws-sdk-java-v2).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To register instrumentation on an SDK client, register the interceptor when creating it.
|
||||||
|
|
||||||
|
```java
|
||||||
|
DynamoDbClient client = DynamoDbClient.builder()
|
||||||
|
.overrideConfiguration(ClientOverrideConfiguration.builder()
|
||||||
|
.addExecutionInterceptor(AwsSdk.newInterceptor()))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trace propagation
|
||||||
|
|
||||||
|
The AWS SDK instrumentation currently only supports injecting the trace header into the request
|
||||||
|
using the [AWS Trace Header](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader) format.
|
||||||
|
This format is the only format recognized by AWS managed services, and populating will allow
|
||||||
|
propagating the trace through them. If this does not fulfill your use case, perhaps because you are
|
||||||
|
using the same SDK with a different non-AWS managed service, let us know so we can provide
|
||||||
|
configuration for this behavior.
|
|
@ -1,6 +1,8 @@
|
||||||
apply from: "$rootDir/gradle/instrumentation-library.gradle"
|
apply from: "$rootDir/gradle/instrumentation-library.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation deps.opentelemetryTraceProps
|
||||||
|
|
||||||
library group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
library group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
||||||
|
|
||||||
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.api.trace.Span.Kind;
|
import io.opentelemetry.api.trace.Span.Kind;
|
||||||
import io.opentelemetry.api.trace.Tracer;
|
import io.opentelemetry.api.trace.Tracer;
|
||||||
import io.opentelemetry.api.trace.attributes.SemanticAttributes;
|
import io.opentelemetry.api.trace.attributes.SemanticAttributes;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.propagation.TextMapPropagator.Setter;
|
import io.opentelemetry.context.propagation.TextMapPropagator.Setter;
|
||||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
||||||
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
||||||
|
@ -77,15 +78,9 @@ final class AwsSdkHttpClientTracer
|
||||||
return super.onRequest(span, sdkHttpRequest);
|
return super.onRequest(span, sdkHttpRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span getOrCreateSpan(String name, Tracer tracer, Kind kind) {
|
public Context startSpan(Context parentContext, String name, Tracer tracer, Kind kind) {
|
||||||
io.opentelemetry.context.Context context = io.opentelemetry.context.Context.current();
|
Span clientSpan =
|
||||||
Span clientSpan = context.get(BaseTracer.CONTEXT_CLIENT_SPAN_KEY);
|
tracer.spanBuilder(name).setSpanKind(kind).setParent(parentContext).startSpan();
|
||||||
|
return parentContext.with(clientSpan).with(BaseTracer.CONTEXT_CLIENT_SPAN_KEY, clientSpan);
|
||||||
if (clientSpan != null) {
|
|
||||||
// We don't want to create two client spans for a given client call, suppress inner spans.
|
|
||||||
return Span.getInvalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracer.spanBuilder(name).setSpanKind(kind).setParent(context).startSpan();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,16 @@ import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk.getContextFrom
|
||||||
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkHttpClientTracer.tracer;
|
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkHttpClientTracer.tracer;
|
||||||
import static io.opentelemetry.instrumentation.awssdk.v2_2.RequestType.ofSdkRequest;
|
import static io.opentelemetry.instrumentation.awssdk.v2_2.RequestType.ofSdkRequest;
|
||||||
|
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.api.trace.Span.Kind;
|
import io.opentelemetry.api.trace.Span.Kind;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.extension.trace.propagation.AwsXRayPropagator;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import software.amazon.awssdk.awscore.AwsResponse;
|
import software.amazon.awssdk.awscore.AwsResponse;
|
||||||
|
import software.amazon.awssdk.core.ClientType;
|
||||||
import software.amazon.awssdk.core.SdkRequest;
|
import software.amazon.awssdk.core.SdkRequest;
|
||||||
import software.amazon.awssdk.core.SdkResponse;
|
import software.amazon.awssdk.core.SdkResponse;
|
||||||
import software.amazon.awssdk.core.interceptor.Context;
|
import software.amazon.awssdk.core.interceptor.Context;
|
||||||
|
@ -33,9 +35,8 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
// instrumentation, and won't conflict with usage outside javaagent instrumentation
|
// instrumentation, and won't conflict with usage outside javaagent instrumentation
|
||||||
static final ExecutionAttribute<io.opentelemetry.context.Context> CONTEXT_ATTRIBUTE =
|
static final ExecutionAttribute<io.opentelemetry.context.Context> CONTEXT_ATTRIBUTE =
|
||||||
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".Context");
|
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".Context");
|
||||||
|
static final ExecutionAttribute<Scope> SCOPE_ATTRIBUTE =
|
||||||
// the class name is part of the attribute name, so that it will be shaded when used in javaagent
|
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".Scope");
|
||||||
// instrumentation, and won't conflict with usage outside javaagent instrumentation
|
|
||||||
static final ExecutionAttribute<RequestType> REQUEST_TYPE_ATTRIBUTE =
|
static final ExecutionAttribute<RequestType> REQUEST_TYPE_ATTRIBUTE =
|
||||||
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".RequestType");
|
new ExecutionAttribute<>(TracingExecutionInterceptor.class.getName() + ".RequestType");
|
||||||
|
|
||||||
|
@ -79,29 +80,36 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
@Override
|
@Override
|
||||||
public void beforeExecution(
|
public void beforeExecution(
|
||||||
Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
|
Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
|
||||||
Span span = tracer().getOrCreateSpan(spanName(executionAttributes), AwsSdk.tracer(), kind);
|
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current();
|
||||||
executionAttributes.putAttribute(
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
CONTEXT_ATTRIBUTE, io.opentelemetry.context.Context.current().with(span));
|
return;
|
||||||
|
}
|
||||||
|
io.opentelemetry.context.Context otelContext =
|
||||||
|
tracer().startSpan(parentContext, spanName(executionAttributes), AwsSdk.tracer(), kind);
|
||||||
|
executionAttributes.putAttribute(CONTEXT_ATTRIBUTE, otelContext);
|
||||||
RequestType type = ofSdkRequest(context.request());
|
RequestType type = ofSdkRequest(context.request());
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
executionAttributes.putAttribute(REQUEST_TYPE_ATTRIBUTE, type);
|
executionAttributes.putAttribute(REQUEST_TYPE_ATTRIBUTE, type);
|
||||||
}
|
}
|
||||||
|
if (executionAttributes
|
||||||
|
.getAttribute(SdkExecutionAttribute.CLIENT_TYPE)
|
||||||
|
.equals(ClientType.SYNC)) {
|
||||||
|
// We can only activate context for synchronous clients, which allows downstream
|
||||||
|
// instrumentation like Apache to know about the SDK span.
|
||||||
|
executionAttributes.putAttribute(SCOPE_ATTRIBUTE, otelContext.makeCurrent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SdkHttpRequest modifyHttpRequest(
|
public SdkHttpRequest modifyHttpRequest(
|
||||||
Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
|
Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
|
||||||
io.opentelemetry.context.Context otelContext =
|
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes);
|
||||||
executionAttributes.getAttribute(CONTEXT_ATTRIBUTE);
|
|
||||||
// Never null in practice unless another interceptor cleared out the attribute, which
|
|
||||||
// is theoretically possible.
|
|
||||||
if (otelContext == null) {
|
if (otelContext == null) {
|
||||||
return context.httpRequest();
|
return context.httpRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
SdkHttpRequest.Builder builder = context.httpRequest().toBuilder();
|
SdkHttpRequest.Builder builder = context.httpRequest().toBuilder();
|
||||||
OpenTelemetry.getGlobalPropagators()
|
AwsXRayPropagator.getInstance().inject(otelContext, builder, AwsSdkInjectAdapter.INSTANCE);
|
||||||
.getTextMapPropagator()
|
|
||||||
.inject(otelContext, builder, AwsSdkInjectAdapter.INSTANCE);
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +117,10 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
public void afterMarshalling(
|
public void afterMarshalling(
|
||||||
Context.AfterMarshalling context, ExecutionAttributes executionAttributes) {
|
Context.AfterMarshalling context, ExecutionAttributes executionAttributes) {
|
||||||
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes);
|
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes);
|
||||||
if (otelContext != null) {
|
if (otelContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Span span = Span.fromContext(otelContext);
|
Span span = Span.fromContext(otelContext);
|
||||||
tracer().onRequest(span, context.httpRequest());
|
tracer().onRequest(span, context.httpRequest());
|
||||||
SdkRequestDecorator decorator = decorator(executionAttributes);
|
SdkRequestDecorator decorator = decorator(executionAttributes);
|
||||||
|
@ -119,7 +130,6 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
decorateWithGenericRequestData(span, context.request());
|
decorateWithGenericRequestData(span, context.request());
|
||||||
decorateWithExAttributesData(span, executionAttributes);
|
decorateWithExAttributesData(span, executionAttributes);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void decorateWithGenericRequestData(Span span, SdkRequest request) {
|
private void decorateWithGenericRequestData(Span span, SdkRequest request) {
|
||||||
|
|
||||||
|
@ -146,6 +156,10 @@ final class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||||
@Override
|
@Override
|
||||||
public void afterExecution(
|
public void afterExecution(
|
||||||
Context.AfterExecution context, ExecutionAttributes executionAttributes) {
|
Context.AfterExecution context, ExecutionAttributes executionAttributes) {
|
||||||
|
Scope scope = executionAttributes.getAttribute(SCOPE_ATTRIBUTE);
|
||||||
|
if (scope != null) {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes);
|
io.opentelemetry.context.Context otelContext = getContextFromAttributes(executionAttributes);
|
||||||
if (otelContext != null) {
|
if (otelContext != null) {
|
||||||
clearAttributes(executionAttributes);
|
clearAttributes(executionAttributes);
|
||||||
|
|
|
@ -148,7 +148,8 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.lastRequest.headers.get("traceparent") != null
|
server.lastRequest.headers.get("X-Amzn-Trace-Id") != null
|
||||||
|
server.lastRequest.headers.get("traceparent") == null
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamoDbRequestDataTable(client) {
|
static dynamoDbRequestDataTable(client) {
|
||||||
|
@ -226,7 +227,8 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.lastRequest.headers.get("traceparent") != null
|
server.lastRequest.headers.get("X-Amzn-Trace-Id") != null
|
||||||
|
server.lastRequest.headers.get("traceparent") == null
|
||||||
|
|
||||||
where:
|
where:
|
||||||
service | operation | method | path | requestId | builder | call | body
|
service | operation | method | path | requestId | builder | call | body
|
||||||
|
@ -314,7 +316,8 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.lastRequest.headers.get("traceparent") != null
|
server.lastRequest.headers.get("X-Amzn-Trace-Id") != null
|
||||||
|
server.lastRequest.headers.get("traceparent") == null
|
||||||
|
|
||||||
where:
|
where:
|
||||||
service | operation | method | path | requestId | builder | call | body
|
service | operation | method | path | requestId | builder | call | body
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
||||||
parentContext = Context.current();
|
parentContext = Context.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tracer().shouldStartSpan(parentContext)) {
|
if (!tracer().shouldStartSpan(parentContext, request)) {
|
||||||
ctx.write(msg, prm);
|
ctx.write(msg, prm);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.client.Netty
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.propagation.TextMapPropagator.Setter;
|
import io.opentelemetry.context.propagation.TextMapPropagator.Setter;
|
||||||
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -65,6 +66,24 @@ public class NettyHttpClientTracer
|
||||||
return SETTER;
|
return SETTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean shouldStartSpan(Context parentContext, HttpRequest request) {
|
||||||
|
if (!super.shouldStartSpan(parentContext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The AWS SDK uses Netty for asynchronous clients but constructs a request signature before
|
||||||
|
// beginning transport. This means we MUST suppress Netty spans we would normally create or
|
||||||
|
// they will inject their own trace header, which does not match what was present when the
|
||||||
|
// signature was computed, breaking the SDK request completely. We have not found how to
|
||||||
|
// cleanly propagate context from the SDK instrumentation, which executes on an application
|
||||||
|
// thread, to Netty instrumentation, which executes on event loops. If it's possible, it may
|
||||||
|
// require instrumenting internal classes. Using a header which is more or less guaranteed to
|
||||||
|
// always exist is arguably more stable.
|
||||||
|
if (request.headers().contains("amz-sdk-invocation-id")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getInstrumentationName() {
|
protected String getInstrumentationName() {
|
||||||
return "io.opentelemetry.javaagent.netty";
|
return "io.opentelemetry.javaagent.netty";
|
||||||
|
|
Loading…
Reference in New Issue