Merge tag 'v0.41.0' into dd-merge

This commit is contained in:
Trask Stalnaker 2020-01-15 18:41:12 -08:00
commit c47f92c6b0
53 changed files with 3256 additions and 103 deletions

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -328,9 +328,8 @@ public class ByteBuddyElementMatchers {
final Set<TypeDefinition> checkedInterfaces = new HashSet<>(); final Set<TypeDefinition> checkedInterfaces = new HashSet<>();
while (declaringType != null) { while (declaringType != null) {
for (final MethodDescription methodDescription : for (final MethodDescription methodDescription : declaringType.getDeclaredMethods()) {
declaringType.getDeclaredMethods().filter(signatureMatcher)) { if (signatureMatcher.matches(methodDescription) && matcher.matches(methodDescription)) {
if (matcher.matches(methodDescription)) {
return true; return true;
} }
} }
@ -349,9 +348,8 @@ public class ByteBuddyElementMatchers {
for (final TypeDefinition type : interfaces) { for (final TypeDefinition type : interfaces) {
if (!checkedInterfaces.contains(type)) { if (!checkedInterfaces.contains(type)) {
checkedInterfaces.add(type); checkedInterfaces.add(type);
for (final MethodDescription methodDescription : for (final MethodDescription methodDescription : type.getDeclaredMethods()) {
type.getDeclaredMethods().filter(signatureMatcher)) { if (signatureMatcher.matches(methodDescription) && matcher.matches(methodDescription)) {
if (matcher.matches(methodDescription)) {
return true; return true;
} }
} }

View File

@ -53,8 +53,11 @@ public class MuzzleVersionScanPlugin {
final ReferenceMatcher muzzle = (ReferenceMatcher) m.invoke(instrumenter); final ReferenceMatcher muzzle = (ReferenceMatcher) m.invoke(instrumenter);
final List<Reference.Mismatch> mismatches = final List<Reference.Mismatch> mismatches =
muzzle.getMismatchedReferenceSources(userClassLoader); muzzle.getMismatchedReferenceSources(userClassLoader);
final boolean passed = mismatches.size() == 0;
if (mismatches.size() > 0) {} final boolean classLoaderMatch =
((Instrumenter.Default) instrumenter).classLoaderMatcher().matches(userClassLoader);
final boolean passed = mismatches.isEmpty() && classLoaderMatch;
if (passed && !assertPass) { if (passed && !assertPass) {
System.err.println( System.err.println(
"MUZZLE PASSED " "MUZZLE PASSED "
@ -64,6 +67,11 @@ public class MuzzleVersionScanPlugin {
} else if (!passed && assertPass) { } else if (!passed && assertPass) {
System.err.println( System.err.println(
"FAILED MUZZLE VALIDATION: " + instrumenter.getClass().getName() + " mismatches:"); "FAILED MUZZLE VALIDATION: " + instrumenter.getClass().getName() + " mismatches:");
if (!classLoaderMatch) {
System.err.println("-- classloader mismatch");
}
for (final Reference.Mismatch mismatch : mismatches) { for (final Reference.Mismatch mismatch : mismatches) {
System.err.println("-- " + mismatch); System.err.println("-- " + mismatch);
} }

View File

@ -42,6 +42,12 @@ dependencies {
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.106' testCompile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.106'
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '1.11.106' testCompile group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '1.11.106'
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.106' testCompile group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.106'
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '1.11.106'
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.106'
testCompile group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.106'
// needed for kinesis:
testCompile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: versions.jackson
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.0') { test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.11.0') {
force = true force = true
@ -52,10 +58,22 @@ dependencies {
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.0') { test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.0') {
force = true force = true
} }
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '1.11.0') {
force = true
}
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.0') {
force = true
}
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.0') {
force = true
}
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '+' latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '+'
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '+' latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: '+'
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '+' latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '+'
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-kinesis', version: '+'
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '+'
latestDepTestCompile group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '+'
} }
test.dependsOn test_before_1_11_106 test.dependsOn test_before_1_11_106

View File

@ -5,8 +5,10 @@ import static net.bytebuddy.matcher.ElementMatchers.declaresField;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.handlers.RequestHandler2; import com.amazonaws.handlers.RequestHandler2;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.tooling.Instrumenter; import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -39,6 +41,7 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
"io.opentelemetry.auto.decorator.ClientDecorator", "io.opentelemetry.auto.decorator.ClientDecorator",
"io.opentelemetry.auto.decorator.HttpClientDecorator", "io.opentelemetry.auto.decorator.HttpClientDecorator",
packageName + ".AwsSdkClientDecorator", packageName + ".AwsSdkClientDecorator",
packageName + ".RequestMeta",
packageName + ".TracingRequestHandler", packageName + ".TracingRequestHandler",
}; };
} }
@ -49,6 +52,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
isConstructor(), AWSClientInstrumentation.class.getName() + "$AWSClientAdvice"); isConstructor(), AWSClientInstrumentation.class.getName() + "$AWSClientAdvice");
} }
@Override
public Map<String, String> contextStore() {
return singletonMap("com.amazonaws.AmazonWebServiceRequest", packageName + ".RequestMeta");
}
public static class AWSClientAdvice { public static class AWSClientAdvice {
// Since we're instrumenting the constructor, we can't add onThrowable. // Since we're instrumenting the constructor, we can't add onThrowable.
@Advice.OnMethodExit(suppress = Throwable.class) @Advice.OnMethodExit(suppress = Throwable.class)
@ -62,7 +70,9 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
} }
} }
if (!hasAgentHandler) { if (!hasAgentHandler) {
handlers.add(TracingRequestHandler.INSTANCE); handlers.add(
new TracingRequestHandler(
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class)));
} }
} }
} }

View File

@ -1,6 +1,7 @@
package io.opentelemetry.auto.instrumentation.aws.v0; package io.opentelemetry.auto.instrumentation.aws.v0;
import static io.opentelemetry.auto.instrumentation.aws.v0.AwsSdkClientDecorator.DECORATE; import static io.opentelemetry.auto.instrumentation.aws.v0.OnErrorDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.aws.v0.RequestMeta.SPAN_SCOPE_PAIR_CONTEXT_KEY;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
@ -41,10 +42,8 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {
"io.opentelemetry.auto.decorator.BaseDecorator", "io.opentelemetry.auto.decorator.BaseDecorator",
"io.opentelemetry.auto.decorator.ClientDecorator", packageName + ".OnErrorDecorator",
"io.opentelemetry.auto.decorator.HttpClientDecorator", packageName + ".RequestMeta",
packageName + ".AwsSdkClientDecorator",
packageName + ".TracingRequestHandler",
}; };
} }
@ -61,10 +60,9 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
@Advice.Argument(value = 0, optional = true) final Request<?> request, @Advice.Argument(value = 0, optional = true) final Request<?> request,
@Advice.Thrown final Throwable throwable) { @Advice.Thrown final Throwable throwable) {
if (throwable != null) { if (throwable != null) {
final SpanScopePair spanScopePair = final SpanScopePair spanScopePair = request.getHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY);
request.getHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY);
if (spanScopePair != null) { if (spanScopePair != null) {
request.addHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY, null); request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null);
final Span span = spanScopePair.getSpan(); final Span span = spanScopePair.getSpan();
DECORATE.onError(span, throwable); DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span); DECORATE.beforeFinish(span);
@ -101,9 +99,9 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
@Advice.Thrown final Throwable throwable) { @Advice.Thrown final Throwable throwable) {
if (throwable != null) { if (throwable != null) {
final SpanScopePair spanScopePair = final SpanScopePair spanScopePair =
request.getHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY); request.getHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY);
if (spanScopePair != null) { if (spanScopePair != null) {
request.addHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY, null); request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null);
final Span span = spanScopePair.getSpan(); final Span span = spanScopePair.getSpan();
DECORATE.onError(span, throwable); DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span); DECORATE.beforeFinish(span);

View File

@ -1,9 +1,11 @@
package io.opentelemetry.auto.instrumentation.aws.v0; package io.opentelemetry.auto.instrumentation.aws.v0;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.AmazonWebServiceResponse; import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.Request; import com.amazonaws.Request;
import com.amazonaws.Response; import com.amazonaws.Response;
import io.opentelemetry.auto.api.MoreTags; import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.decorator.HttpClientDecorator; import io.opentelemetry.auto.decorator.HttpClientDecorator;
import io.opentelemetry.trace.Span; import io.opentelemetry.trace.Span;
import java.net.URI; import java.net.URI;
@ -12,12 +14,17 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response> { public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response> {
public static final AwsSdkClientDecorator DECORATE = new AwsSdkClientDecorator();
static final String COMPONENT_NAME = "java-aws-sdk"; static final String COMPONENT_NAME = "java-aws-sdk";
private final Map<String, String> serviceNames = new ConcurrentHashMap<>(); private final Map<String, String> serviceNames = new ConcurrentHashMap<>();
private final Map<Class, String> operationNames = new ConcurrentHashMap<>(); private final Map<Class, String> operationNames = new ConcurrentHashMap<>();
private final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore;
public AwsSdkClientDecorator(
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore) {
this.contextStore = contextStore;
}
@Override @Override
public Span onRequest(final Span span, final Request request) { public Span onRequest(final Span span, final Request request) {
@ -25,7 +32,8 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response
super.onRequest(span, request); super.onRequest(span, request);
final String awsServiceName = request.getServiceName(); final String awsServiceName = request.getServiceName();
final Class<?> awsOperation = request.getOriginalRequest().getClass(); final AmazonWebServiceRequest originalRequest = request.getOriginalRequest();
final Class<?> awsOperation = originalRequest.getClass();
span.setAttribute("aws.agent", COMPONENT_NAME); span.setAttribute("aws.agent", COMPONENT_NAME);
span.setAttribute("aws.service", awsServiceName); span.setAttribute("aws.service", awsServiceName);
@ -36,6 +44,32 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response
MoreTags.RESOURCE_NAME, MoreTags.RESOURCE_NAME,
remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation)); remapServiceName(awsServiceName) + "." + remapOperationName(awsOperation));
if (contextStore != null) {
final RequestMeta requestMeta = contextStore.get(originalRequest);
if (requestMeta != null) {
final String bucketName = requestMeta.getBucketName();
if (bucketName != null) {
span.setAttribute("aws.bucket.name", bucketName);
}
final String queueUrl = requestMeta.getQueueUrl();
if (queueUrl != null) {
span.setAttribute("aws.queue.url", queueUrl);
}
final String queueName = requestMeta.getQueueName();
if (queueName != null) {
span.setAttribute("aws.queue.name", queueName);
}
final String streamName = requestMeta.getStreamName();
if (streamName != null) {
span.setAttribute("aws.stream.name", streamName);
}
final String tableName = requestMeta.getTableName();
if (tableName != null) {
span.setAttribute("aws.table.name", tableName);
}
}
}
return span; return span;
} }

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.aws.v0;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class OnErrorDecorator extends BaseDecorator {
public static final OnErrorDecorator DECORATE = new OnErrorDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"aws-sdk"};
}
@Override
protected String spanType() {
return null;
}
@Override
protected String component() {
return "java-aws-sdk";
}
}

View File

