Merge pull request #64 from trask/dd-merge

Merge changes from dd-trace-java 0.41.0
This commit is contained in:
Tyler Benson 2020-01-16 16:09:50 -08:00 committed by GitHub
commit 1d474353a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 3256 additions and 103 deletions

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View File

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

View File

@ -53,8 +53,11 @@ public class MuzzleVersionScanPlugin {
final ReferenceMatcher muzzle = (ReferenceMatcher) m.invoke(instrumenter);
final List<Reference.Mismatch> mismatches =
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) {
System.err.println(
"MUZZLE PASSED "
@ -64,6 +67,11 @@ public class MuzzleVersionScanPlugin {
} else if (!passed && assertPass) {
System.err.println(
"FAILED MUZZLE VALIDATION: " + instrumenter.getClass().getName() + " mismatches:");
if (!classLoaderMatch) {
System.err.println("-- classloader mismatch");
}
for (final Reference.Mismatch mismatch : mismatches) {
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-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-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') {
force = true
@ -52,10 +58,22 @@ dependencies {
test_before_1_11_106Compile(group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.0') {
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-rds', 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

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.named;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.handlers.RequestHandler2;
import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.List;
import java.util.Map;
@ -39,6 +41,7 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
"io.opentelemetry.auto.decorator.ClientDecorator",
"io.opentelemetry.auto.decorator.HttpClientDecorator",
packageName + ".AwsSdkClientDecorator",
packageName + ".RequestMeta",
packageName + ".TracingRequestHandler",
};
}
@ -49,6 +52,11 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
isConstructor(), AWSClientInstrumentation.class.getName() + "$AWSClientAdvice");
}
@Override
public Map<String, String> contextStore() {
return singletonMap("com.amazonaws.AmazonWebServiceRequest", packageName + ".RequestMeta");
}
public static class AWSClientAdvice {
// Since we're instrumenting the constructor, we can't add onThrowable.
@Advice.OnMethodExit(suppress = Throwable.class)
@ -62,7 +70,9 @@ public final class AWSClientInstrumentation extends Instrumenter.Default {
}
}
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;
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 net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
@ -41,10 +42,8 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.auto.decorator.BaseDecorator",
"io.opentelemetry.auto.decorator.ClientDecorator",
"io.opentelemetry.auto.decorator.HttpClientDecorator",
packageName + ".AwsSdkClientDecorator",
packageName + ".TracingRequestHandler",
packageName + ".OnErrorDecorator",
packageName + ".RequestMeta",
};
}
@ -61,10 +60,9 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
@Advice.Argument(value = 0, optional = true) final Request<?> request,
@Advice.Thrown final Throwable throwable) {
if (throwable != null) {
final SpanScopePair spanScopePair =
request.getHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY);
final SpanScopePair spanScopePair = request.getHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY);
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();
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
@ -101,9 +99,9 @@ public class AWSHttpClientInstrumentation extends Instrumenter.Default {
@Advice.Thrown final Throwable throwable) {
if (throwable != null) {
final SpanScopePair spanScopePair =
request.getHandlerContext(TracingRequestHandler.SPAN_SCOPE_PAIR_CONTEXT_KEY);
request.getHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY);
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();
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);

View File

