OpenTracing instrumentation for gRPC.
pom.xml
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-grpc</artifactId>
<version>VERSION</version>
</dependency>- Instantiate tracer
- Optionally register tracer with GlobalTracer:
GlobalTracer.register(tracer) - Create a
TracingServerInterceptor - Intercept a service
importio.opentracing.Tracer;
publicclassYourServer {
privateintport;
privateServerserver;
privatefinalTracertracer;
privatevoidstart() throwsIOException {
TracingServerInterceptortracingInterceptor = newTracingServerInterceptor(this.tracer);
// If GlobalTracer is used: TracingServerInterceptorserver = ServerBuilder.forPort(port)
.addService(tracingInterceptor.intercept(someServiceDef))
.build()
.start();
}
}- Instantiate a tracer
- Optionally register tracer with GlobalTracer:
GlobalTracer.register(tracer) - Create a
TracingClientInterceptor - Intercept the client channel
importio.opentracing.Tracer;
publicclassYourClient {
privatefinalManagedChannelchannel;
privatefinalGreeterGrpc.GreeterBlockingStubblockingStub;
privatefinalTracertracer;
publicYourClient(Stringhost, intport) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext(true)
.build();
TracingClientInterceptortracingInterceptor = newTracingClientInterceptor(this.tracer);
// If GlobalTracer is used: TracingClientInterceptorblockingStub = GreeterGrpc.newBlockingStub(tracingInterceptor.intercept(channel));
}
}A TracingServerInterceptor uses default settings, which you can override by creating it using a TracingServerInterceptor.Builder.
withOperationName(OperationNameConstructor constructor): Define how the operation name is constructed for all spans created for the intercepted service. Default sets the operation name as the name of the RPC method. More details in theOperation Namesection.withStreaming(): Logs to the server span whenever a message is received. Note: This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know.withVerbosity(): Logs to the server span additional events, such as message received, half close (client finished sending messages), and call complete. Default only logs if a call is cancelled.withTracedAttributes(ServerRequestAttribute... attrs): Sets tags on the server span in case you want to track information about the RPC call. See ServerRequestAttribute.java for a list of traceable request attributes.
TracingServerInterceptortracingInterceptor = newTracingServerInterceptor
.Builder(tracer)
.withStreaming()
.withVerbosity()
.withOperationName(newOperationNameConstructor() {
@Overridepublic <ReqT, RespT> StringconstructOperationName(MethodDescriptor<ReqT, RespT> method) {
// construct some operation name from the method descriptor
}
})
.withTracedAttributes(ServerRequestAttribute.HEADERS,
ServerRequestAttribute.METHOD_TYPE)
.build();A TracingClientInterceptor also has default settings, which you can override by creating it using a TracingClientInterceptor.Builder.
withOperationName(String operationName): Define how the operation name is constructed for all spans created for this intercepted client. Default is the name of the RPC method. More details in theOperation Namesection.withActiveSpanSource(ActiveSpanSource activeSpanSource): Define how to extract the current active span, if any. More details in theActive Span Sourcessection.withActiveSpanContextSource(ActiveSpanContextSource activeSpanContextSource): Define how to extract the current active span context, if any. More details in theActive Span Context Sourcessection.withStreaming(): Logs to the client span whenever a message is sent or a response is received. Note: This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know.withVerbosity(): Logs to the client span additional events, such as call started, message sent, half close (client finished sending messages), response received, and call complete. Default only logs if a call is cancelled.withTracedAttributes(ClientRequestAttribute... attrs): Sets tags on the client span in case you want to track information about the RPC call. See ClientRequestAttribute.java for a list of traceable request attributes.
importio.opentracing.Span;
TracingClientInterceptortracingInterceptor = newTracingClientInterceptor
.Builder(tracer)
.withStreaming()
.withVerbosity()
.withOperationName(newOperationNameConstructor() {
@Overridepublic <ReqT, RespT> StringconstructOperationName(MethodDescriptor<ReqT, RespT> method) {
// construct some operation name from the method descriptor
}
})
.withActiveSpanSource(newActiveSpanSource() {
@OverridepublicSpangetActiveSpan() {
// implement how to get the current active span, for example:returnOpenTracingContextKey.activeSpan();
}
})
.withTracingAttributes(ClientRequestAttribute.ALL_CALL_OPTIONS,
ClientRequestAttribute.HEADERS)
.build();In your server request handler, you can access the current active span for that request by calling
Spanspan = OpenTracingContextKey.activeSpan();This is useful if you want to manually set tags on the span, log important events, or create a new child span for internal units of work. You can also use this key to wrap these internal units of work with a new context that has a user-defined active span.
For example:
Tracertracer = ...;
// some unit of internal work that you want to traceRunnableinternalWork = someInternalWork// a wrapper that traces the work of the runnableclassTracedRunnableimplementsRunnable {
Runnablework;
Tracertracer;
TracedRunnable(Runnablework, Tracertracer) {
this.work = work;
this.tracer = tracer;
}
publicvoidrun() {
// create a child span for the current active spanSpanspan = tracer
.buildSpan("internal-work")
.asChildOf(OpenTracingContextKey.activeSpan())
.start();
// create a new context with the child span as the active spanContextcontextWithNewSpan = Context.current()
.withValue(OpenTracingContextKey.get(), span);
// wrap the original work and run itRunnabletracedWork = contextWithNewSpan.wrap(this.work);
tracedWork.run();
// make sure to finish any manually created spans!span.finish();
}
}
RunnabletracedInternalWork = newTracedRunnable(internalWork, tracer);
tracedInternalWork.run();The default operation name for any span is the RPC method name (io.grpc.MethodDescriptor.getFullMethodName()). However, you may want to add your own prefixes, alter the name, or define a new name. For examples of good operation names, check out the OpenTracing semantics.
To alter the operation name, you need to add an implementation of the interface OperationNameConstructor to the TracingClientInterceptor.Builder or TracingServerInterceptor.Builder. For example, if you want to add a prefix to the default operation name of your ClientInterceptor, your code would look like this:
TracingClientInterceptorinterceptor = TracingClientInterceptor.Builder ...
.withOperationName(newOperationNameConstructor() {
@Overridepublic <ReqT, RespT> StringconstructOperationName(MethodDescriptor<ReqT, RespT> method) {
return"your-prefix" + method.getFullMethodName();
}
})
.with....
.build()If you want your client to continue a trace rather than starting a new one, then you can tell your TracingClientInterceptor how to extract the current active span by building it with your own implementation of the interface ActiveSpanSource. This interface has one method, getActiveSpan, in which you will define how to access the current active span.
For example, if you're creating the client in an environment that has the active span stored in a global dictionary-style context under OPENTRACING_SPAN_KEY, then you could configure your Interceptor as follows:
importio.opentracing.Span;
TracingClientInterceptorinterceptor = newTracingClientInterceptor
.Builder(tracer)
...
.withActiveSpanSource(newActiveSpanSource() {
@OverridepublicSpangetActiveSpan() {
returnContext.get(OPENTRACING_SPAN_KEY);
}
})
...
.build();We also provide two built-in implementations:
ActiveSpanSource.GRPC_CONTEXTuses the currentio.grpc.Contextand returns the active span forOpenTracingContextKey.ActiveSpanSource.NONEalways returns null as the active span, which means the client will retrieve the span fromio.opentracing.Tracer.activeSpan(). This is the default active span source.
Instead of ActiveSpanSource it's possible to use ActiveSpanContextSource if span is not available
importio.opentracing.Span;
TracingClientInterceptorinterceptor = newTracingClientInterceptor
.Builder(tracer)
...
.withActiveSpanContextSource(newActiveSpanContextSource() {
@OverridepublicSpanContextgetActiveSpanContext() {
returnContext.get(OPENTRACING_SPAN_CONTEXT_KEY);
}
})
...
.build();We also provide two built-in implementations:
ActiveSpanContextSource.GRPC_CONTEXTuses the currentio.grpc.Contextand returns the active span context forOpenTracingContextKey.ActiveSpanContextSource.NONEalways returns null as the active span context, which means the client will retrieve the span fromio.opentracing.Tracer.activeSpan().context(). This is the default active span context source.
If you want to add custom tags or logs to the server and client spans, then you can implement the
ClientSpanDecorator, ClientCloseDecorator, ServerSpanDecorator, and ServerCloseDecorator interfaces.
Multiple different decorators may be added to the builder.
TracingClientInterceptorclientInterceptor = newTracingClientInterceptor
.Builder(tracer)
...
.withClientSpanDecorator(newClientSpanDecorator() {
@OverridepublicvoidinterceptCall(Spanspan, MethodDescriptormethod, CallOptionscallOptions) {
span.setTag("some_tag", "some_value");
span.log("Example log");
}
})
.withClientCloseDecorator(newClientCloseDecorator() {
@Overridepublicvoidclose(Spanspan, Statusstatus, Metadatatrailers) {
span.setTag("some_other_tag", "some_other_value");
}
})
...
.build();
TracingServerInterceptorserverInterceptor = newTracingServerInterceptor
.Builder(tracer)
...
.withServerSpanDecorator(newServerSpanDecorator() {
@OverridepublicvoidinterceptCall(Spanspan, ServerCallcall, Metadataheaders) {
span.setTag("some_tag", "some_value");
span.log("Intercepting server call");
}
})
.withServerCloseDecorator(newServerCloseDecorator() {
@Overridepublicvoidclose(Spanspan, Statusstatus, Metadatatrailers) {
span.setTag("some_other_tag", "some_other_value");
}
})
...
.build();Although we provide TracingServerInterceptor.intercept(service) and TracingClientInterceptor.intercept(channel) methods, you don't want to use these if you're chaining multiple interceptors. Instead, use the following code (preferably putting the tracing interceptor at the top of the interceptor stack so that it traces the entire request lifecycle, including other interceptors):
server = ServerBuilder.forPort(port)
.addService(ServerInterceptors.intercept(service, someInterceptor,
someOtherInterceptor, TracingServerInterceptor))
.build()
.start();blockingStub = GreeterGrpc.newBlockingStub(ClientInterceptors.intercept(channel,
someInterceptor, someOtherInterceptor, TracingClientInterceptor));