@ -0,0 +1,144 @@
package io.opentelemetry.auto.instrumentation.aws.v0;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.amazonaws.AmazonWebServiceRequest;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
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;
@AutoService(Instrumenter.class)
public final class RequestInstrumentation extends Instrumenter.Default {
public RequestInstrumentation() {
super("aws-sdk");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(named("com.amazonaws.AmazonWebServiceRequest"));
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".RequestMeta",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
named("setBucketName").and(takesArgument(0, String.class)),
RequestInstrumentation.class.getName() + "$BucketNameAdvice");
transformers.put(
named("setQueueUrl").and(takesArgument(0, String.class)),
RequestInstrumentation.class.getName() + "$QueueUrlAdvice");
transformers.put(
named("setQueueName").and(takesArgument(0, String.class)),
RequestInstrumentation.class.getName() + "$QueueNameAdvice");
transformers.put(
named("setStreamName").and(takesArgument(0, String.class)),
RequestInstrumentation.class.getName() + "$StreamNameAdvice");
transformers.put(
named("setTableName").and(takesArgument(0, String.class)),
RequestInstrumentation.class.getName() + "$TableNameAdvice");
return transformers;
}
@Override
public Map<String, String> contextStore() {
return singletonMap("com.amazonaws.AmazonWebServiceRequest", packageName + ".RequestMeta");
}
public static class BucketNameAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(0) final String value,
@Advice.This final AmazonWebServiceRequest request) {
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore =
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class);
RequestMeta requestMeta = contextStore.get(request);
if (requestMeta == null) {
requestMeta = new RequestMeta();
contextStore.put(request, requestMeta);
}
requestMeta.setBucketName(value);
}
}
public static class QueueUrlAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(0) final String value,
@Advice.This final AmazonWebServiceRequest request) {
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore =
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class);
RequestMeta requestMeta = contextStore.get(request);
if (requestMeta == null) {
requestMeta = new RequestMeta();
contextStore.put(request, requestMeta);
}
requestMeta.setQueueUrl(value);
}
}
public static class QueueNameAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(0) final String value,
@Advice.This final AmazonWebServiceRequest request) {
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore =
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class);
RequestMeta requestMeta = contextStore.get(request);
if (requestMeta == null) {
requestMeta = new RequestMeta();
contextStore.put(request, requestMeta);
}
requestMeta.setQueueName(value);
}
}
public static class StreamNameAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(0) final String value,
@Advice.This final AmazonWebServiceRequest request) {
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore =
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class);
RequestMeta requestMeta = contextStore.get(request);
if (requestMeta == null) {
requestMeta = new RequestMeta();
contextStore.put(request, requestMeta);
}
requestMeta.setStreamName(value);
}
}
public static class TableNameAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.Argument(0) final String value,
@Advice.This final AmazonWebServiceRequest request) {
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore =
InstrumentationContext.get(AmazonWebServiceRequest.class, RequestMeta.class);
RequestMeta requestMeta = contextStore.get(request);
if (requestMeta == null) {
requestMeta = new RequestMeta();
contextStore.put(request, requestMeta);
}
requestMeta.setTableName(value);
}
}
}

View File

@ -0,0 +1,19 @@
package io.opentelemetry.auto.instrumentation.aws.v0;
import com.amazonaws.handlers.HandlerContextKey;
import io.opentelemetry.auto.instrumentation.api.SpanScopePair;
import lombok.Data;
@Data
public class RequestMeta {
// Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context
// safely.
public static final HandlerContextKey<SpanScopePair> SPAN_SCOPE_PAIR_CONTEXT_KEY =
new HandlerContextKey<>("io.opentelemetry.auto.SpanScopePair");
private String bucketName;
private String queueUrl;
private String queueName;
private String streamName;
private String tableName;
}

View File

@ -1,28 +1,27 @@
package io.opentelemetry.auto.instrumentation.aws.v0; package io.opentelemetry.auto.instrumentation.aws.v0;
import static io.opentelemetry.auto.instrumentation.aws.v0.AwsSdkClientDecorator.DECORATE; import static io.opentelemetry.auto.instrumentation.aws.v0.RequestMeta.SPAN_SCOPE_PAIR_CONTEXT_KEY;
import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.Request; import com.amazonaws.Request;
import com.amazonaws.Response; import com.amazonaws.Response;
import com.amazonaws.handlers.HandlerContextKey;
import com.amazonaws.handlers.RequestHandler2; import com.amazonaws.handlers.RequestHandler2;
import io.opentelemetry.OpenTelemetry; import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.instrumentation.api.SpanScopePair; import io.opentelemetry.auto.instrumentation.api.SpanScopePair;
import io.opentelemetry.trace.Span; import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer; import io.opentelemetry.trace.Tracer;
/** Tracing Request Handler */ /** Tracing Request Handler */
public class TracingRequestHandler extends RequestHandler2 { public class TracingRequestHandler extends RequestHandler2 {
public static TracingRequestHandler INSTANCE = new TracingRequestHandler(); public static final Tracer TRACER = OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto");
private static final Tracer TRACER = private final AwsSdkClientDecorator decorate;
OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto");
// Note: aws1.x sdk doesn't have any truly async clients so we can store scope in request context public TracingRequestHandler(
// safely. final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore) {
public static final HandlerContextKey<SpanScopePair> SPAN_SCOPE_PAIR_CONTEXT_KEY = decorate = new AwsSdkClientDecorator(contextStore);
new HandlerContextKey<>("io.opentelemetry.auto.SpanScopePair"); }
@Override @Override
public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) { public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) {
@ -32,8 +31,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override @Override
public void beforeRequest(final Request<?> request) { public void beforeRequest(final Request<?> request) {
final Span span = TRACER.spanBuilder("aws.http").startSpan(); final Span span = TRACER.spanBuilder("aws.http").startSpan();
DECORATE.afterStart(span); decorate.afterStart(span);
DECORATE.onRequest(span, request); decorate.onRequest(span, request);
request.addHandlerContext( request.addHandlerContext(
SPAN_SCOPE_PAIR_CONTEXT_KEY, new SpanScopePair(span, TRACER.withSpan(span))); SPAN_SCOPE_PAIR_CONTEXT_KEY, new SpanScopePair(span, TRACER.withSpan(span)));
} }
@ -45,8 +44,8 @@ public class TracingRequestHandler extends RequestHandler2 {
request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null); request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null);
spanScopePair.getScope().close(); spanScopePair.getScope().close();
final Span span = spanScopePair.getSpan(); final Span span = spanScopePair.getSpan();
DECORATE.onResponse(span, response); decorate.onResponse(span, response);
DECORATE.beforeFinish(span); decorate.beforeFinish(span);
span.end(); span.end();
} }
} }
@ -58,8 +57,8 @@ public class TracingRequestHandler extends RequestHandler2 {
request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null); request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null);
spanScopePair.getScope().close(); spanScopePair.getScope().close();
final Span span = spanScopePair.getSpan(); final Span span = spanScopePair.getSpan();
DECORATE.onError(span, e); decorate.onError(span, e);
DECORATE.beforeFinish(span); decorate.beforeFinish(span);
span.end(); span.end();
} }
} }

View File

