Merge pull request #64 from trask/dd-merge
Merge changes from dd-trace-java 0.41.0
This commit is contained in:
commit
1d474353a5
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ public class RmiServerDecorator extends ServerDecorator {
|
|||
|
||||
@Override
|
||||
protected String[] instrumentationNames() {
|
||||
return new String[] {"rmi"};
|
||||
return new String[] {"rmi", "rmi-server"};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue