Instrument gRPC
Adds spans for the duration of the connection and child spans for each message. Also propagates the trace using the metadata object.
This commit is contained in:
parent
aa3fc0717e
commit
a34f7b849b
|
@ -0,0 +1,74 @@
|
||||||
|
apply plugin: 'version-scan'
|
||||||
|
|
||||||
|
versionScan {
|
||||||
|
group = "io.grpc"
|
||||||
|
module = "grpc-core"
|
||||||
|
versions = "[1.5.0,)"
|
||||||
|
verifyPresent = [
|
||||||
|
"io.grpc.InternalServerInterceptors": null,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
apply plugin: 'idea'
|
||||||
|
apply plugin: 'com.google.protobuf'
|
||||||
|
|
||||||
|
def grpcVersion = '1.5.0'
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
// Download compiler rather than using locally installed version:
|
||||||
|
artifact = 'com.google.protobuf:protoc:3.3.0'
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
|
||||||
|
}
|
||||||
|
generateProtoTasks {
|
||||||
|
all()*.plugins { grpc {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'io.grpc', name: 'grpc-core', version: grpcVersion
|
||||||
|
|
||||||
|
compile project(':dd-trace-ot')
|
||||||
|
compile project(':dd-java-agent:agent-tooling')
|
||||||
|
|
||||||
|
compile deps.bytebuddy
|
||||||
|
compile deps.opentracing
|
||||||
|
compile deps.autoservice
|
||||||
|
annotationProcessor deps.autoservice
|
||||||
|
implementation deps.autoservice
|
||||||
|
|
||||||
|
testCompile project(':dd-java-agent:testing')
|
||||||
|
|
||||||
|
testCompile group: 'io.grpc', name: 'grpc-netty', version: grpcVersion
|
||||||
|
testCompile group: 'io.grpc', name: 'grpc-protobuf', version: grpcVersion
|
||||||
|
testCompile group: 'io.grpc', name: 'grpc-stub', version: grpcVersion
|
||||||
|
|
||||||
|
latestDepTestCompile sourceSets.test.output // include the protobuf generated classes
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.latestDepTestCompile {
|
||||||
|
resolutionStrategy {
|
||||||
|
force group: 'io.grpc', name: 'grpc-netty', version: '+'
|
||||||
|
force group: 'io.grpc', name: 'grpc-protobuf', version: '+'
|
||||||
|
force group: 'io.grpc', name: 'grpc-stub', version: '+'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package datadog.trace.instrumentation.grpc.client;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import io.grpc.ClientInterceptor;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class GrpcClientBuilderInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public GrpcClientBuilderInstrumentation() {
|
||||||
|
super("grpc", "grpc-client");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher typeMatcher() {
|
||||||
|
return named("io.grpc.internal.AbstractManagedChannelImplBuilder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<? super ClassLoader> classLoaderMatcher() {
|
||||||
|
return classLoaderHasClasses("io.grpc.InternalServerInterceptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
"datadog.trace.instrumentation.grpc.client.GrpcInjectAdapter",
|
||||||
|
"datadog.trace.instrumentation.grpc.client.TracingClientInterceptor",
|
||||||
|
"datadog.trace.instrumentation.grpc.client.TracingClientInterceptor$TracingClientCall",
|
||||||
|
"datadog.trace.instrumentation.grpc.client.TracingClientInterceptor$TracingClientCallListener",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ElementMatcher, String> transformers() {
|
||||||
|
return Collections.<ElementMatcher, String>singletonMap(
|
||||||
|
isMethod().and(named("build")), AddInterceptorAdvice.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AddInterceptorAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void addInterceptor(
|
||||||
|
@Advice.FieldValue("interceptors") final List<ClientInterceptor> interceptors) {
|
||||||
|
boolean shouldRegister = true;
|
||||||
|
for (final ClientInterceptor interceptor : interceptors) {
|
||||||
|
if (interceptor instanceof TracingClientInterceptor) {
|
||||||
|
shouldRegister = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRegister) {
|
||||||
|
interceptors.add(0, new TracingClientInterceptor(GlobalTracer.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package datadog.trace.instrumentation.grpc.client;
|
||||||
|
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.opentracing.propagation.TextMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class GrpcInjectAdapter implements TextMap {
|
||||||
|
private final Metadata metadata;
|
||||||
|
|
||||||
|
public GrpcInjectAdapter(final Metadata metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Map.Entry<String, String>> iterator() {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"GrpcInjectAdapter should only be used with Tracer.inject()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(final String key, final String value) {
|
||||||
|
this.metadata.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package datadog.trace.instrumentation.grpc.client;
|
||||||
|
|
||||||
|
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||||
|
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import datadog.trace.api.DDTags;
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.Channel;
|
||||||
|
import io.grpc.ClientCall;
|
||||||
|
import io.grpc.ClientInterceptor;
|
||||||
|
import io.grpc.ForwardingClientCall;
|
||||||
|
import io.grpc.ForwardingClientCallListener;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.MethodDescriptor;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
|
import io.opentracing.propagation.Format;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class TracingClientInterceptor implements ClientInterceptor {
|
||||||
|
|
||||||
|
private final Tracer tracer;
|
||||||
|
|
||||||
|
public TracingClientInterceptor(final Tracer tracer) {
|
||||||
|
this.tracer = tracer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||||
|
final MethodDescriptor<ReqT, RespT> method,
|
||||||
|
final CallOptions callOptions,
|
||||||
|
final Channel next) {
|
||||||
|
|
||||||
|
final Scope scope =
|
||||||
|
tracer
|
||||||
|
.buildSpan("grpc.client")
|
||||||
|
.withTag(DDTags.RESOURCE_NAME, method.getFullMethodName())
|
||||||
|
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.RPC)
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||||
|
.startActive(false);
|
||||||
|
final Span span = scope.span();
|
||||||
|
|
||||||
|
final ClientCall<ReqT, RespT> result;
|
||||||
|
try {
|
||||||
|
// call other interceptors
|
||||||
|
result = next.newCall(method, callOptions);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TracingClientCall<>(tracer, span, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class TracingClientCall<ReqT, RespT>
|
||||||
|
extends ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT> {
|
||||||
|
final Tracer tracer;
|
||||||
|
final Span span;
|
||||||
|
|
||||||
|
TracingClientCall(
|
||||||
|
final Tracer tracer, final Span span, final ClientCall<ReqT, RespT> delegate) {
|
||||||
|
super(delegate);
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(final Listener<RespT> responseListener, final Metadata headers) {
|
||||||
|
tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new GrpcInjectAdapter(headers));
|
||||||
|
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, false)) {
|
||||||
|
super.start(new TracingClientCallListener<>(tracer, span, responseListener), headers);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(final ReqT message) {
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, false)) {
|
||||||
|
super.sendMessage(message);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class TracingClientCallListener<RespT>
|
||||||
|
extends ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
|
||||||
|
final Tracer tracer;
|
||||||
|
final Span span;
|
||||||
|
|
||||||
|
TracingClientCallListener(
|
||||||
|
final Tracer tracer, final Span span, final ClientCall.Listener<RespT> delegate) {
|
||||||
|
super(delegate);
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(final RespT message) {
|
||||||
|
final Scope scope =
|
||||||
|
tracer
|
||||||
|
.buildSpan("grpc.message")
|
||||||
|
.asChildOf(span)
|
||||||
|
.withTag("message.type", message.getClass().getName())
|
||||||
|
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.RPC)
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||||
|
.startActive(true);
|
||||||
|
try {
|
||||||
|
delegate().onMessage(message);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
final Span span = scope.span();
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
this.span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
this.span.finish();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(final Status status, final Metadata trailers) {
|
||||||
|
span.setTag("status.code", status.getCode().name());
|
||||||
|
if (status.getDescription() != null) {
|
||||||
|
span.setTag("status.description", status.getDescription());
|
||||||
|
}
|
||||||
|
if (!status.isOk()) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
}
|
||||||
|
if (status.getCause() != null) {
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, status.getCause()));
|
||||||
|
}
|
||||||
|
// Finishes span.
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, true)) {
|
||||||
|
delegate().onClose(status, trailers);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReady() {
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, false)) {
|
||||||
|
delegate().onReady();
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package datadog.trace.instrumentation.grpc.server;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import io.grpc.ServerInterceptor;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class GrpcServerBuilderInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public GrpcServerBuilderInstrumentation() {
|
||||||
|
super("grpc", "grpc-server");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher typeMatcher() {
|
||||||
|
return named("io.grpc.internal.AbstractServerImplBuilder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<? super ClassLoader> classLoaderMatcher() {
|
||||||
|
return classLoaderHasClasses("io.grpc.InternalServerInterceptors");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
"datadog.trace.instrumentation.grpc.server.TracingServerInterceptor",
|
||||||
|
"datadog.trace.instrumentation.grpc.server.TracingServerInterceptor$TracingServerCallListener",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ElementMatcher, String> transformers() {
|
||||||
|
return Collections.<ElementMatcher, String>singletonMap(
|
||||||
|
isMethod().and(named("build")), AddInterceptorAdvice.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AddInterceptorAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void addInterceptor(
|
||||||
|
@Advice.FieldValue("interceptors") final List<ServerInterceptor> interceptors) {
|
||||||
|
boolean shouldRegister = true;
|
||||||
|
for (final ServerInterceptor interceptor : interceptors) {
|
||||||
|
if (interceptor instanceof TracingServerInterceptor) {
|
||||||
|
shouldRegister = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRegister) {
|
||||||
|
interceptors.add(0, new TracingServerInterceptor(GlobalTracer.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package datadog.trace.instrumentation.grpc.server;
|
||||||
|
|
||||||
|
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||||
|
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import datadog.trace.api.DDTags;
|
||||||
|
import io.grpc.ForwardingServerCallListener;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.ServerCall;
|
||||||
|
import io.grpc.ServerCallHandler;
|
||||||
|
import io.grpc.ServerInterceptor;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.SpanContext;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
|
import io.opentracing.propagation.Format;
|
||||||
|
import io.opentracing.propagation.TextMapExtractAdapter;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class TracingServerInterceptor implements ServerInterceptor {
|
||||||
|
|
||||||
|
private final Tracer tracer;
|
||||||
|
|
||||||
|
public TracingServerInterceptor(final Tracer tracer) {
|
||||||
|
this.tracer = tracer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||||
|
final ServerCall<ReqT, RespT> call,
|
||||||
|
final Metadata headers,
|
||||||
|
final ServerCallHandler<ReqT, RespT> next) {
|
||||||
|
|
||||||
|
final Map<String, String> headerMap = new HashMap<>();
|
||||||
|
for (final String key : headers.keys()) {
|
||||||
|
if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
||||||
|
final String value = headers.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
|
||||||
|
headerMap.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final SpanContext spanContext =
|
||||||
|
tracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(headerMap));
|
||||||
|
|
||||||
|
final Tracer.SpanBuilder spanBuilder =
|
||||||
|
tracer
|
||||||
|
.buildSpan("grpc.server")
|
||||||
|
.withTag(DDTags.RESOURCE_NAME, call.getMethodDescriptor().getFullMethodName())
|
||||||
|
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.RPC)
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER);
|
||||||
|
if (spanContext != null) {
|
||||||
|
spanBuilder.asChildOf(spanContext);
|
||||||
|
}
|
||||||
|
final Scope scope = spanBuilder.startActive(false);
|
||||||
|
final Span span = scope.span();
|
||||||
|
|
||||||
|
final ServerCall.Listener<ReqT> result;
|
||||||
|
try {
|
||||||
|
// call other interceptors
|
||||||
|
result = next.startCall(call, headers);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ensures the server implementation can see the span in scope
|
||||||
|
return new TracingServerCallListener<>(tracer, span, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class TracingServerCallListener<ReqT>
|
||||||
|
extends ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
|
||||||
|
final Tracer tracer;
|
||||||
|
final Span span;
|
||||||
|
|
||||||
|
TracingServerCallListener(
|
||||||
|
final Tracer tracer, final Span span, final ServerCall.Listener<ReqT> delegate) {
|
||||||
|
super(delegate);
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(final ReqT message) {
|
||||||
|
final Scope scope =
|
||||||
|
tracer
|
||||||
|
.buildSpan("grpc.message")
|
||||||
|
.asChildOf(span)
|
||||||
|
.withTag("message.type", message.getClass().getName())
|
||||||
|
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.RPC)
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
|
||||||
|
.startActive(true);
|
||||||
|
try {
|
||||||
|
delegate().onMessage(message);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
final Span span = scope.span();
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
this.span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
this.span.finish();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
scope.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHalfClose() {
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, false)) {
|
||||||
|
delegate().onHalfClose();
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {
|
||||||
|
// Finishes span.
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, true)) {
|
||||||
|
delegate().onCancel();
|
||||||
|
span.setTag("canceled", true);
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
// Finishes span.
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, true)) {
|
||||||
|
delegate().onComplete();
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReady() {
|
||||||
|
try (final Scope ignored = tracer.scopeManager().activate(span, false)) {
|
||||||
|
delegate().onReady();
|
||||||
|
} catch (final RuntimeException | Error e) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||||
|
span.finish();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.api.DDTags
|
||||||
|
import example.GreeterGrpc
|
||||||
|
import example.Helloworld
|
||||||
|
import io.grpc.BindableService
|
||||||
|
import io.grpc.ManagedChannel
|
||||||
|
import io.grpc.Server
|
||||||
|
import io.grpc.inprocess.InProcessChannelBuilder
|
||||||
|
import io.grpc.inprocess.InProcessServerBuilder
|
||||||
|
import io.grpc.stub.StreamObserver
|
||||||
|
import io.opentracing.tag.Tags
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||||
|
|
||||||
|
class GrpcStreamingTest extends AgentTestRunner {
|
||||||
|
static {
|
||||||
|
System.setProperty("dd.integration.grpc.enabled", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test conversation #name"() {
|
||||||
|
setup:
|
||||||
|
final msgCount = serverMessageCount
|
||||||
|
def serverReceived = new CopyOnWriteArrayList<>()
|
||||||
|
def clientReceived = new CopyOnWriteArrayList<>()
|
||||||
|
def error = new AtomicReference()
|
||||||
|
|
||||||
|
BindableService greeter = new GreeterGrpc.GreeterImplBase() {
|
||||||
|
@Override
|
||||||
|
StreamObserver<Helloworld.Response> conversation(StreamObserver<Helloworld.Response> observer) {
|
||||||
|
return new StreamObserver<Helloworld.Response>() {
|
||||||
|
@Override
|
||||||
|
void onNext(Helloworld.Response value) {
|
||||||
|
serverReceived << value.message
|
||||||
|
|
||||||
|
(1..msgCount).each {
|
||||||
|
observer.onNext(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onError(Throwable t) {
|
||||||
|
error.set(t)
|
||||||
|
observer.onError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onCompleted() {
|
||||||
|
observer.onCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start()
|
||||||
|
|
||||||
|
ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build()
|
||||||
|
GreeterGrpc.GreeterStub client = GreeterGrpc.newStub(channel).withWaitForReady()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def observer = client.conversation(new StreamObserver<Helloworld.Response>() {
|
||||||
|
@Override
|
||||||
|
void onNext(Helloworld.Response value) {
|
||||||
|
clientReceived << value.message
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onError(Throwable t) {
|
||||||
|
error.set(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onCompleted() {
|
||||||
|
TEST_WRITER.waitForTraces(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
clientRange.each {
|
||||||
|
def message = Helloworld.Response.newBuilder().setMessage("call $it").build()
|
||||||
|
observer.onNext(message)
|
||||||
|
}
|
||||||
|
observer.onCompleted()
|
||||||
|
|
||||||
|
then:
|
||||||
|
error.get() == null
|
||||||
|
|
||||||
|
assertTraces(TEST_WRITER, 2) {
|
||||||
|
trace(0, clientMessageCount + 1) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.server"
|
||||||
|
resourceName "example.Greeter/Conversation"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf trace(1).get(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clientRange.each {
|
||||||
|
span(it) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Response"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, (clientMessageCount * serverMessageCount) + 1) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.client"
|
||||||
|
resourceName "example.Greeter/Conversation"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
parent()
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"status.code" "OK"
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(1..(clientMessageCount * serverMessageCount)).each {
|
||||||
|
span(it) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Response"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverReceived == clientRange.collect { "call $it" }
|
||||||
|
clientReceived == serverRange.collect { clientRange.collect { "call $it" } }.flatten().sort()
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS)
|
||||||
|
server?.shutdownNow()?.awaitTermination()
|
||||||
|
|
||||||
|
where:
|
||||||
|
name | clientMessageCount | serverMessageCount
|
||||||
|
"A" | 1 | 1
|
||||||
|
"B" | 2 | 1
|
||||||
|
"C" | 1 | 2
|
||||||
|
"D" | 2 | 2
|
||||||
|
"E" | 3 | 3
|
||||||
|
|
||||||
|
clientRange = 1..clientMessageCount
|
||||||
|
serverRange = 1..serverMessageCount
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.api.DDTags
|
||||||
|
import example.GreeterGrpc
|
||||||
|
import example.Helloworld
|
||||||
|
import io.grpc.BindableService
|
||||||
|
import io.grpc.ManagedChannel
|
||||||
|
import io.grpc.Server
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.grpc.inprocess.InProcessChannelBuilder
|
||||||
|
import io.grpc.inprocess.InProcessServerBuilder
|
||||||
|
import io.grpc.stub.StreamObserver
|
||||||
|
import io.opentracing.tag.Tags
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||||
|
|
||||||
|
class GrpcTest extends AgentTestRunner {
|
||||||
|
static {
|
||||||
|
System.setProperty("dd.integration.grpc.enabled", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test request-response"() {
|
||||||
|
setup:
|
||||||
|
BindableService greeter = new GreeterGrpc.GreeterImplBase() {
|
||||||
|
@Override
|
||||||
|
void sayHello(
|
||||||
|
final Helloworld.Request req, final StreamObserver<Helloworld.Response> responseObserver) {
|
||||||
|
final Helloworld.Response reply = Helloworld.Response.newBuilder().setMessage("Hello $req.name").build()
|
||||||
|
responseObserver.onNext(reply)
|
||||||
|
responseObserver.onCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start()
|
||||||
|
|
||||||
|
ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build()
|
||||||
|
GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def response = client.sayHello(Helloworld.Request.newBuilder().setName(name).build())
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.message == "Hello $name"
|
||||||
|
assertTraces(TEST_WRITER, 2) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.server"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf trace(1).get(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Request"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.client"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
parent()
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"status.code" "OK"
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Response"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS)
|
||||||
|
server?.shutdownNow()?.awaitTermination()
|
||||||
|
|
||||||
|
where:
|
||||||
|
name << ["some name", "some other name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test error - #name"() {
|
||||||
|
setup:
|
||||||
|
def error = status.asException()
|
||||||
|
BindableService greeter = new GreeterGrpc.GreeterImplBase() {
|
||||||
|
@Override
|
||||||
|
void sayHello(
|
||||||
|
final Helloworld.Request req, final StreamObserver<Helloworld.Response> responseObserver) {
|
||||||
|
responseObserver.onError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start()
|
||||||
|
|
||||||
|
ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build()
|
||||||
|
GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel)
|
||||||
|
|
||||||
|
when:
|
||||||
|
client.sayHello(Helloworld.Request.newBuilder().setName(name).build())
|
||||||
|
|
||||||
|
then:
|
||||||
|
thrown StatusRuntimeException
|
||||||
|
|
||||||
|
assertTraces(TEST_WRITER, 2) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.server"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf trace(1).get(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Request"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, 1) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.client"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
parent()
|
||||||
|
errored true
|
||||||
|
tags {
|
||||||
|
"status.code" "${status.code.name()}"
|
||||||
|
"status.description" description
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
tag "error", true
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS)
|
||||||
|
server?.shutdownNow()?.awaitTermination()
|
||||||
|
|
||||||
|
where:
|
||||||
|
name | status | description
|
||||||
|
"Runtime - cause" | Status.UNKNOWN.withCause(new RuntimeException("some error")) | null
|
||||||
|
"Status - cause" | Status.PERMISSION_DENIED.withCause(new RuntimeException("some error")) | null
|
||||||
|
"StatusRuntime - cause" | Status.UNIMPLEMENTED.withCause(new RuntimeException("some error")) | null
|
||||||
|
"Runtime - description" | Status.UNKNOWN.withDescription("some description") | "some description"
|
||||||
|
"Status - description" | Status.PERMISSION_DENIED.withDescription("some description") | "some description"
|
||||||
|
"StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description") | "some description"
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test error thrown - #name"() {
|
||||||
|
setup:
|
||||||
|
def error = status.asRuntimeException()
|
||||||
|
BindableService greeter = new GreeterGrpc.GreeterImplBase() {
|
||||||
|
@Override
|
||||||
|
void sayHello(
|
||||||
|
final Helloworld.Request req, final StreamObserver<Helloworld.Response> responseObserver) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server server = InProcessServerBuilder.forName(getClass().name).addService(greeter).directExecutor().build().start()
|
||||||
|
|
||||||
|
ManagedChannel channel = InProcessChannelBuilder.forName(getClass().name).build()
|
||||||
|
GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel)
|
||||||
|
|
||||||
|
when:
|
||||||
|
client.sayHello(Helloworld.Request.newBuilder().setName(name).build())
|
||||||
|
|
||||||
|
then:
|
||||||
|
thrown StatusRuntimeException
|
||||||
|
|
||||||
|
assertTraces(TEST_WRITER, 2) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.server"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf trace(1).get(0)
|
||||||
|
errored true
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
errorTags error.class, error.message
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.message"
|
||||||
|
resourceName "grpc.message"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
childOf span(0)
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
"message.type" "example.Helloworld\$Request"
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace(1, 1) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "grpc.client"
|
||||||
|
resourceName "example.Greeter/SayHello"
|
||||||
|
spanType DDSpanTypes.RPC
|
||||||
|
parent()
|
||||||
|
errored true
|
||||||
|
tags {
|
||||||
|
"status.code" "UNKNOWN"
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||||
|
"$DDTags.SPAN_TYPE" DDSpanTypes.RPC
|
||||||
|
tag "error", true
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
channel?.shutdownNow()?.awaitTermination(10, TimeUnit.SECONDS)
|
||||||
|
server?.shutdownNow()?.awaitTermination()
|
||||||
|
|
||||||
|
where:
|
||||||
|
name | status
|
||||||
|
"Runtime - cause" | Status.UNKNOWN.withCause(new RuntimeException("some error"))
|
||||||
|
"Status - cause" | Status.PERMISSION_DENIED.withCause(new RuntimeException("some error"))
|
||||||
|
"StatusRuntime - cause" | Status.UNIMPLEMENTED.withCause(new RuntimeException("some error"))
|
||||||
|
"Runtime - description" | Status.UNKNOWN.withDescription("some description")
|
||||||
|
"Status - description" | Status.PERMISSION_DENIED.withDescription("some description")
|
||||||
|
"StatusRuntime - description" | Status.UNIMPLEMENTED.withDescription("some description")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import io.grpc.CallOptions
|
||||||
|
import io.grpc.Channel
|
||||||
|
import io.grpc.ClientCall
|
||||||
|
import io.grpc.ClientInterceptor
|
||||||
|
import io.grpc.ForwardingClientCall
|
||||||
|
import io.grpc.Metadata
|
||||||
|
import io.grpc.MethodDescriptor
|
||||||
|
|
||||||
|
import java.util.concurrent.Phaser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor that blocks client from returning until server trace is reported.
|
||||||
|
*/
|
||||||
|
class BlockingInterceptor implements ClientInterceptor {
|
||||||
|
private final Phaser phaser
|
||||||
|
|
||||||
|
BlockingInterceptor(Phaser phaser) {
|
||||||
|
this.phaser = phaser
|
||||||
|
phaser.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
|
||||||
|
return new ForwardingClientCall.SimpleForwardingClientCall(next.newCall(method, callOptions)) {
|
||||||
|
@Override
|
||||||
|
void start(final ClientCall.Listener responseListener, final Metadata headers) {
|
||||||
|
super.start(new BlockingListener(responseListener, phaser), headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import io.grpc.ClientCall
|
||||||
|
import io.grpc.ForwardingClientCallListener
|
||||||
|
import io.grpc.Metadata
|
||||||
|
import io.grpc.Status
|
||||||
|
|
||||||
|
import java.util.concurrent.Phaser
|
||||||
|
|
||||||
|
class BlockingListener<RespT> extends ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
|
||||||
|
private final Phaser phaser
|
||||||
|
|
||||||
|
BlockingListener(ClientCall.Listener<RespT> delegate, Phaser phaser) {
|
||||||
|
super(delegate)
|
||||||
|
this.phaser = phaser
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onClose(final Status status, final Metadata trailers) {
|
||||||
|
delegate().onClose(status, trailers)
|
||||||
|
phaser.arriveAndAwaitAdvance()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package example;
|
||||||
|
|
||||||
|
service Greeter {
|
||||||
|
rpc SayHello (Request) returns (Response) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc Conversation (stream Response) returns (stream Response) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
string message = 1;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package datadog.trace.agent.test
|
package datadog.trace.agent.test
|
||||||
|
|
||||||
|
import datadog.opentracing.DDSpan
|
||||||
import datadog.trace.common.writer.ListWriter
|
import datadog.trace.common.writer.ListWriter
|
||||||
import org.codehaus.groovy.runtime.powerassert.PowerAssertionError
|
import org.codehaus.groovy.runtime.powerassert.PowerAssertionError
|
||||||
import org.spockframework.runtime.Condition
|
import org.spockframework.runtime.Condition
|
||||||
|
@ -13,7 +14,7 @@ class ListWriterAssert {
|
||||||
private final int size
|
private final int size
|
||||||
private final Set<Integer> assertedIndexes = new HashSet<>()
|
private final Set<Integer> assertedIndexes = new HashSet<>()
|
||||||
|
|
||||||
private ListWriterAssert(writer) {
|
private ListWriterAssert(ListWriter writer) {
|
||||||
this.writer = writer
|
this.writer = writer
|
||||||
size = writer.size()
|
size = writer.size()
|
||||||
}
|
}
|
||||||
|
@ -49,6 +50,10 @@ class ListWriterAssert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<DDSpan> trace(int index) {
|
||||||
|
return writer.get(index)
|
||||||
|
}
|
||||||
|
|
||||||
void trace(int index, int expectedSize,
|
void trace(int index, int expectedSize,
|
||||||
@DelegatesTo(value = TraceAssert, strategy = Closure.DELEGATE_FIRST) Closure spec) {
|
@DelegatesTo(value = TraceAssert, strategy = Closure.DELEGATE_FIRST) Closure spec) {
|
||||||
if (index >= size) {
|
if (index >= size) {
|
||||||
|
|
|
@ -44,6 +44,9 @@ class TagsAssert {
|
||||||
}
|
}
|
||||||
|
|
||||||
def tag(String name, value) {
|
def tag(String name, value) {
|
||||||
|
if (value == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
assertedTags.add(name)
|
assertedTags.add(name)
|
||||||
if (value instanceof Class) {
|
if (value instanceof Class) {
|
||||||
assert ((Class) value).isInstance(tags[name])
|
assert ((Class) value).isInstance(tags[name])
|
||||||
|
|
|
@ -77,13 +77,12 @@ public abstract class AgentTestRunner extends Specification {
|
||||||
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.WARN);
|
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.WARN);
|
||||||
((Logger) LoggerFactory.getLogger("datadog")).setLevel(Level.DEBUG);
|
((Logger) LoggerFactory.getLogger("datadog")).setLevel(Level.DEBUG);
|
||||||
|
|
||||||
WRITER_PHASER.register();
|
|
||||||
TEST_WRITER =
|
TEST_WRITER =
|
||||||
new ListWriter() {
|
new ListWriter() {
|
||||||
@Override
|
@Override
|
||||||
public boolean add(final List<DDSpan> trace) {
|
public boolean add(final List<DDSpan> trace) {
|
||||||
final boolean result = super.add(trace);
|
final boolean result = super.add(trace);
|
||||||
WRITER_PHASER.arrive();
|
WRITER_PHASER.arriveAndDeregister();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -137,6 +136,7 @@ public abstract class AgentTestRunner extends Specification {
|
||||||
@Before
|
@Before
|
||||||
public void beforeTest() {
|
public void beforeTest() {
|
||||||
TEST_WRITER.start();
|
TEST_WRITER.start();
|
||||||
|
WRITER_PHASER.register();
|
||||||
INSTRUMENTATION_ERROR_COUNT.set(0);
|
INSTRUMENTATION_ERROR_COUNT.set(0);
|
||||||
ERROR_LISTENER.activateTest(this);
|
ERROR_LISTENER.activateTest(this);
|
||||||
assert getTestTracer().activeSpan() == null;
|
assert getTestTracer().activeSpan() == null;
|
||||||
|
@ -159,11 +159,11 @@ public abstract class AgentTestRunner extends Specification {
|
||||||
public static class ErrorCountingListener implements AgentBuilder.Listener {
|
public static class ErrorCountingListener implements AgentBuilder.Listener {
|
||||||
private static final List<AgentTestRunner> activeTests = new CopyOnWriteArrayList<>();
|
private static final List<AgentTestRunner> activeTests = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public void activateTest(AgentTestRunner testRunner) {
|
public void activateTest(final AgentTestRunner testRunner) {
|
||||||
activeTests.add(testRunner);
|
activeTests.add(testRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deactivateTest(AgentTestRunner testRunner) {
|
public void deactivateTest(final AgentTestRunner testRunner) {
|
||||||
activeTests.remove(testRunner);
|
activeTests.remove(testRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ public abstract class AgentTestRunner extends Specification {
|
||||||
final JavaModule module,
|
final JavaModule module,
|
||||||
final boolean loaded,
|
final boolean loaded,
|
||||||
final Throwable throwable) {
|
final Throwable throwable) {
|
||||||
for (AgentTestRunner testRunner : activeTests) {
|
for (final AgentTestRunner testRunner : activeTests) {
|
||||||
if (testRunner.onInstrumentationError(typeName, classLoader, module, loaded, throwable)) {
|
if (testRunner.onInstrumentationError(typeName, classLoader, module, loaded, throwable)) {
|
||||||
INSTRUMENTATION_ERROR_COUNT.incrementAndGet();
|
INSTRUMENTATION_ERROR_COUNT.incrementAndGet();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3,6 +3,7 @@ package datadog.trace.api;
|
||||||
public class DDSpanTypes {
|
public class DDSpanTypes {
|
||||||
public static final String HTTP_CLIENT = "http";
|
public static final String HTTP_CLIENT = "http";
|
||||||
public static final String WEB_SERVLET = "web";
|
public static final String WEB_SERVLET = "web";
|
||||||
|
public static final String RPC = "rpc";
|
||||||
|
|
||||||
public static final String SQL = "sql";
|
public static final String SQL = "sql";
|
||||||
public static final String MONGO = "mongodb";
|
public static final String MONGO = "mongodb";
|
||||||
|
|
|
@ -16,6 +16,7 @@ tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs += ['-Xep:FutureReturnValueIgnored:OFF']
|
options.compilerArgs += ['-Xep:FutureReturnValueIgnored:OFF']
|
||||||
// workaround for: https://github.com/google/error-prone/issues/780
|
// workaround for: https://github.com/google/error-prone/issues/780
|
||||||
options.compilerArgs += ['-Xep:ParameterName:OFF']
|
options.compilerArgs += ['-Xep:ParameterName:OFF']
|
||||||
|
options.compilerArgs += ['-XepDisableWarningsInGeneratedCode']
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: "eclipse"
|
apply plugin: "eclipse"
|
||||||
|
|
|
@ -18,6 +18,7 @@ include ':dd-java-agent:instrumentation:elasticsearch-rest-5'
|
||||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-2'
|
include ':dd-java-agent:instrumentation:elasticsearch-transport-2'
|
||||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-5'
|
include ':dd-java-agent:instrumentation:elasticsearch-transport-5'
|
||||||
include ':dd-java-agent:instrumentation:elasticsearch-transport-6'
|
include ':dd-java-agent:instrumentation:elasticsearch-transport-6'
|
||||||
|
include ':dd-java-agent:instrumentation:grpc-1.5'
|
||||||
include ':dd-java-agent:instrumentation:http-url-connection'
|
include ':dd-java-agent:instrumentation:http-url-connection'
|
||||||
include ':dd-java-agent:instrumentation:hystrix-1.4'
|
include ':dd-java-agent:instrumentation:hystrix-1.4'
|
||||||
include ':dd-java-agent:instrumentation:jax-rs-annotations'
|
include ':dd-java-agent:instrumentation:jax-rs-annotations'
|
||||||
|
|
Loading…
Reference in New Issue