@ -16,11 +16,18 @@ import com.amazonaws.client.builder.AwsClientBuilder
import com.amazonaws.handlers.RequestHandler2 import com.amazonaws.handlers.RequestHandler2
import com.amazonaws.regions.Regions import com.amazonaws.regions.Regions
import com.amazonaws.retry.PredefinedRetryPolicies import com.amazonaws.retry.PredefinedRetryPolicies
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder import com.amazonaws.services.ec2.AmazonEC2ClientBuilder
import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder
import com.amazonaws.services.kinesis.model.DeleteStreamRequest
import com.amazonaws.services.rds.AmazonRDSClientBuilder import com.amazonaws.services.rds.AmazonRDSClientBuilder
import com.amazonaws.services.rds.model.DeleteOptionGroupRequest import com.amazonaws.services.rds.model.DeleteOptionGroupRequest
import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.s3.AmazonS3Client
import com.amazonaws.services.s3.AmazonS3ClientBuilder import com.amazonaws.services.s3.AmazonS3ClientBuilder
import com.amazonaws.services.sqs.AmazonSQSClientBuilder
import com.amazonaws.services.sqs.model.CreateQueueRequest
import com.amazonaws.services.sqs.model.SendMessageRequest
import io.opentelemetry.auto.api.MoreTags import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.api.SpanTypes import io.opentelemetry.auto.api.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.instrumentation.api.Tags
@ -144,6 +151,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address" "aws.endpoint" "$server.address"
"aws.operation" "${operation}Request" "aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
} }
} }
span(1) { span(1) {
@ -156,7 +166,7 @@ class AWSClientTest extends AgentTestRunner {
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.PEER_HOSTNAME" "localhost" "$Tags.PEER_HOSTNAME" "localhost"
"$Tags.PEER_PORT" server.address.port "$Tags.PEER_PORT" server.address.port
"$Tags.HTTP_URL" "$server.address/$url" "$Tags.HTTP_URL" "${server.address}${path}"
"$Tags.HTTP_METHOD" "$method" "$Tags.HTTP_METHOD" "$method"
"$Tags.HTTP_STATUS" 200 "$Tags.HTTP_STATUS" 200
} }
@ -166,23 +176,41 @@ class AWSClientTest extends AgentTestRunner {
server.lastRequest.headers.get("traceparent") == null server.lastRequest.headers.get("traceparent") == null
where: where:
service | operation | method | url | handlerCount | call | body | client service | operation | method | path | handlerCount | client | call | additionalTags | body
"S3" | "CreateBucket" | "PUT" | "testbucket/" | 1 | { client -> client.createBucket("testbucket") } | "" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() "S3" | "CreateBucket" | "PUT" | "/testbucket/" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.createBucket("testbucket") } | ["aws.bucket.name": "testbucket"] | ""
"S3" | "GetObject" | "GET" | "someBucket/someKey" | 1 | { client -> client.getObject("someBucket", "someKey") } | "" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() "S3" | "GetObject" | "GET" | "/someBucket/someKey" | 1 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | ""
"EC2" | "AllocateAddress" | "POST" | "" | 4 | { client -> client.allocateAddress() } | """ "DynamoDBv2" | "CreateTable" | "POST" | "/" | 1 | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable"] | ""
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"> "Kinesis" | "DeleteStream" | "POST" | "/" | 1 | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream"] | ""
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> "SQS" | "CreateQueue" | "POST" | "/" | 4 | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue"] | """
<publicIp>192.0.2.1</publicIp> <CreateQueueResponse>
<domain>standard</domain> <CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/MyQueue</QueueUrl></CreateQueueResult>
</AllocateAddressResponse> <ResponseMetadata><RequestId>7a62c49f-347e-4fc4-9331-6e8e7a96aa73</RequestId></ResponseMetadata>
""" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() </CreateQueueResponse>
"RDS" | "DeleteOptionGroup" | "POST" | "" | 5 | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | """ """
"SQS" | "SendMessage" | "POST" | "/someurl" | 4 | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("someurl", "")) } | ["aws.queue.url": "someurl"] | """
<SendMessageResponse>
<SendMessageResult>
<MD5OfMessageBody>d41d8cd98f00b204e9800998ecf8427e</MD5OfMessageBody>
<MD5OfMessageAttributes>3ae8f24a165a8cedc005670c81a27295</MD5OfMessageAttributes>
<MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
</SendMessageResult>
<ResponseMetadata><RequestId>27daac76-34dd-47df-bd01-1f6e873584a0</RequestId></ResponseMetadata>
</SendMessageResponse>
"""
"EC2" | "AllocateAddress" | "POST" | "/" | 4 | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.allocateAddress() } | [:] | """
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<publicIp>192.0.2.1</publicIp>
<domain>standard</domain>
</AllocateAddressResponse>
"""
"RDS" | "DeleteOptionGroup" | "POST" | "/" | 5 | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """
<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/"> <DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata> <ResponseMetadata>
<RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId> <RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId>
</ResponseMetadata> </ResponseMetadata>
</DeleteOptionGroupResponse> </DeleteOptionGroupResponse>
""" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() """
} }
def "send #operation request to closed port"() { def "send #operation request to closed port"() {
@ -213,6 +241,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:${UNUSABLE_PORT}" "aws.endpoint" "http://localhost:${UNUSABLE_PORT}"
"aws.operation" "${operation}Request" "aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
errorTags SdkClientException, ~/Unable to execute HTTP request/ errorTags SdkClientException, ~/Unable to execute HTTP request/
} }
} }
@ -235,8 +266,8 @@ class AWSClientTest extends AgentTestRunner {
} }
where: where:
service | operation | method | url | call | body | client service | operation | method | url | call | additionalTags | body | client
"S3" | "GetObject" | "GET" | "someBucket/someKey" | { client -> client.getObject("someBucket", "someKey") } | "" | new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN, new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))).withEndpoint("http://localhost:${UNUSABLE_PORT}") "S3" | "GetObject" | "GET" | "someBucket/someKey" | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" | new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN, new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))).withEndpoint("http://localhost:${UNUSABLE_PORT}")
} }
def "naughty request handler doesn't break the trace"() { def "naughty request handler doesn't break the trace"() {
@ -318,6 +349,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address" "aws.endpoint" "$server.address"
"aws.operation" "GetObjectRequest" "aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
try { try {
errorTags AmazonClientException, ~/Unable to execute HTTP request/ errorTags AmazonClientException, ~/Unable to execute HTTP request/
} catch (AssertionError e) { } catch (AssertionError e) {

View File

@ -114,6 +114,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address" "aws.endpoint" "$server.address"
"aws.operation" "${operation}Request" "aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
} }
} }
span(1) { span(1) {
@ -126,7 +129,7 @@ class AWSClientTest extends AgentTestRunner {
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.PEER_HOSTNAME" "localhost" "$Tags.PEER_HOSTNAME" "localhost"
"$Tags.PEER_PORT" server.address.port "$Tags.PEER_PORT" server.address.port
"$Tags.HTTP_URL" "$server.address/$url" "$Tags.HTTP_URL" "${server.address}${path}"
"$Tags.HTTP_METHOD" "$method" "$Tags.HTTP_METHOD" "$method"
"$Tags.HTTP_STATUS" 200 "$Tags.HTTP_STATUS" 200
} }
@ -136,23 +139,23 @@ class AWSClientTest extends AgentTestRunner {
server.lastRequest.headers.get("traceparent") == null server.lastRequest.headers.get("traceparent") == null
where: where:
service | operation | method | url | handlerCount | call | body | client service | operation | method | path | handlerCount | client | additionalTags | call | body
"S3" | "CreateBucket" | "PUT" | "testbucket/" | 1 | { client -> client.setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build()); client.createBucket("testbucket") } | "" | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") "S3" | "CreateBucket" | "PUT" | "/testbucket/" | 1 | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") | ["aws.bucket.name": "testbucket"] | { client -> client.setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build()); client.createBucket("testbucket") } | ""
"S3" | "GetObject" | "GET" | "someBucket/someKey" | 1 | { client -> client.getObject("someBucket", "someKey") } | "" | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") "S3" | "GetObject" | "GET" | "/someBucket/someKey" | 1 | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") | ["aws.bucket.name": "someBucket"] | { client -> client.getObject("someBucket", "someKey") } | ""
"EC2" | "AllocateAddress" | "POST" | "" | 4 | { client -> client.allocateAddress() } | """ "EC2" | "AllocateAddress" | "POST" | "/" | 4 | new AmazonEC2Client().withEndpoint("http://localhost:$server.address.port") | [:] | { client -> client.allocateAddress() } | """
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"> <AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<publicIp>192.0.2.1</publicIp> <publicIp>192.0.2.1</publicIp>
<domain>standard</domain> <domain>standard</domain>
</AllocateAddressResponse> </AllocateAddressResponse>
""" | new AmazonEC2Client().withEndpoint("http://localhost:$server.address.port") """
"RDS" | "DeleteOptionGroup" | "POST" | "" | 1 | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | """ "RDS" | "DeleteOptionGroup" | "POST" | "/" | 1 | new AmazonRDSClient().withEndpoint("http://localhost:$server.address.port") | [:] | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | """
<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/"> <DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata> <ResponseMetadata>
<RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId> <RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId>
</ResponseMetadata> </ResponseMetadata>
</DeleteOptionGroupResponse> </DeleteOptionGroupResponse>
""" | new AmazonRDSClient().withEndpoint("http://localhost:$server.address.port") """
} }
def "send #operation request to closed port"() { def "send #operation request to closed port"() {
@ -183,6 +186,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:${UNUSABLE_PORT}" "aws.endpoint" "http://localhost:${UNUSABLE_PORT}"
"aws.operation" "${operation}Request" "aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
errorTags AmazonClientException, ~/Unable to execute HTTP request/ errorTags AmazonClientException, ~/Unable to execute HTTP request/
} }
} }
@ -205,8 +211,8 @@ class AWSClientTest extends AgentTestRunner {
} }
where: where:
service | operation | method | url | call | body | client service | operation | method | url | call | additionalTags | body | client
"S3" | "GetObject" | "GET" | "someBucket/someKey" | { client -> client.getObject("someBucket", "someKey") } | "" | new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN, new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))).withEndpoint("http://localhost:${UNUSABLE_PORT}") "S3" | "GetObject" | "GET" | "someBucket/someKey" | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | "" | new AmazonS3Client(CREDENTIALS_PROVIDER_CHAIN, new ClientConfiguration().withRetryPolicy(PredefinedRetryPolicies.getDefaultRetryPolicyWithCustomMaxRetries(0))).withEndpoint("http://localhost:${UNUSABLE_PORT}")
} }
def "naughty request handler doesn't break the trace"() { def "naughty request handler doesn't break the trace"() {
@ -243,6 +249,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "https://s3.amazonaws.com" "aws.endpoint" "https://s3.amazonaws.com"
"aws.operation" "GetObjectRequest" "aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
errorTags RuntimeException, "bad handler" errorTags RuntimeException, "bad handler"
} }
} }
@ -288,6 +295,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:$server.address.port" "aws.endpoint" "http://localhost:$server.address.port"
"aws.operation" "GetObjectRequest" "aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk" "aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
errorTags AmazonClientException, ~/Unable to execute HTTP request/ errorTags AmazonClientException, ~/Unable to execute HTTP request/
} }
} }

View File

@ -22,21 +22,21 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<SdkHttpRequest, S
request request
.getValueForField("Bucket", String.class) .getValueForField("Bucket", String.class)
.ifPresent(name -> span.setAttribute("aws.bucket.name", name)); .ifPresent(name -> span.setAttribute("aws.bucket.name", name));
// DynamoDB
request
.getValueForField("TableName", String.class)
.ifPresent(name -> span.setAttribute("aws.table.name", name));
// SQS // SQS
request
.getValueForField("QueueName", String.class)
.ifPresent(name -> span.setAttribute("aws.queue.name", name));
request request
.getValueForField("QueueUrl", String.class) .getValueForField("QueueUrl", String.class)
.ifPresent(name -> span.setAttribute("aws.queue.url", name)); .ifPresent(name -> span.setAttribute("aws.queue.url", name));
request
.getValueForField("QueueName", String.class)
.ifPresent(name -> span.setAttribute("aws.queue.name", name));
// Kinesis // Kinesis
request request
.getValueForField("StreamName", String.class) .getValueForField("StreamName", String.class)
.ifPresent(name -> span.setAttribute("aws.stream.name", name)); .ifPresent(name -> span.setAttribute("aws.stream.name", name));
// DynamoDB
request
.getValueForField("TableName", String.class)
.ifPresent(name -> span.setAttribute("aws.table.name", name));
return span; return span;
} }

View File

@ -2,6 +2,8 @@ package io.opentelemetry.auto.instrumentation.grpc.server;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.opentelemetry.auto.instrumentation.api.AgentPropagation; import io.opentelemetry.auto.instrumentation.api.AgentPropagation;
import java.util.ArrayList;
import java.util.List;
public final class GrpcExtractAdapter implements AgentPropagation.Getter<Metadata> { public final class GrpcExtractAdapter implements AgentPropagation.Getter<Metadata> {
@ -9,7 +11,15 @@ public final class GrpcExtractAdapter implements AgentPropagation.Getter<Metadat
@Override @Override
public Iterable<String> keys(final Metadata carrier) { public Iterable<String> keys(final Metadata carrier) {
return carrier.keys(); final List<String> keys = new ArrayList<>();
for (final String key : carrier.keys()) {
if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
keys.add(key);
}
}
return keys;
} }
@Override @Override

View File

@ -2,6 +2,7 @@ import example.GreeterGrpc
import example.Helloworld import example.Helloworld
import io.grpc.BindableService import io.grpc.BindableService
import io.grpc.ManagedChannel import io.grpc.ManagedChannel
import io.grpc.Metadata
import io.grpc.Server import io.grpc.Server
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
@ -11,6 +12,7 @@ import io.grpc.stub.StreamObserver
import io.opentelemetry.auto.api.MoreTags import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.api.SpanTypes import io.opentelemetry.auto.api.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.instrumentation.grpc.server.GrpcExtractAdapter
import io.opentelemetry.auto.test.AgentTestRunner import io.opentelemetry.auto.test.AgentTestRunner
import io.opentelemetry.sdk.trace.SpanData import io.opentelemetry.sdk.trace.SpanData
@ -268,4 +270,17 @@ class GrpcTest extends AgentTestRunner {
"Status - description" | Status.PERMISSION_DENIED.withDescription("some description") "Status - description" | Status.PERMISSION_DENIED.withDescription("some description")
"StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description") "StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description")
} }
def "skip binary headers"() {
setup:
def meta = new Metadata()
meta.put(Metadata.Key.<String> of("test", Metadata.ASCII_STRING_MARSHALLER), "val")
meta.put(Metadata.Key.<byte[]> of("test-bin", Metadata.BINARY_BYTE_MARSHALLER), "bin-val".bytes)
when:
def keys = GrpcExtractAdapter.GETTER.keys(meta)
then:
keys == ["test"]
}
} }

View File

@ -4,7 +4,11 @@ muzzle {
module = "jsr311-api" module = "jsr311-api"
versions = "[0.5,)" versions = "[0.5,)"
} }
// Muzzle doesn't detect the classLoaderMatcher, so we can't assert fail for v2 api. fail {
group = "javax.ws.rs"
module = "javax.ws.rs-api"
versions = "[,]"
}
} }
apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/java.gradle"

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.jdbc;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class DataSourceDecorator extends BaseDecorator {
public static final DataSourceDecorator DECORATE = new DataSourceDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"jdbc-datasource"};
}
@Override
protected String component() {
return "java-jdbc-connection";
}
@Override
protected String spanType() {
return null;
}
}

View File

