This library is a Java platform API for OpenTracing.
In order to understand the Java platform API, one must first be familiar with the OpenTracing project and terminology more specifically.
Initialization is OpenTracing-implementation-specific. Generally speaking, the pattern is to initialize a Tracer
once for the entire process and to use that Tracer
for the remainder of the process lifetime. It is a best practice to set the GlobalTracer, even if also making use of cleaner, more modern dependency injection. (See the next section below for rationale)
Where possible, use some form of dependency injection (of which there are many) to access the Tracer
instance. For vanilla application code, this is often reasonable and cleaner for all of the usual DI reasons.
That said, instrumentation for packages that are themselves statically configured (e.g., JDBC drivers) may be unable to make use of said DI mechanisms for Tracer
access, and as such they should fall back on GlobalTracer. By and large, OpenTracing instrumentation should always allow the programmer to specify a Tracer
instance to use for instrumentation, though the GlobalTracer is a reasonable fallback or default value.
For any thread, at most one Span
may be "active". Of course there may be many other Spans
involved with the thread which are (a) started, (b) not finished, and yet (c) not "active": perhaps they are waiting for I/O, blocked on a child Span, or otherwise off of the critical path.
It's inconvenient to pass an active Span
from function to function manually, so OpenTracing requires that every Tracer
contains a ScopeManager
that grants access to the active Span
through a Scope
. Any Span
may be transferred to another callback or thread, but not Scope
; more on this below.
Access to the active span is straightforward:
io.opentracing.Tracer tracer = ...;
...
Scope scope = tracer.scopeManager().active();
if (scope != null) {
scope.span().log("...");
}
The common case starts a Scope
that's automatically registered for intra-process propagation via ScopeManager
.
Note that startActive(true)
finishes the span on Scope.close()
.
Use it carefully because the try-with-resources
construct finishes the span before
the catch
or finally
blocks are executed, which makes logging exceptions and
setting tags impossible. It is recommended to start the span and activate it later in try-with-resources
.
This makes the span available in catch and finally blocks.
io.opentracing.Tracer tracer = ...;
...
Span span = tracer.buildSpan("someWork").start();
try (Scope scope = tracer.scopeManager().activate(span, false)) {
// Do things.
} catch(Exception ex) {
Tags.ERROR.set(span, true);
span.log(Map.of(Fields.EVENT, "error", Fields.ERROR_OBJECT, ex, Fields.MESSAGE, ex.getMessage()));
} finally {
span.finish();
}
The following code uses try-with-resources
to finish the span.
io.opentracing.Tracer tracer = ...;
...
try (Scope scope = tracer.buildSpan("someWork").startActive(true)) {
// Do things.
//
// `scope.span()` allows us to pass the `Span` to newly created threads.
} catch(Exception ex) {
// cannot record the exception in the span since scope is not accessible and span is finished
}
If there is a Scope
, it will act as the parent to any newly started Span
unless
the programmer invokes ignoreActiveSpan()
at buildSpan()
time or specified parent context explicitly:
io.opentracing.Tracer tracer = ...;
...
Scope scope = tracer.buildSpan("someWork").ignoreActiveSpan().startActive();
Consider the case where a Span
's lifetime logically starts in one thread and ends in another. For instance, the Span's own internal timing breakdown might look like this:
[ ServiceHandlerSpan ]
|·FunctionA·|·····waiting on an RPC······|·FunctionB·|
---------------------------------------------------------> time
The "ServiceHandlerSpan"
is active while it's running FunctionA and FunctionB, and inactive while it's waiting on an RPC (presumably modelled as its own Span, though that's not the concern here).
The ScopeManager
API makes it possible to fetch the span()
in FunctionA
and re-activate it in FunctionB
. Note that every Tracer
contains a ScopeManager
. These are the steps:
- Start a
Span
via eitherstartManual
orstartActive(false)
to prevent theSpan
from being finished uponScope
deactivation. - In the closure/
Runnable
/Future
/etc itself, invoketracer.scopeManager().activate(span, false)
to re-activate theSpan
and get a newScope
, thendeactivate()
it when theSpan
is no longer active (or use try-with-resources for less typing). - In the closure/
Runnable
/Future
/etc where the end of the task is reached, invoketracer.scopeManager().activate(span, true)
to re-activate theSpan
and have the newScope
close theSpan
automatically.
For example:
io.opentracing.Tracer tracer = ...;
...
// STEP 1 ABOVE: start the Scope/Span
try (Scope scope = tracer.buildSpan("ServiceHandlerSpan").startActive(false)) {
...
final Span span = scope.span();
doAsyncWork(new Runnable() {
@Override
public void run() {
// STEP 2 ABOVE: reactivate the Span in the callback, passing true to
// startActive() if/when the Span must be finished.
try (Scope scope = tracer.scopeManager().activate(span, false)) {
...
}
}
});
}
Observe that passing Scope
to another thread or callback is not supported. Only Span
can be used under this scenario.
In practice, all of this is most fluently accomplished through the use of an OpenTracing-aware ExecutorService
and/or Runnable
/Callable
adapter; they factor out most of the typing.
For users supporting instrumentation code using Opentracing 0.30, there is a 0.30 compatibility package that can be used to wrap a 0.31 Tracer
and expose it as a 0.30 Tracer
:
io.opentracing.Tracer upstreamTracer = ...;
io.opentracing.v_030.Tracer tracer = new TracerShim(upstreamTracer);
try (ActiveSpan span = tracer.buildSpan("ServiceHandlerSpan").startActive()) {
...
}
This project has a working design of interfaces for the OpenTracing API. There is a MockTracer to facilitate unit-testing of OpenTracing Java instrumentation.
Packages are deployed to Maven Central under the io.opentracing
group.
This is a maven project, and provides a wrapper, ./mvnw
to pin a consistent
version. Run ./mvnw clean install
to build, run tests, and create jars.
This wrapper was generated by mvn -N io.takari:maven:wrapper -Dmaven=3.5.0
See Contributing for matters such as license headers.