@ -1,9 +1,11 @@
package io.opentelemetry.auto.instrumentation.aws.v0;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.Request;
import com.amazonaws.Response;
import io.opentelemetry.auto.api.MoreTags;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.decorator.HttpClientDecorator;
import io.opentelemetry.trace.Span;
import java.net.URI;
@ -12,12 +14,17 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response> {
public static final AwsSdkClientDecorator DECORATE = new AwsSdkClientDecorator();
static final String COMPONENT_NAME = "java-aws-sdk";
private final Map<String, String> serviceNames = 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
public Span onRequest(final Span span, final Request request) {
@ -25,7 +32,8 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response
super.onRequest(span, request);
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.service", awsServiceName);
@ -36,6 +44,32 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response
MoreTags.RESOURCE_NAME,
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;
}

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;
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.Request;
import com.amazonaws.Response;
import com.amazonaws.handlers.HandlerContextKey;
import com.amazonaws.handlers.RequestHandler2;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.instrumentation.api.SpanScopePair;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
/** Tracing Request Handler */
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 =
OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto");
private final AwsSdkClientDecorator decorate;
// 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");
public TracingRequestHandler(
final ContextStore<AmazonWebServiceRequest, RequestMeta> contextStore) {
decorate = new AwsSdkClientDecorator(contextStore);
}
@Override
public AmazonWebServiceRequest beforeMarshalling(final AmazonWebServiceRequest request) {
@ -32,8 +31,8 @@ public class TracingRequestHandler extends RequestHandler2 {
@Override
public void beforeRequest(final Request<?> request) {
final Span span = TRACER.spanBuilder("aws.http").startSpan();
DECORATE.afterStart(span);
DECORATE.onRequest(span, request);
decorate.afterStart(span);
decorate.onRequest(span, request);
request.addHandlerContext(
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);
spanScopePair.getScope().close();
final Span span = spanScopePair.getSpan();
DECORATE.onResponse(span, response);
DECORATE.beforeFinish(span);
decorate.onResponse(span, response);
decorate.beforeFinish(span);
span.end();
}
}
@ -58,8 +57,8 @@ public class TracingRequestHandler extends RequestHandler2 {
request.addHandlerContext(SPAN_SCOPE_PAIR_CONTEXT_KEY, null);
spanScopePair.getScope().close();
final Span span = spanScopePair.getSpan();
DECORATE.onError(span, e);
DECORATE.beforeFinish(span);
decorate.onError(span, e);
decorate.beforeFinish(span);
span.end();
}
}

View File

@ -16,11 +16,18 @@ import com.amazonaws.client.builder.AwsClientBuilder
import com.amazonaws.handlers.RequestHandler2
import com.amazonaws.regions.Regions
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.kinesis.AmazonKinesisClientBuilder
import com.amazonaws.services.kinesis.model.DeleteStreamRequest
import com.amazonaws.services.rds.AmazonRDSClientBuilder
import com.amazonaws.services.rds.model.DeleteOptionGroupRequest
import com.amazonaws.services.s3.AmazonS3Client
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.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags
@ -144,6 +151,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address"
"aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
}
}
span(1) {
@ -156,7 +166,7 @@ class AWSClientTest extends AgentTestRunner {
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.PEER_HOSTNAME" "localhost"
"$Tags.PEER_PORT" server.address.port
"$Tags.HTTP_URL" "$server.address/$url"
"$Tags.HTTP_URL" "${server.address}${path}"
"$Tags.HTTP_METHOD" "$method"
"$Tags.HTTP_STATUS" 200
}
@ -166,23 +176,41 @@ class AWSClientTest extends AgentTestRunner {
server.lastRequest.headers.get("traceparent") == null
where:
service | operation | method | url | handlerCount | call | body | client
"S3" | "CreateBucket" | "PUT" | "testbucket/" | 1 | { client -> client.createBucket("testbucket") } | "" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build()
"S3" | "GetObject" | "GET" | "someBucket/someKey" | 1 | { client -> client.getObject("someBucket", "someKey") } | "" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build()
"EC2" | "AllocateAddress" | "POST" | "" | 4 | { 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>
""" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build()
"RDS" | "DeleteOptionGroup" | "POST" | "" | 5 | { client -> client.deleteOptionGroup(new DeleteOptionGroupRequest()) } | """
service | operation | method | path | handlerCount | client | call | additionalTags | body
"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 | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { client -> client.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket"] | ""
"DynamoDBv2" | "CreateTable" | "POST" | "/" | 1 | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable"] | ""
"Kinesis" | "DeleteStream" | "POST" | "/" | 1 | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream"] | ""
"SQS" | "CreateQueue" | "POST" | "/" | 4 | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue"] | """
<CreateQueueResponse>
<CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/MyQueue</QueueUrl></CreateQueueResult>
<ResponseMetadata><RequestId>7a62c49f-347e-4fc4-9331-6e8e7a96aa73</RequestId></ResponseMetadata>
</CreateQueueResponse>
"""
"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/">
<ResponseMetadata>
<RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId>
</ResponseMetadata>
</DeleteOptionGroupResponse>
""" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build()
"""
}
def "send #operation request to closed port"() {
@ -213,6 +241,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:${UNUSABLE_PORT}"
"aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
errorTags SdkClientException, ~/Unable to execute HTTP request/
}
}
@ -235,8 +266,8 @@ class AWSClientTest extends AgentTestRunner {
}
where:
service | operation | method | url | call | 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}")
service | operation | method | url | call | additionalTags | body | client
"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"() {
@ -318,6 +349,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address"
"aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
try {
errorTags AmazonClientException, ~/Unable to execute HTTP request/
} catch (AssertionError e) {

View File

@ -114,6 +114,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "$server.address"
"aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
}
}
span(1) {
@ -126,7 +129,7 @@ class AWSClientTest extends AgentTestRunner {
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
"$Tags.PEER_HOSTNAME" "localhost"
"$Tags.PEER_PORT" server.address.port
"$Tags.HTTP_URL" "$server.address/$url"
"$Tags.HTTP_URL" "${server.address}${path}"
"$Tags.HTTP_METHOD" "$method"
"$Tags.HTTP_STATUS" 200
}
@ -136,23 +139,23 @@ class AWSClientTest extends AgentTestRunner {
server.lastRequest.headers.get("traceparent") == null
where:
service | operation | method | url | handlerCount | call | body | client
"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" | "GetObject" | "GET" | "someBucket/someKey" | 1 | { client -> client.getObject("someBucket", "someKey") } | "" | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port")
"EC2" | "AllocateAddress" | "POST" | "" | 4 | { client -> client.allocateAddress() } | """
service | operation | method | path | handlerCount | client | additionalTags | call | body
"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 | new AmazonS3Client().withEndpoint("http://localhost:$server.address.port") | ["aws.bucket.name": "someBucket"] | { client -> client.getObject("someBucket", "someKey") } | ""
"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/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<publicIp>192.0.2.1</publicIp>
<domain>standard</domain>
</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/">
<ResponseMetadata>
<RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId>
</ResponseMetadata>
</DeleteOptionGroupResponse>
""" | new AmazonRDSClient().withEndpoint("http://localhost:$server.address.port")
"""
}
def "send #operation request to closed port"() {
@ -183,6 +186,9 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:${UNUSABLE_PORT}"
"aws.operation" "${operation}Request"
"aws.agent" "java-aws-sdk"
for (def addedTag : additionalTags) {
"$addedTag.key" "$addedTag.value"
}
errorTags AmazonClientException, ~/Unable to execute HTTP request/
}
}
@ -205,8 +211,8 @@ class AWSClientTest extends AgentTestRunner {
}
where:
service | operation | method | url | call | 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}")
service | operation | method | url | call | additionalTags | body | client
"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"() {
@ -243,6 +249,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "https://s3.amazonaws.com"
"aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
errorTags RuntimeException, "bad handler"
}
}
@ -288,6 +295,7 @@ class AWSClientTest extends AgentTestRunner {
"aws.endpoint" "http://localhost:$server.address.port"
"aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk"
"aws.bucket.name" "someBucket"
errorTags AmazonClientException, ~/Unable to execute HTTP request/
}
}