@ -0,0 +1,81 @@
package io.opentelemetry.auto.instrumentation.jdbc;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activeSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.startSpan;
import static io.opentelemetry.auto.instrumentation.jdbc.DataSourceDecorator.DECORATE;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Map;
import javax.sql.DataSource;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class DataSourceInstrumentation extends Instrumenter.Default {
public DataSourceInstrumentation() {
super("jdbc-datasource");
}
@Override
public boolean defaultEnabled() {
return false;
}
@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.decorator.BaseDecorator", packageName + ".DataSourceDecorator",
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("javax.sql.DataSource")));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(named("getConnection"), GetConnectionAdvice.class.getName());
}
public static class GetConnectionAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope start(@Advice.This final DataSource ds) {
if (activeSpan() == null) {
// Don't want to generate a new top-level span
return null;
}
final AgentSpan span = startSpan("database.connection");
DECORATE.afterStart(span);
span.setAttribute(MoreTags.RESOURCE_NAME, ds.getClass().getSimpleName() + ".getConnection");
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
DECORATE.onError(scope, throwable);
DECORATE.beforeFinish(scope);
scope.close();
}
}
}

View File

@ -69,7 +69,12 @@ public class JDBCDecorator extends DatabaseClientDecorator<DBInfo> {
final DatabaseMetaData metaData = connection.getMetaData(); final DatabaseMetaData metaData = connection.getMetaData();
final String url = metaData.getURL(); final String url = metaData.getURL();
if (url != null) { if (url != null) {
dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo()); try {
dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo());
} catch (final Exception ex) {
// getClientInfo is likely not allowed.
dbInfo = JDBCConnectionUrlParser.parse(url, null);
}
} else { } else {
dbInfo = DBInfo.DEFAULT; dbInfo = DBInfo.DEFAULT;
} }

View File

@ -30,13 +30,16 @@ public abstract class JDBCUtils {
connection = connection.unwrap(Connection.class); connection = connection.unwrap(Connection.class);
} }
} catch (final Exception | AbstractMethodError e) { } catch (final Exception | AbstractMethodError e) {
// Attempt to work around c3po delegating to an connection that doesn't support unwrapping. if (connection != null) {
final Class<? extends Connection> connectionClass = connection.getClass(); // Attempt to work around c3po delegating to an connection that doesn't support
if (connectionClass.getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) { // unwrapping.
final Field inner = connectionClass.getDeclaredField("inner"); final Class<? extends Connection> connectionClass = connection.getClass();
inner.setAccessible(true); if (connectionClass.getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
c3poField = inner; final Field inner = connectionClass.getDeclaredField("inner");
return (Connection) c3poField.get(connection); inner.setAccessible(true);
c3poField = inner;
return (Connection) c3poField.get(connection);
}
} }
// perhaps wrapping isn't supported? // perhaps wrapping isn't supported?

View File

@ -6,11 +6,15 @@ import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.api.SpanTypes import io.opentelemetry.auto.api.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner import io.opentelemetry.auto.test.AgentTestRunner
import org.apache.derby.jdbc.EmbeddedDataSource
import org.apache.derby.jdbc.EmbeddedDriver import org.apache.derby.jdbc.EmbeddedDriver
import org.h2.Driver import org.h2.Driver
import org.h2.jdbcx.JdbcDataSource
import org.hsqldb.jdbc.JDBCDriver import org.hsqldb.jdbc.JDBCDriver
import spock.lang.Shared import spock.lang.Shared
import spock.lang.Unroll import spock.lang.Unroll
import test.TestConnection
import test.TestStatement
import javax.sql.DataSource import javax.sql.DataSource
import java.sql.CallableStatement import java.sql.CallableStatement
@ -24,6 +28,9 @@ import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
class JDBCInstrumentationTest extends AgentTestRunner { class JDBCInstrumentationTest extends AgentTestRunner {
static {
System.setProperty("opentelemetry.auto.integration.jdbc-datasource.enabled", "true")
}
@Shared @Shared
def dbName = "jdbcUnitTest" def dbName = "jdbcUnitTest"
@ -476,7 +483,6 @@ class JDBCInstrumentationTest extends AgentTestRunner {
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" "derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))"
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))"
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))"
} }
@Unroll @Unroll
@ -486,7 +492,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
when: when:
try { try {
connection = new DummyThrowingConnection() connection = new TestConnection(true)
} catch (Exception e) { } catch (Exception e) {
connection = driverClass.connect(url, null) connection = driverClass.connect(url, null)
} }
@ -550,6 +556,110 @@ class JDBCInstrumentationTest extends AgentTestRunner {
false | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" false | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
} }
def "calling #datasource.class.simpleName getConnection generates a span when under existing trace"() {
setup:
assert datasource instanceof DataSource
init?.call(datasource)
when:
datasource.getConnection().close()
then:
!TEST_WRITER.traces.any { it.any { it.name == "database.connection" } }
TEST_WRITER.clear()
when:
runUnderTrace("parent") {
datasource.getConnection().close()
}
then:
assertTraces(1) {
trace(0, recursive ? 3 : 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "database.connection"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "${datasource.class.simpleName}.getConnection"
"$Tags.COMPONENT" "java-jdbc-connection"
}
}
if (recursive) {
span(2) {
operationName "database.connection"
childOf span(1)
tags {
"$MoreTags.RESOURCE_NAME" "${datasource.class.simpleName}.getConnection"
"$Tags.COMPONENT" "java-jdbc-connection"
}
}
}
}
}
where:
datasource | init
new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) }
new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") }
cpDatasources.get("hikari").get("h2") | null
cpDatasources.get("hikari").get("derby") | null
cpDatasources.get("c3p0").get("h2") | null
cpDatasources.get("c3p0").get("derby") | null
// Tomcat's pool doesn't work because the getConnection method is
// implemented in a parent class that doesn't implement DataSource
recursive = datasource instanceof EmbeddedDataSource
}
def "test getClientInfo exception"() {
setup:
Connection connection = new TestConnection(false)
when:
Statement statement = null
runUnderTrace("parent") {
statement = connection.createStatement()
return statement.executeQuery(query)
}
then:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "database.query"
childOf span(0)
errored false
tags {
"$MoreTags.SERVICE_NAME" database
"$MoreTags.RESOURCE_NAME" query
"$MoreTags.SPAN_TYPE" SpanTypes.SQL
"$Tags.COMPONENT" "java-jdbc-statement"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.DB_TYPE" database
"$Tags.DB_STATEMENT" query
"span.origin.type" TestStatement.name
}
}
}
}
cleanup:
if (statement != null) {
statement.close()
}
if (connection != null) {
connection.close()
}
where:
database = "testdb"
query = "testing 123"
}
@Unroll @Unroll
def "#connectionPoolName connections should be cached in case of wrapped connections"() { def "#connectionPoolName connections should be cached in case of wrapped connections"() {
setup: setup:

View File

@ -1,3 +1,5 @@
package test
import java.sql.Array import java.sql.Array
import java.sql.Blob import java.sql.Blob
import java.sql.CallableStatement import java.sql.CallableStatement
@ -17,16 +19,19 @@ import java.util.concurrent.Executor
/** /**
* A JDBC connection class that throws an exception in the constructor, used to test * A JDBC connection class that optionally throws an exception in the constructor, used to test
*/ */
class DummyThrowingConnection implements Connection { class TestConnection implements Connection {
DummyThrowingConnection() { TestConnection(boolean throwException) {
throw new RuntimeException("Dummy exception") if (throwException) {
throw new RuntimeException("connection exception")
}
} }
@Override @Override
Statement createStatement() throws SQLException { Statement createStatement() throws SQLException {
return null return new TestStatement(this)
} }
@Override @Override
@ -76,7 +81,7 @@ class DummyThrowingConnection implements Connection {
@Override @Override
DatabaseMetaData getMetaData() throws SQLException { DatabaseMetaData getMetaData() throws SQLException {
return null return new TestDatabaseMetaData()
} }
@Override @Override
@ -241,12 +246,12 @@ class DummyThrowingConnection implements Connection {
@Override @Override
String getClientInfo(String name) throws SQLException { String getClientInfo(String name) throws SQLException {
return null throw new UnsupportedOperationException("Test 123")
} }
@Override @Override
Properties getClientInfo() throws SQLException { Properties getClientInfo() throws SQLException {
return null throw new UnsupportedOperationException("Test 123")
} }
@Override @Override

View File

@ -0,0 +1,889 @@
package test
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet
import java.sql.RowIdLifetime
import java.sql.SQLException
class TestDatabaseMetaData implements DatabaseMetaData {
@Override
boolean allProceduresAreCallable() throws SQLException {
return false
}
@Override
boolean allTablesAreSelectable() throws SQLException {
return false
}
@Override
String getURL() throws SQLException {
return "jdbc:testdb://localhost"
}
@Override
String getUserName() throws SQLException {
return null
}
@Override
boolean isReadOnly() throws SQLException {
return false
}
@Override
boolean nullsAreSortedHigh() throws SQLException {
return false
}
@Override
boolean nullsAreSortedLow() throws SQLException {
return false
}
@Override
boolean nullsAreSortedAtStart() throws SQLException {
return false
}
@Override
boolean nullsAreSortedAtEnd() throws SQLException {
return false
}
@Override
String getDatabaseProductName() throws SQLException {
return null
}
@Override
String getDatabaseProductVersion() throws SQLException {
return null
}
@Override
String getDriverName() throws SQLException {
return null
}
@Override
String getDriverVersion() throws SQLException {
return null
}
@Override
int getDriverMajorVersion() {
return 0
}
@Override
int getDriverMinorVersion() {
return 0
}
@Override
boolean usesLocalFiles() throws SQLException {
return false
}
@Override
boolean usesLocalFilePerTable() throws SQLException {
return false
}
@Override
boolean supportsMixedCaseIdentifiers() throws SQLException {
return false
}
@Override
boolean storesUpperCaseIdentifiers() throws SQLException {
return false
}
@Override
boolean storesLowerCaseIdentifiers() throws SQLException {
return false
}
@Override
boolean storesMixedCaseIdentifiers() throws SQLException {
return false
}
@Override
boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
return false
}
@Override
boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
return false
}
@Override
boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
return false
}
@Override
boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
return false
}
@Override
String getIdentifierQuoteString() throws SQLException {
return null
}
@Override
String getSQLKeywords() throws SQLException {
return null
}
@Override
String getNumericFunctions() throws SQLException {
return null
}
@Override
String getStringFunctions() throws SQLException {
return null
}
@Override
String getSystemFunctions() throws SQLException {
return null
}
@Override
String getTimeDateFunctions() throws SQLException {
return null
}
@Override
String getSearchStringEscape() throws SQLException {
return null
}
@Override
String getExtraNameCharacters() throws SQLException {
return null
}
@Override
boolean supportsAlterTableWithAddColumn() throws SQLException {
return false
}
@Override
boolean supportsAlterTableWithDropColumn() throws SQLException {
return false
}
@Override
boolean supportsColumnAliasing() throws SQLException {
return false
}
@Override
boolean nullPlusNonNullIsNull() throws SQLException {
return false
}
@Override
boolean supportsConvert() throws SQLException {
return false
}
@Override
boolean supportsConvert(int fromType, int toType) throws SQLException {
return false
}
@Override
boolean supportsTableCorrelationNames() throws SQLException {
return false
}
@Override
boolean supportsDifferentTableCorrelationNames() throws SQLException {
return false
}
@Override
boolean supportsExpressionsInOrderBy() throws SQLException {
return false
}
@Override
boolean supportsOrderByUnrelated() throws SQLException {
return false
}
@Override
boolean supportsGroupBy() throws SQLException {
return false
}
@Override
boolean supportsGroupByUnrelated() throws SQLException {
return false
}
@Override
boolean supportsGroupByBeyondSelect() throws SQLException {
return false
}
@Override
boolean supportsLikeEscapeClause() throws SQLException {
return false
}
@Override
boolean supportsMultipleResultSets() throws SQLException {
return false
}
@Override
boolean supportsMultipleTransactions() throws SQLException {
return false
}
@Override
boolean supportsNonNullableColumns() throws SQLException {
return false
}
@Override
boolean supportsMinimumSQLGrammar() throws SQLException {
return false
}
@Override
boolean supportsCoreSQLGrammar() throws SQLException {
return false
}
@Override
boolean supportsExtendedSQLGrammar() throws SQLException {
return false
}
@Override
boolean supportsANSI92EntryLevelSQL() throws SQLException {
return false
}
@Override
boolean supportsANSI92IntermediateSQL() throws SQLException {
return false
}
@Override
boolean supportsANSI92FullSQL() throws SQLException {
return false
}
@Override
boolean supportsIntegrityEnhancementFacility() throws SQLException {
return false
}
@Override
boolean supportsOuterJoins() throws SQLException {
return false
}
@Override
boolean supportsFullOuterJoins() throws SQLException {
return false
}
@Override
boolean supportsLimitedOuterJoins() throws SQLException {
return false
}
@Override
String getSchemaTerm() throws SQLException {
return null
}
@Override
String getProcedureTerm() throws SQLException {
return null
}
@Override
String getCatalogTerm() throws SQLException {
return null
}
@Override
boolean isCatalogAtStart() throws SQLException {
return false
}
@Override
String getCatalogSeparator() throws SQLException {
return null
}
@Override
boolean supportsSchemasInDataManipulation() throws SQLException {
return false
}
@Override
boolean supportsSchemasInProcedureCalls() throws SQLException {
return false
}
@Override
boolean supportsSchemasInTableDefinitions() throws SQLException {
return false
}
@Override
boolean supportsSchemasInIndexDefinitions() throws SQLException {
return false
}
@Override
boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
return false
}
@Override
boolean supportsCatalogsInDataManipulation() throws SQLException {
return false
}
@Override
boolean supportsCatalogsInProcedureCalls() throws SQLException {
return false
}
@Override
boolean supportsCatalogsInTableDefinitions() throws SQLException {
return false
}
@Override
boolean supportsCatalogsInIndexDefinitions() throws SQLException {
return false
}
@Override
boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
return false
}
@Override
boolean supportsPositionedDelete() throws SQLException {
return false
}
@Override
boolean supportsPositionedUpdate() throws SQLException {
return false
}
@Override
boolean supportsSelectForUpdate() throws SQLException {
return false
}
@Override
boolean supportsStoredProcedures() throws SQLException {
return false
}
@Override
boolean supportsSubqueriesInComparisons() throws SQLException {
return false
}
@Override
boolean supportsSubqueriesInExists() throws SQLException {
return false
}
@Override
boolean supportsSubqueriesInIns() throws SQLException {
return false
}
@Override
boolean supportsSubqueriesInQuantifieds() throws SQLException {
return false
}
@Override
boolean supportsCorrelatedSubqueries() throws SQLException {
return false
}
@Override
boolean supportsUnion() throws SQLException {
return false
}
@Override
boolean supportsUnionAll() throws SQLException {
return false
}
@Override
boolean supportsOpenCursorsAcrossCommit() throws SQLException {
return false
}
@Override
boolean supportsOpenCursorsAcrossRollback() throws SQLException {
return false
}
@Override
boolean supportsOpenStatementsAcrossCommit() throws SQLException {
return false
}
@Override
boolean supportsOpenStatementsAcrossRollback() throws SQLException {
return false
}
@Override
int getMaxBinaryLiteralLength() throws SQLException {
return 0
}
@Override
int getMaxCharLiteralLength() throws SQLException {
return 0
}
@Override
int getMaxColumnNameLength() throws SQLException {
return 0
}
@Override
int getMaxColumnsInGroupBy() throws SQLException {
return 0
}
@Override
int getMaxColumnsInIndex() throws SQLException {
return 0
}
@Override
int getMaxColumnsInOrderBy() throws SQLException {
return 0
}
@Override
int getMaxColumnsInSelect() throws SQLException {
return 0
}
@Override
int getMaxColumnsInTable() throws SQLException {
return 0
}
@Override
int getMaxConnections() throws SQLException {
return 0
}
@Override
int getMaxCursorNameLength() throws SQLException {
return 0
}
@Override
int getMaxIndexLength() throws SQLException {
return 0
}
@Override
int getMaxSchemaNameLength() throws SQLException {
return 0
}
@Override
int getMaxProcedureNameLength() throws SQLException {
return 0
}
@Override
int getMaxCatalogNameLength() throws SQLException {
return 0
}
@Override
int getMaxRowSize() throws SQLException {
return 0
}
@Override
boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
return false
}
@Override
int getMaxStatementLength() throws SQLException {
return 0
}
@Override
int getMaxStatements() throws SQLException {
return 0
}
@Override
int getMaxTableNameLength() throws SQLException {
return 0
}
@Override
int getMaxTablesInSelect() throws SQLException {
return 0
}
@Override
int getMaxUserNameLength() throws SQLException {
return 0
}
@Override
int getDefaultTransactionIsolation() throws SQLException {
return 0
}
@Override
boolean supportsTransactions() throws SQLException {
return false
}
@Override
boolean supportsTransactionIsolationLevel(int level) throws SQLException {
return false
}
@Override
boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
return false
}
@Override
boolean supportsDataManipulationTransactionsOnly() throws SQLException {
return false
}
@Override
boolean dataDefinitionCausesTransactionCommit() throws SQLException {
return false
}
@Override
boolean dataDefinitionIgnoredInTransactions() throws SQLException {
return false
}
@Override
ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException {
return null
}
@Override
ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException {
return null
}
@Override
ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException {
return null
}
@Override
ResultSet getSchemas() throws SQLException {
return null
}
@Override
ResultSet getCatalogs() throws SQLException {
return null
}
@Override
ResultSet getTableTypes() throws SQLException {
return null
}
@Override
ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
return null
}
@Override
ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException {
return null
}
@Override
ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
return null
}
@Override
ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException {
return null
}
@Override
ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
return null
}
@Override
ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
return null
}
@Override
ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
return null
}
@Override
ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
return null
}
@Override
ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
return null
}
@Override
ResultSet getTypeInfo() throws SQLException {
return null
}
@Override
ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
return null
}
@Override
boolean supportsResultSetType(int type) throws SQLException {
return false
}
@Override
boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
return false
}
@Override
boolean ownUpdatesAreVisible(int type) throws SQLException {
return false
}
@Override
boolean ownDeletesAreVisible(int type) throws SQLException {
return false
}
@Override
boolean ownInsertsAreVisible(int type) throws SQLException {
return false
}
@Override
boolean othersUpdatesAreVisible(int type) throws SQLException {
return false
}
@Override
boolean othersDeletesAreVisible(int type) throws SQLException {
return false
}
@Override
boolean othersInsertsAreVisible(int type) throws SQLException {
return false
}
@Override
boolean updatesAreDetected(int type) throws SQLException {
return false
}
@Override
boolean deletesAreDetected(int type) throws SQLException {
return false
}
@Override
boolean insertsAreDetected(int type) throws SQLException {
return false
}
@Override
boolean supportsBatchUpdates() throws SQLException {
return false
}
@Override
ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException {
return null
}
@Override
Connection getConnection() throws SQLException {
return null
}
@Override
boolean supportsSavepoints() throws SQLException {
return false
}
@Override
boolean supportsNamedParameters() throws SQLException {
return false
}
@Override
boolean supportsMultipleOpenResults() throws SQLException {
return false
}
@Override
boolean supportsGetGeneratedKeys() throws SQLException {
return false
}
@Override
ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
return null
}
@Override
ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
return null
}
@Override
ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException {
return null
}
@Override
boolean supportsResultSetHoldability(int holdability) throws SQLException {
return false
}
@Override
int getResultSetHoldability() throws SQLException {
return 0
}
@Override
int getDatabaseMajorVersion() throws SQLException {
return 0
}
@Override
int getDatabaseMinorVersion() throws SQLException {
return 0
}
@Override
int getJDBCMajorVersion() throws SQLException {
return 0
}
@Override
int getJDBCMinorVersion() throws SQLException {
return 0
}
@Override
int getSQLStateType() throws SQLException {
return 0
}
@Override
boolean locatorsUpdateCopy() throws SQLException {
return false
}
@Override
boolean supportsStatementPooling() throws SQLException {
return false
}
@Override
RowIdLifetime getRowIdLifetime() throws SQLException {
return null
}
@Override
ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
return null
}
@Override
boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
return false
}
@Override
boolean autoCommitFailureClosesAllResultSets() throws SQLException {
return false
}
@Override
ResultSet getClientInfoProperties() throws SQLException {
return null
}
@Override
ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException {
return null
}
@Override
ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException {
return null
}
@Override
ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
return null
}
@Override
boolean generatedKeyAlwaysReturned() throws SQLException {
return false
}
@Override
def <T> T unwrap(Class<T> iface) throws SQLException {
return null
}
@Override
boolean isWrapperFor(Class<?> iface) throws SQLException {
return false
}
}

View File

@ -0,0 +1,45 @@
package test
import java.sql.Connection
import java.sql.Driver
import java.sql.DriverPropertyInfo
import java.sql.SQLException
import java.sql.SQLFeatureNotSupportedException
import java.util.logging.Logger
class TestDriver implements Driver {
@Override
Connection connect(String url, Properties info) throws SQLException {
return new TestConnection("connectException=true" == url)
}
@Override
boolean acceptsURL(String url) throws SQLException {
return false
}
@Override
DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return new DriverPropertyInfo[0]
}
@Override
int getMajorVersion() {
return 0
}
@Override
int getMinorVersion() {
return 0
}
@Override
boolean jdbcCompliant() {
return false
}
@Override
Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null
}
}

View File

@ -0,0 +1,235 @@
package test
import java.sql.Connection
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.SQLWarning
import java.sql.Statement
class TestStatement implements Statement {
final Connection connection
TestStatement(Connection connection) {
this.connection = connection
}
@Override
ResultSet executeQuery(String sql) throws SQLException {
return null
}
@Override
int executeUpdate(String sql) throws SQLException {
return 0
}
@Override
void close() throws SQLException {
}
@Override
int getMaxFieldSize() throws SQLException {
return 0
}
@Override
void setMaxFieldSize(int max) throws SQLException {
}
@Override
int getMaxRows() throws SQLException {
return 0
}
@Override
void setMaxRows(int max) throws SQLException {
}
@Override
void setEscapeProcessing(boolean enable) throws SQLException {
}
@Override
int getQueryTimeout() throws SQLException {
return 0
}
@Override
void setQueryTimeout(int seconds) throws SQLException {
}
@Override
void cancel() throws SQLException {
}
@Override
SQLWarning getWarnings() throws SQLException {
return null
}
@Override
void clearWarnings() throws SQLException {
}
@Override
void setCursorName(String name) throws SQLException {
}
@Override
boolean execute(String sql) throws SQLException {
return false
}
@Override
ResultSet getResultSet() throws SQLException {
return null
}
@Override
int getUpdateCount() throws SQLException {
return 0
}
@Override
boolean getMoreResults() throws SQLException {
return false
}
@Override
void setFetchDirection(int direction) throws SQLException {
}
@Override
int getFetchDirection() throws SQLException {
return 0
}
@Override
void setFetchSize(int rows) throws SQLException {
}
@Override
int getFetchSize() throws SQLException {
return 0
}
@Override
int getResultSetConcurrency() throws SQLException {
return 0
}
@Override
int getResultSetType() throws SQLException {
return 0
}
@Override
void addBatch(String sql) throws SQLException {
}
@Override
void clearBatch() throws SQLException {
}
@Override
int[] executeBatch() throws SQLException {
return new int[0]
}
@Override
Connection getConnection() throws SQLException {
return connection
}
@Override
boolean getMoreResults(int current) throws SQLException {
return false
}
@Override
ResultSet getGeneratedKeys() throws SQLException {
return null
}
@Override
int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
return 0
}
@Override
int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
return 0
}
@Override
int executeUpdate(String sql, String[] columnNames) throws SQLException {
return 0
}
@Override
boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
return false
}
@Override
boolean execute(String sql, int[] columnIndexes) throws SQLException {
return false
}
@Override
boolean execute(String sql, String[] columnNames) throws SQLException {
return false
}
@Override
int getResultSetHoldability() throws SQLException {
return 0
}
@Override
boolean isClosed() throws SQLException {
return false
}
@Override
void setPoolable(boolean poolable) throws SQLException {
}
@Override
boolean isPoolable() throws SQLException {
return false
}
@Override
void closeOnCompletion() throws SQLException {
}
@Override
boolean isCloseOnCompletion() throws SQLException {
return false
}
@Override
def <T> T unwrap(Class<T> iface) throws SQLException {
return null
}
@Override
boolean isWrapperFor(Class<?> iface) throws SQLException {
return false
}
}