View File

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

View File

@ -2,6 +2,8 @@ package io.opentelemetry.auto.instrumentation.grpc.server;
import io.grpc.Metadata;
import io.opentelemetry.auto.instrumentation.api.AgentPropagation;
import java.util.ArrayList;
import java.util.List;
public final class GrpcExtractAdapter implements AgentPropagation.Getter<Metadata> {
@ -9,7 +11,15 @@ public final class GrpcExtractAdapter implements AgentPropagation.Getter<Metadat
@Override
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

View File

@ -2,6 +2,7 @@ import example.GreeterGrpc
import example.Helloworld
import io.grpc.BindableService
import io.grpc.ManagedChannel
import io.grpc.Metadata
import io.grpc.Server
import io.grpc.Status
import io.grpc.StatusRuntimeException
@ -11,6 +12,7 @@ import io.grpc.stub.StreamObserver
import io.opentelemetry.auto.api.MoreTags
import io.opentelemetry.auto.api.SpanTypes
import io.opentelemetry.auto.instrumentation.api.Tags
import io.opentelemetry.auto.instrumentation.grpc.server.GrpcExtractAdapter
import io.opentelemetry.auto.test.AgentTestRunner
import io.opentelemetry.sdk.trace.SpanData
@ -268,4 +270,17 @@ class GrpcTest extends AgentTestRunner {
"Status - description" | Status.PERMISSION_DENIED.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"
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"

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 String url = metaData.getURL();
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 {
dbInfo = DBInfo.DEFAULT;
}

View File

@ -30,13 +30,16 @@ public abstract class JDBCUtils {
connection = connection.unwrap(Connection.class);
}
} catch (final Exception | AbstractMethodError e) {
// Attempt to work around c3po delegating to an connection that doesn't support unwrapping.
final Class<? extends Connection> connectionClass = connection.getClass();
if (connectionClass.getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
final Field inner = connectionClass.getDeclaredField("inner");
inner.setAccessible(true);
c3poField = inner;
return (Connection) c3poField.get(connection);
if (connection != null) {
// Attempt to work around c3po delegating to an connection that doesn't support
// unwrapping.
final Class<? extends Connection> connectionClass = connection.getClass();
if (connectionClass.getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
final Field inner = connectionClass.getDeclaredField("inner");
inner.setAccessible(true);
c3poField = inner;
return (Connection) c3poField.get(connection);
}
}
// 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.instrumentation.api.Tags
import io.opentelemetry.auto.test.AgentTestRunner
import org.apache.derby.jdbc.EmbeddedDataSource
import org.apache.derby.jdbc.EmbeddedDriver
import org.h2.Driver
import org.h2.jdbcx.JdbcDataSource
import org.hsqldb.jdbc.JDBCDriver
import spock.lang.Shared
import spock.lang.Unroll
import test.TestConnection
import test.TestStatement
import javax.sql.DataSource
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
class JDBCInstrumentationTest extends AgentTestRunner {
static {
System.setProperty("opentelemetry.auto.integration.jdbc-datasource.enabled", "true")
}
@Shared
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 ))"
"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 ))"
}
@Unroll
@ -486,7 +492,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
when:
try {
connection = new DummyThrowingConnection()
connection = new TestConnection(true)
} catch (Exception e) {
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"
}
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
def "#connectionPoolName connections should be cached in case of wrapped connections"() {
setup:

View File

@ -1,3 +1,5 @@
package test
import java.sql.Array
import java.sql.Blob
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 {
DummyThrowingConnection() {
throw new RuntimeException("Dummy exception")
class TestConnection implements Connection {
TestConnection(boolean throwException) {
if (throwException) {
throw new RuntimeException("connection exception")
}
}
@Override
Statement createStatement() throws SQLException {
return null
return new TestStatement(this)
}
@Override
@ -76,7 +81,7 @@ class DummyThrowingConnection implements Connection {
@Override
DatabaseMetaData getMetaData() throws SQLException {
return null
return new TestDatabaseMetaData()
}
@Override
@ -241,12 +246,12 @@ class DummyThrowingConnection implements Connection {
@Override
String getClientInfo(String name) throws SQLException {
return null
throw new UnsupportedOperationException("Test 123")
}
@Override
Properties getClientInfo() throws SQLException {
return null
throw new UnsupportedOperationException("Test 123")
}
@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"
module = "jedis"
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"

View File

@ -584,4 +584,48 @@ class JSPInstrumentationBasicTests extends AgentTestRunner {
"normal" | "compileError.jsp" | "compileError_jsp" | ""
"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
protected String[] instrumentationNames() {
return new String[] {"rmi"};
return new String[] {"rmi", "rmi-client"};
}
@Override
@ -23,6 +23,6 @@ public class RmiClientDecorator extends ClientDecorator {
@Override
protected String service() {
return "rmi";
return null;
}
}

View File

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

View File

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

View File

@ -5,8 +5,12 @@ muzzle {
versions = "[2.3, 3.0)"
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"

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.auto.instrumentation.servlet2.HttpServletRequestE
import static io.opentelemetry.auto.instrumentation.servlet2.Servlet2Decorator.DECORATE;
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.api.Tags;
@ -36,12 +37,16 @@ public class Servlet2Advice {
return null;
}
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (response instanceof HttpServletResponse) {
// For use by HttpServletResponseInstrumentation:
InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class)
.put((HttpServletResponse) response, httpServletRequest);
response = new StatusSavingHttpServletResponseWrapper((HttpServletResponse) response);
}
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final AgentSpan.Context extractedContext = propagate().extract(httpServletRequest, GETTER);
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"))));
}
@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
* advice is always called before HttpServletInstrumentation which is instrumenting the protected

View File

@ -1,7 +1,6 @@
package io.opentelemetry.auto.instrumentation.servlet3;
import io.opentelemetry.auto.instrumentation.api.AgentPropagation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@ -14,7 +13,7 @@ public class HttpServletRequestExtractAdapter
@Override
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()));
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 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.api.Tags;
@ -24,7 +25,10 @@ public class Servlet3Advice {
@Advice.OnMethodEnter(suppress = Throwable.class)
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 hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof AgentSpan;
final boolean invalidRequest = !(request instanceof HttpServletRequest);
@ -35,6 +39,10 @@ public class Servlet3Advice {
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 span =

View File

@ -41,6 +41,12 @@ public final class Servlet3Instrumentation extends Instrumenter.Default {
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
* 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"
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;
}
}
}
}