View File

@ -3,8 +3,8 @@ muzzle {
group = "redis.clients" group = "redis.clients"
module = "jedis" module = "jedis"
versions = "[1.4.0,3.0.0)" versions = "[1.4.0,3.0.0)"
assertInverse = true
} }
// Muzzle doesn't detect the classLoaderMatcher, so we can't assert fail for 3.0+
} }
apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/java.gradle"

View File

@ -584,4 +584,48 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"normal" | "compileError.jsp" | "compileError_jsp" | "" "normal" | "compileError.jsp" | "compileError_jsp" | ""
"forward" | "forwards/forwardWithCompileError.jsp" | "forwardWithCompileError_jsp" | "forwards." "forward" | "forwards/forwardWithCompileError.jsp" | "forwardWithCompileError_jsp" | "forwards."
} }
def "direct static file reference"() {
setup:
String reqUrl = baseUrl + "/$staticFile"
def req = new Request.Builder().url(new URL(reqUrl)).get().build()
when:
Response res = client.newCall(req).execute()
then:
res.code() == HttpStatus.OK_200
assertTraces(1) {
trace(0, 1) {
span(0) {
parent()
// serviceName jspWebappContext
operationName "servlet.request"
// FIXME: this is not a great resource name for serving static content.
// resourceName "GET /$jspWebappContext/$staticFile"
errored false
tags {
"$MoreTags.SPAN_TYPE" SpanTypes.HTTP_SERVER
"$Tags.COMPONENT" "java-web-servlet"
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
"$Tags.PEER_HOSTNAME" "127.0.0.1"
"$Tags.PEER_HOST_IPV4" "127.0.0.1"
"$Tags.PEER_PORT" Long
"$Tags.HTTP_URL" "http://localhost:$port/$jspWebappContext/$staticFile"
"$Tags.HTTP_METHOD" "GET"
"$Tags.HTTP_STATUS" 200
"span.origin.type" "org.apache.catalina.core.ApplicationFilterChain"
"servlet.context" "/$jspWebappContext"
"servlet.path" "/$staticFile"
}
}
}
}
cleanup:
res.close()
where:
staticFile = "common/hello.html"
}
} }

View File

@ -8,7 +8,7 @@ public class RmiClientDecorator extends ClientDecorator {
@Override @Override
protected String[] instrumentationNames() { protected String[] instrumentationNames() {
return new String[] {"rmi"}; return new String[] {"rmi", "rmi-client"};
} }
@Override @Override
@ -23,6 +23,6 @@ public class RmiClientDecorator extends ClientDecorator {
@Override @Override
protected String service() { protected String service() {
return "rmi"; return null;
} }
} }

View File

@ -8,7 +8,7 @@ public class RmiServerDecorator extends ServerDecorator {
@Override @Override
protected String[] instrumentationNames() { protected String[] instrumentationNames() {
return new String[] {"rmi"}; return new String[] {"rmi", "rmi-server"};
} }
@Override @Override

View File

@ -43,7 +43,6 @@ class RmiTest extends AgentTestRunner {
operationName "rmi.invoke" operationName "rmi.invoke"
childOf span(0) childOf span(0)
tags { tags {
"$MoreTags.SERVICE_NAME" "rmi"
"$MoreTags.RESOURCE_NAME" "Greeter.hello" "$MoreTags.RESOURCE_NAME" "Greeter.hello"
"$MoreTags.SPAN_TYPE" SpanTypes.RPC "$MoreTags.SPAN_TYPE" SpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
@ -118,7 +117,6 @@ class RmiTest extends AgentTestRunner {
childOf span(0) childOf span(0)
errored true errored true
tags { tags {
"$MoreTags.SERVICE_NAME" "rmi"
"$MoreTags.RESOURCE_NAME" "Greeter.exceptional" "$MoreTags.RESOURCE_NAME" "Greeter.exceptional"
"$MoreTags.SPAN_TYPE" SpanTypes.RPC "$MoreTags.SPAN_TYPE" SpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
@ -166,7 +164,6 @@ class RmiTest extends AgentTestRunner {
operationName "rmi.invoke" operationName "rmi.invoke"
childOf span(0) childOf span(0)
tags { tags {
"$MoreTags.SERVICE_NAME" "rmi"
"$MoreTags.RESOURCE_NAME" "Greeter.hello" "$MoreTags.RESOURCE_NAME" "Greeter.hello"
"$MoreTags.SPAN_TYPE" SpanTypes.RPC "$MoreTags.SPAN_TYPE" SpanTypes.RPC
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT

View File

@ -5,8 +5,12 @@ muzzle {
versions = "[2.3, 3.0)" versions = "[2.3, 3.0)"
assertInverse = true assertInverse = true
} }
// can't add a fail block for servlet 3, because servlet 3 is backward compatible with servlet 2.3+,
// meaning that for every class that exists in servlet 2, it also exists in servlet 3 fail {
group = "javax.servlet"
module = 'javax.servlet-api'
versions = "[3.0,)"
}
} }
apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/java.gradle"

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.auto.instrumentation.servlet2.HttpServletRequestE
import static io.opentelemetry.auto.instrumentation.servlet2.Servlet2Decorator.DECORATE; import static io.opentelemetry.auto.instrumentation.servlet2.Servlet2Decorator.DECORATE;
import io.opentelemetry.auto.api.MoreTags; import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.AgentScope; import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan; import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.instrumentation.api.Tags; import io.opentelemetry.auto.instrumentation.api.Tags;
@ -36,12 +37,16 @@ public class Servlet2Advice {
return null; return null;
} }
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (response instanceof HttpServletResponse) { if (response instanceof HttpServletResponse) {
// For use by HttpServletResponseInstrumentation:
InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class)
.put((HttpServletResponse) response, httpServletRequest);
response = new StatusSavingHttpServletResponseWrapper((HttpServletResponse) response); response = new StatusSavingHttpServletResponseWrapper((HttpServletResponse) response);
} }
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final AgentSpan.Context extractedContext = propagate().extract(httpServletRequest, GETTER); final AgentSpan.Context extractedContext = propagate().extract(httpServletRequest, GETTER);
final AgentSpan span = final AgentSpan span =

View File

@ -49,6 +49,12 @@ public final class Servlet2Instrumentation extends Instrumenter.Default {
named("javax.servlet.FilterChain").or(named("javax.servlet.http.HttpServlet")))); named("javax.servlet.FilterChain").or(named("javax.servlet.http.HttpServlet"))));
} }
@Override
public Map<String, String> contextStore() {
return singletonMap(
"javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest");
}
/** /**
* Here we are instrumenting the public method for HttpServlet. This should ensure that this * Here we are instrumenting the public method for HttpServlet. This should ensure that this
* advice is always called before HttpServletInstrumentation which is instrumenting the protected * advice is always called before HttpServletInstrumentation which is instrumenting the protected

View File

@ -1,7 +1,6 @@
package io.opentelemetry.auto.instrumentation.servlet3; package io.opentelemetry.auto.instrumentation.servlet3;
import io.opentelemetry.auto.instrumentation.api.AgentPropagation; import io.opentelemetry.auto.instrumentation.api.AgentPropagation;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -14,7 +13,7 @@ public class HttpServletRequestExtractAdapter
@Override @Override
public List<String> keys(final HttpServletRequest carrier) { public List<String> keys(final HttpServletRequest carrier) {
final ArrayList<String> keys = Collections.list(carrier.getHeaderNames()); final List<String> keys = Collections.list(carrier.getHeaderNames());
keys.addAll(Collections.list(carrier.getAttributeNames())); keys.addAll(Collections.list(carrier.getAttributeNames()));
return keys; return keys;
} }

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.auto.instrumentation.servlet3.HttpServletRequestE
import static io.opentelemetry.auto.instrumentation.servlet3.Servlet3Decorator.DECORATE; import static io.opentelemetry.auto.instrumentation.servlet3.Servlet3Decorator.DECORATE;
import io.opentelemetry.auto.api.MoreTags; import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.AgentScope; import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan; import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.instrumentation.api.Tags; import io.opentelemetry.auto.instrumentation.api.Tags;
@ -24,7 +25,10 @@ public class Servlet3Advice {
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope onEnter( public static AgentScope onEnter(
@Advice.This final Object servlet, @Advice.Argument(0) final ServletRequest request) { @Advice.This final Object servlet,
@Advice.Argument(0) final ServletRequest request,
@Advice.Argument(1) final ServletResponse response) {
final boolean hasActiveTrace = activeSpan() != null; final boolean hasActiveTrace = activeSpan() != null;
final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof AgentSpan; final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof AgentSpan;
final boolean invalidRequest = !(request instanceof HttpServletRequest); final boolean invalidRequest = !(request instanceof HttpServletRequest);
@ -35,6 +39,10 @@ public class Servlet3Advice {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request; final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// For use by HttpServletResponseInstrumentation:
InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class)
.put((HttpServletResponse) response, httpServletRequest);
final AgentSpan.Context extractedContext = propagate().extract(httpServletRequest, GETTER); final AgentSpan.Context extractedContext = propagate().extract(httpServletRequest, GETTER);
final AgentSpan span = final AgentSpan span =

View File

@ -41,6 +41,12 @@ public final class Servlet3Instrumentation extends Instrumenter.Default {
named("javax.servlet.FilterChain").or(named("javax.servlet.http.HttpServlet")))); named("javax.servlet.FilterChain").or(named("javax.servlet.http.HttpServlet"))));
} }
@Override
public Map<String, String> contextStore() {
return singletonMap(
"javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest");
}
/** /**
* Here we are instrumenting the public method for HttpServlet. This should ensure that this * Here we are instrumenting the public method for HttpServlet. This should ensure that this
* advice is always called before HttpServletInstrumentation which is instrumenting the protected * advice is always called before HttpServletInstrumentation which is instrumenting the protected

View File

@ -1 +1,29 @@
muzzle {
pass {
group = "javax.servlet"
module = 'javax.servlet-api'
versions = "[,]"
assertInverse = true
}
pass {
group = "javax.servlet"
module = 'servlet-api'
versions = "[,]"
}
}
apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/java.gradle"
dependencies {
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
testCompile group: 'javax.servlet', name: 'servlet-api', version: '2.3'
// servlet request instrumentation required for linking request to response.
testCompile project(':java-agent:instrumentation:servlet:request-2')
// Don't want to conflict with jetty from the test server.
testCompile(project(':java-agent:testing')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
}
}

View File

@ -0,0 +1,14 @@
package io.opentelemetry.auto.instrumentation.servlet;
import io.opentelemetry.auto.instrumentation.api.AgentPropagation;
import javax.servlet.ServletRequest;
/** Inject into request attributes since the request headers can't be modified. */
public class ServletRequestSetter implements AgentPropagation.Setter<ServletRequest> {
public static final ServletRequestSetter SETTER = new ServletRequestSetter();
@Override
public void set(final ServletRequest carrier, final String key, final String value) {
carrier.setAttribute(key, value);
}
}

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.servlet.dispatcher;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class RequestDispatcherDecorator extends BaseDecorator {
public static final RequestDispatcherDecorator DECORATE = new RequestDispatcherDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"servlet", "servlet-dispatcher"};
}
@Override
protected String spanType() {
return null;
}
@Override
protected String component() {
return "java-web-servlet-dispatcher";
}
}

View File

@ -0,0 +1,113 @@
package io.opentelemetry.auto.instrumentation.servlet.dispatcher;
import static io.opentelemetry.auto.decorator.HttpServerDecorator.SPAN_ATTRIBUTE;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activeSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.propagate;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.startSpan;
import static io.opentelemetry.auto.instrumentation.servlet.ServletRequestSetter.SETTER;
import static io.opentelemetry.auto.instrumentation.servlet.dispatcher.RequestDispatcherDecorator.DECORATE;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class RequestDispatcherInstrumentation extends Instrumenter.Default {
public RequestDispatcherInstrumentation() {
super("servlet", "servlet-dispatcher");
}
@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.instrumentation.servlet.ServletRequestSetter",
"io.opentelemetry.auto.decorator.BaseDecorator",
packageName + ".RequestDispatcherDecorator",
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("javax.servlet.RequestDispatcher")));
}
@Override
public Map<String, String> contextStore() {
return singletonMap("javax.servlet.RequestDispatcher", String.class.getName());
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("forward")
.or(named("include"))
.and(takesArgument(0, named("javax.servlet.ServletRequest")))
.and(takesArgument(1, named("javax.servlet.ServletResponse")))
.and(isPublic()),
RequestDispatcherAdvice.class.getName());
}
public static class RequestDispatcherAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope start(
@Advice.Origin("#m") final String method,
@Advice.This final RequestDispatcher dispatcher,
@Advice.Argument(0) final ServletRequest request) {
if (activeSpan() == null) {
// Don't want to generate a new top-level span
return null;
}
final AgentSpan span = startSpan("servlet." + method);
DECORATE.afterStart(span);
final String target =
InstrumentationContext.get(RequestDispatcher.class, String.class).get(dispatcher);
span.setAttribute(MoreTags.RESOURCE_NAME, target);
// In case we lose context, inject trace into to the request.
propagate().inject(span, request, SETTER);
// temporarily remove from request to avoid spring resource name bubbling up:
request.removeAttribute(SPAN_ATTRIBUTE);
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stop(
@Advice.Enter final AgentScope scope,
@Advice.Argument(0) final ServletRequest request,
@Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
// now add it back...
request.setAttribute(SPAN_ATTRIBUTE, scope.span());
DECORATE.onError(scope, throwable);
DECORATE.beforeFinish(scope);
scope.close();
}
}
}

View File

@ -0,0 +1,57 @@
package io.opentelemetry.auto.instrumentation.servlet.dispatcher;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class ServletContextInstrumentation extends Instrumenter.Default {
public ServletContextInstrumentation() {
super("servlet", "servlet-dispatcher");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("javax.servlet.ServletContext")));
}
@Override
public Map<String, String> contextStore() {
return singletonMap("javax.servlet.RequestDispatcher", String.class.getName());
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
returns(named("javax.servlet.RequestDispatcher"))
.and(takesArgument(0, String.class))
// javax.servlet.ServletContext.getRequestDispatcher
// javax.servlet.ServletContext.getNamedDispatcher
.and(isPublic()),
RequestDispatcherTargetAdvice.class.getName());
}
public static class RequestDispatcherTargetAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void saveTarget(
@Advice.Argument(0) final String target,
@Advice.Return final RequestDispatcher dispatcher) {
InstrumentationContext.get(RequestDispatcher.class, String.class).put(dispatcher, target);
}
}
}

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.servlet.filter;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class FilterDecorator extends BaseDecorator {
public static final FilterDecorator DECORATE = new FilterDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"servlet-filter"};
}
@Override
protected String spanType() {
return null;
}
@Override
protected String component() {
return "java-web-servlet-filter";
}
}

View File

@ -0,0 +1,88 @@
package io.opentelemetry.auto.instrumentation.servlet.filter;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activeSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.startSpan;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Map;
import javax.servlet.Filter;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class FilterInstrumentation extends Instrumenter.Default {
public FilterInstrumentation() {
super("servlet-filter");
}
@Override
public boolean defaultEnabled() {
return false;
}
@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.decorator.BaseDecorator", packageName + ".FilterDecorator",
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("javax.servlet.Filter")));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("doFilter")
.and(takesArgument(0, named("javax.servlet.ServletRequest")))
.and(takesArgument(1, named("javax.servlet.ServletResponse")))
.and(isPublic()),
FilterAdvice.class.getName());
}
public static class FilterAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope start(@Advice.This final Filter filter) {
if (activeSpan() == null) {
// Don't want to generate a new top-level span
return null;
}
final AgentSpan span = startSpan("servlet.filter");
FilterDecorator.DECORATE.afterStart(span);
// Here we use "this" instead of "the method target" to distinguish abstract filter instances.
span.setAttribute(MoreTags.RESOURCE_NAME, filter.getClass().getSimpleName() + ".doFilter");
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
FilterDecorator.DECORATE.onError(scope, throwable);
FilterDecorator.DECORATE.beforeFinish(scope);
scope.close();
}
}
}

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.servlet.http;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class HttpServletDecorator extends BaseDecorator {
public static final HttpServletDecorator DECORATE = new HttpServletDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"servlet-service"};
}
@Override
protected String spanType() {
return null;
}
@Override
protected String component() {
return "java-web-servlet-service";
}
}

View File

@ -0,0 +1,97 @@
package io.opentelemetry.auto.instrumentation.servlet.http;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activeSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.startSpan;
import static io.opentelemetry.auto.instrumentation.servlet.http.HttpServletDecorator.DECORATE;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isProtected;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.lang.reflect.Method;
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;
@AutoService(Instrumenter.class)
public final class HttpServletInstrumentation extends Instrumenter.Default {
public HttpServletInstrumentation() {
super("servlet-service");
}
@Override
public boolean defaultEnabled() {
return false;
}
@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.decorator.BaseDecorator", packageName + ".HttpServletDecorator",
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("javax.servlet.http.HttpServlet")));
}
/**
* Here we are instrumenting the protected method for HttpServlet. This should ensure that this
* advice is always called after Servlet3Instrumentation which is instrumenting the public method.
*/
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("service")
.or(nameStartsWith("do")) // doGet, doPost, etc
.and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
.and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
.and(isProtected().or(isPublic())),
HttpServletAdvice.class.getName());
}
public static class HttpServletAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope start(@Advice.Origin final Method method) {
if (activeSpan() == null) {
// Don't want to generate a new top-level span
return null;
}
final AgentSpan span = startSpan("servlet." + method.getName());
DECORATE.afterStart(span);
// Here we use the Method instead of "this.class.name" to distinguish calls to "super".
span.setAttribute(MoreTags.RESOURCE_NAME, DECORATE.spanNameForMethod(method));
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
DECORATE.onError(scope, throwable);
DECORATE.beforeFinish(scope);
scope.close();
}
}
}

View File

@ -0,0 +1,22 @@
package io.opentelemetry.auto.instrumentation.servlet.http;
import io.opentelemetry.auto.decorator.BaseDecorator;
public class HttpServletResponseDecorator extends BaseDecorator {
public static final HttpServletResponseDecorator DECORATE = new HttpServletResponseDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"servlet", "servlet-response"};
}
@Override
protected String spanType() {
return null;
}
@Override
protected String component() {
return "java-web-servlet-response";
}
}

View File

@ -0,0 +1,99 @@
package io.opentelemetry.auto.instrumentation.servlet.http;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.activeSpan;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.propagate;
import static io.opentelemetry.auto.instrumentation.api.AgentTracer.startSpan;
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.AgentScope;
import io.opentelemetry.auto.instrumentation.api.AgentSpan;
import io.opentelemetry.auto.instrumentation.servlet.ServletRequestSetter;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class HttpServletResponseInstrumentation extends Instrumenter.Default {
public HttpServletResponseInstrumentation() {
super("servlet", "servlet-response");
}
@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.instrumentation.servlet.ServletRequestSetter",
"io.opentelemetry.auto.decorator.BaseDecorator",
packageName + ".HttpServletResponseDecorator",
};
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface())
.and(safeHasSuperType(named("javax.servlet.http.HttpServletResponse")));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(named("sendError").or(named("sendRedirect")), SendAdvice.class.getName());
}
@Override
public Map<String, String> contextStore() {
return singletonMap(
"javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest");
}
public static class SendAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope start(
@Advice.Origin("#m") final String method, @Advice.This final HttpServletResponse resp) {
if (activeSpan() == null) {
// Don't want to generate a new top-level span
return null;
}
final HttpServletRequest req =
InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class).get(resp);
if (req == null) {
// Missing the response->request linking... probably in a wrapped instance.
return null;
}
final AgentSpan span = startSpan("servlet.response");
HttpServletResponseDecorator.DECORATE.afterStart(span);
span.setAttribute(MoreTags.RESOURCE_NAME, "HttpServletResponse." + method);
// In case we lose context, inject trace into to the request.
propagate().inject(span, req, ServletRequestSetter.SETTER);
return activateSpan(span, true);
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
HttpServletResponseDecorator.DECORATE.onError(scope, throwable);
HttpServletResponseDecorator.DECORATE.beforeFinish(scope);
scope.close();
}
}
}

View File

@ -0,0 +1,106 @@
import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
class FilterTest extends AgentTestRunner {
static {
System.setProperty("opentelemetry.auto.integration.servlet-filter.enabled", "true")
}
def "test doFilter no-parent"() {
when:
filter.doFilter(null, null, null)
then:
assertTraces(0) {}
where:
filter = new TestFilter()
}
def "test doFilter with parent"() {
when:
runUnderTrace("parent") {
filter.doFilter(null, null, null)
}
then:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "servlet.filter"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "${filter.class.simpleName}.doFilter"
"$Tags.COMPONENT" "java-web-servlet-filter"
}
}
}
}
where:
filter << [new TestFilter(), new TestFilter() {}]
}
def "test doFilter exception"() {
setup:
def ex = new Exception("some error")
def filter = new TestFilter() {
@Override
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
throw ex
}
}
when:
runUnderTrace("parent") {
filter.doFilter(null, null, null)
}
then:
def th = thrown(Exception)
th == ex
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", null, null, ex)
span(1) {
operationName "servlet.filter"
childOf span(0)
errored true
tags {
"$MoreTags.RESOURCE_NAME" "${filter.class.simpleName}.doFilter"
"$Tags.COMPONENT" "java-web-servlet-filter"
errorTags(ex.class, ex.message)
}
}
}
}
}
static class TestFilter implements Filter {
@Override
void init(FilterConfig filterConfig) throws ServletException {
}
@Override
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
void destroy() {
}
}
}

View File

@ -0,0 +1,280 @@
import groovy.servlet.AbstractHttpServlet
import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner
import spock.lang.Subject
import javax.servlet.ServletOutputStream
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.Cookie
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
import static java.util.Collections.emptyEnumeration
class HttpServletResponseTest extends AgentTestRunner {
@Subject
def response = new TestResponse()
def request = Mock(HttpServletRequest) {
getMethod() >> "GET"
getProtocol() >> "TEST"
getHeaderNames() >> emptyEnumeration()
getAttributeNames() >> emptyEnumeration()
}
def setup() {
def servlet = new AbstractHttpServlet() {}
// We need to call service so HttpServletAdvice can link the request to the response.
servlet.service((ServletRequest) request, (ServletResponse) response)
assert response.__opentelemetryContext$javax$servlet$http$HttpServletResponse != null
TEST_WRITER.clear()
}
def "test send no-parent"() {
when:
response.sendError(0)
response.sendError(0, "")
response.sendRedirect("")
then:
assertTraces(0) {}
}
def "test send with parent"() {
when:
runUnderTrace("parent") {
response.sendError(0)
response.sendError(0, "")
response.sendRedirect("")
}
then:
assertTraces(1) {
trace(0, 4) {
basicSpan(it, 0, "parent")
span(1) {
operationName "servlet.response"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "HttpServletResponse.sendError"
"$Tags.COMPONENT" "java-web-servlet-response"
}
}
span(2) {
operationName "servlet.response"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "HttpServletResponse.sendError"
"$Tags.COMPONENT" "java-web-servlet-response"
}
}
span(3) {
operationName "servlet.response"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "HttpServletResponse.sendRedirect"
"$Tags.COMPONENT" "java-web-servlet-response"
}
}
}
}
}
def "test send with exception"() {
setup:
def ex = new Exception("some error")
def response = new TestResponse() {
@Override
void sendRedirect(String s) {
throw ex
}
}
def servlet = new AbstractHttpServlet() {}
// We need to call service so HttpServletAdvice can link the request to the response.
servlet.service((ServletRequest) request, (ServletResponse) response)
assert response.__opentelemetryContext$javax$servlet$http$HttpServletResponse != null
TEST_WRITER.clear()
when:
runUnderTrace("parent") {
response.sendRedirect("")
}
then:
def th = thrown(Exception)
th == ex
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", null, null, ex)
span(1) {
operationName "servlet.response"
childOf span(0)
errored true
tags {
"$MoreTags.RESOURCE_NAME" "HttpServletResponse.sendRedirect"
"$Tags.COMPONENT" "java-web-servlet-response"
errorTags(ex.class, ex.message)
}
}
}
}
}
static class TestResponse implements HttpServletResponse {
@Override
void addCookie(Cookie cookie) {
}
@Override
boolean containsHeader(String s) {
return false
}
@Override
String encodeURL(String s) {
return null
}
@Override
String encodeRedirectURL(String s) {
return null
}
@Override
String encodeUrl(String s) {
return null
}
@Override
String encodeRedirectUrl(String s) {
return null
}
@Override
void sendError(int i, String s) throws IOException {
}
@Override
void sendError(int i) throws IOException {
}
@Override
void sendRedirect(String s) throws IOException {
}
@Override
void setDateHeader(String s, long l) {
}
@Override
void addDateHeader(String s, long l) {
}
@Override
void setHeader(String s, String s1) {
}
@Override
void addHeader(String s, String s1) {
}
@Override
void setIntHeader(String s, int i) {
}
@Override
void addIntHeader(String s, int i) {
}
@Override
void setStatus(int i) {
}
@Override
void setStatus(int i, String s) {
}
@Override
String getCharacterEncoding() {
return null
}
@Override
ServletOutputStream getOutputStream() throws IOException {
return null
}
@Override
PrintWriter getWriter() throws IOException {
return null
}
@Override
void setContentLength(int i) {
}
@Override
void setContentType(String s) {
}
@Override
void setBufferSize(int i) {
}
@Override
int getBufferSize() {
return 0
}
@Override
void flushBuffer() throws IOException {
}
@Override
void resetBuffer() {
}
@Override
boolean isCommitted() {
return false
}
@Override
void reset() {
}
@Override
void setLocale(Locale locale) {
}
@Override
Locale getLocale() {
return null
}
}
}

View File

@ -0,0 +1,124 @@
import groovy.servlet.AbstractHttpServlet
import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
class HttpServletTest extends AgentTestRunner {
static {
System.setProperty("opentelemetry.auto.integration.servlet-service.enabled", "true")
}
def req = Mock(HttpServletRequest) {
getMethod() >> "GET"
getProtocol() >> "TEST"
}
def resp = Mock(HttpServletResponse)
def "test service no-parent"() {
when:
servlet.service(req, resp)
then:
assertTraces(0) {}
where:
servlet = new TestServlet()
}
def "test service with parent"() {
when:
runUnderTrace("parent") {
servlet.service(req, resp)
}
then:
assertTraces(1) {
trace(0, 3) {
basicSpan(it, 0, "parent")
span(1) {
operationName "servlet.service"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" "HttpServlet.service"
"$Tags.COMPONENT" "java-web-servlet-service"
}
}
span(2) {
operationName "servlet.doGet"
childOf span(1)
tags {
"$MoreTags.RESOURCE_NAME" "${expectedResourceName}.doGet"
"$Tags.COMPONENT" "java-web-servlet-service"
}
}
}
}
where:
servlet << [new TestServlet(), new TestServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
}
}]
expectedResourceName = servlet.class.anonymousClass ? servlet.class.name : servlet.class.simpleName
}
def "test service exception"() {
setup:
def ex = new Exception("some error")
def servlet = new TestServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
throw ex
}
}
when:
runUnderTrace("parent") {
servlet.service(req, resp)
}
then:
def th = thrown(Exception)
th == ex
assertTraces(1) {
trace(0, 3) {
basicSpan(it, 0, "parent", null, null, ex)
span(1) {
operationName "servlet.service"
childOf span(0)
errored true
tags {
"$MoreTags.RESOURCE_NAME" "HttpServlet.service"
"$Tags.COMPONENT" "java-web-servlet-service"
errorTags(ex.class, ex.message)
}
}
span(2) {
operationName "servlet.doGet"
childOf span(1)
errored true
tags {
"$MoreTags.RESOURCE_NAME" "${servlet.class.name}.doGet"
"$Tags.COMPONENT" "java-web-servlet-service"
errorTags(ex.class, ex.message)
}
}
}
}
}
static class TestServlet extends AbstractHttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
}
}
}

View File

@ -0,0 +1,95 @@
import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
class RequestDispatcherTest extends AgentTestRunner {
def dispatcher = new RequestDispatcherUtils(Mock(HttpServletRequest), Mock(HttpServletResponse))
def "test dispatch no-parent"() {
when:
dispatcher.forward("")
dispatcher.include("")
then:
assertTraces(0) {}
}
def "test dispatcher #method with parent"() {
when:
runUnderTrace("parent") {
dispatcher."$method"(target)
}
then:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent")
span(1) {
operationName "servlet.$operation"
childOf span(0)
tags {
"$MoreTags.RESOURCE_NAME" target
"$Tags.COMPONENT" "java-web-servlet-dispatcher"
}
}
}
}
where:
operation | method
"forward" | "forward"
"forward" | "forwardNamed"
"include" | "include"
"include" | "includeNamed"
target = "test-$method"
}
def "test dispatcher #method exception"() {
setup:
def ex = new ServletException("some error")
def dispatcher = new RequestDispatcherUtils(Mock(HttpServletRequest), Mock(HttpServletResponse), ex)
when:
runUnderTrace("parent") {
dispatcher."$method"(target)
}
then:
def th = thrown(ServletException)
th == ex
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", null, null, ex)
span(1) {
operationName "servlet.$operation"
childOf span(0)
errored true
tags {
"$MoreTags.RESOURCE_NAME" target
"$Tags.COMPONENT" "java-web-servlet-dispatcher"
errorTags(ex.class, ex.message)
}
}
}
}
where:
operation | method
"forward" | "forward"
"forward" | "forwardNamed"
"include" | "include"
"include" | "includeNamed"
target = "test-$method"
}
}

View File

@ -0,0 +1,181 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Set;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class RequestDispatcherUtils {
private final ServletRequest req;
private final ServletResponse resp;
private final ServletException toThrow;
public RequestDispatcherUtils(final ServletRequest req, final ServletResponse resp) {
this.req = req;
this.resp = resp;
toThrow = null;
}
public RequestDispatcherUtils(
final ServletRequest req, final ServletResponse resp, final ServletException toThrow) {
this.req = req;
this.resp = resp;
this.toThrow = toThrow;
}
/* RequestDispatcher can't be visible to groovy otherwise things break, so everything is
* encapsulated in here where groovy doesn't need to access it.
*/
void forward(final String target) throws ServletException, IOException {
new TestContext().getRequestDispatcher(target).forward(req, resp);
}
void include(final String target) throws ServletException, IOException {
new TestContext().getRequestDispatcher(target).include(req, resp);
}
void forwardNamed(final String target) throws ServletException, IOException {
new TestContext().getNamedDispatcher(target).forward(req, resp);
}
void includeNamed(final String target) throws ServletException, IOException {
new TestContext().getNamedDispatcher(target).include(req, resp);
}
class TestContext implements ServletContext {
@Override
public ServletContext getContext(final String s) {
return null;
}
@Override
public int getMajorVersion() {
return 0;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public String getMimeType(final String s) {
return null;
}
@Override
public Set getResourcePaths(final String s) {
return null;
}
@Override
public URL getResource(final String s) throws MalformedURLException {
return null;
}
@Override
public InputStream getResourceAsStream(final String s) {
return null;
}
@Override
public RequestDispatcher getRequestDispatcher(final String s) {
return new TestDispatcher();
}
@Override
public RequestDispatcher getNamedDispatcher(final String s) {
return new TestDispatcher();
}
@Override
public Servlet getServlet(final String s) throws ServletException {
return null;
}
@Override
public Enumeration getServlets() {
return null;
}
@Override
public Enumeration getServletNames() {
return null;
}
@Override
public void log(final String s) {}
@Override
public void log(final Exception e, final String s) {}
@Override
public void log(final String s, final Throwable throwable) {}
@Override
public String getRealPath(final String s) {
return null;
}
@Override
public String getServerInfo() {
return null;
}
@Override
public String getInitParameter(final String s) {
return null;
}
@Override
public Enumeration getInitParameterNames() {
return null;
}
@Override
public Object getAttribute(final String s) {
return null;
}
@Override
public Enumeration getAttributeNames() {
return null;
}
@Override
public void setAttribute(final String s, final Object o) {}
@Override
public void removeAttribute(final String s) {}
@Override
public String getServletContextName() {
return null;
}
}
class TestDispatcher implements RequestDispatcher {
@Override
public void forward(final ServletRequest servletRequest, final ServletResponse servletResponse)
throws ServletException, IOException {
if (toThrow != null) {
throw toThrow;
}
}
@Override
public void include(final ServletRequest servletRequest, final ServletResponse servletResponse)
throws ServletException, IOException {
if (toThrow != null) {
throw toThrow;
}
}
}
}