OpenTracing Proxy is a library that adds the ability to observe and react to span activities in OpenTracing.
- Technology stack: Java 8+, OpenTracing
- Status: Under development and used in production
- active spans are not proxied
- operation name not customizable
- scopes are not observable
- mutability
- stateful (each span has a copy of all tags)
Tracer tracer = new ProxyTracer(original)
.with(new Rename(CaseFormat.LOWER_UNDERSCORE))
.with(new LogCorrelation()
.withTraceId("trace_id")
.withSpanId("span_id")
.withBaggage("flow_id"))
.with(new AutoTagging("flow_id"));
- Customizable operation name
- Observe span/scope lifecycle events
- start and finish
- activate and close (scope)
- new tags
- new logs
- new baggage
- Built-in, reusable, high-level patterns
- Immutability
- Stateless
- Java 8 or higher
- OpenTracing
- SLF4J (optional)
Add the following dependency to your project:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>opentracing-proxy</artifactId>
<version>${opentracing-proxy.version}</version>
</dependency>
new ProxyTracer(tracer)
.with(plugin)
.with(anotherPlugin)
.with(yetAnotherPlugin);
Registering a Naming
plugin allows to customize the operation name of all spans:
new ProxyTracer(tracer)
.with(naming(String::toLowerCase));
A slightly more sophisticated way to is provided by Rename
:
new Rename(CaseFormat.LOWER_HYPHEN)
It translates operations names into a specific CaseFormat
. The original case format is detected automatically. Rename
allows to produce a consistent naming convention of operation names across different instrumentation libraries.
The majority of available Plugin
types are listeners of various kinds. The following list shows all listeners and when they are being invoked:
BaggageListener
, new baggage itemLogListener
, new log fieldsScopeListener
, scope activate and closeSpanListener
, span start and finishTagListener
, new tags
All listeners are reactive in the sense that they are being invoked after the fact. They can't influence the operation that was performed, but merely react to it. Listeners are generally useful in order to perform some additional task based on a relevant span activity. The most useful and common tasks can be found in the following section.
Interceptors, compared to listeners are not merely reacting to an action but can actively participate. Interceptors allow to replace or decorate certain aspects, e.g. SpanBuilder
or Span
instances and keep state in each decorator. This pattern is less frequently needed than listeners, but can be very powerful when needed.
Correlating log messages (referring to local log files, not OpenTracing span logs) is a very common use case. OpenTracing exposes the Trace-Context identifiers (Trace and Span ID) for this very purpose.
The built-in LogCorrelation
plugin combines the reactive abilities of a ScopeListener
and a BaggageListener
with the MDC to create an out-of-the-box solution for log correlation. Every active Span
will be correlated to the logs by pushing the relevant pieces to the mapped diagnostic context.
The following setup configures the plugin to expose the Trace ID as trace_id
, the Span ID as span_id
and the baggage item flow_id
as-is to the MDC.
Tracer tracer = new ProxyTracer(original)
.with(new LogCorrelation()
.withTraceId("trace_id")
.withSpanId("span_id")
.withBaggage("flow_id"));
It's also possible to map request-id
baggage item to the request_id
MDC:
Tracer tracer = new ProxyTracer(original)
.with(new LogCorrelation()
.withBaggage("request-id", "request_id"));
OpenTracing offers tags and baggage, both of which have very different characteristics.
- Clients can't read them
- Shipped to and indexed by a central collector
- Not propagated to downstream dependencies
- Client can read them
- Not necessarily shipped to or indexed by a central collector (vendor specific)
- Propagated to downstream dependencies
Due to the nature of those two constructs there are cases where one needs both of them. One example is Zalando's Flow ID which we a) want to index and query by (tag) and b) need to propagate downstream (baggage).
One way to solve that is the AutoTagging
plugin which automatically translates certain baggage items also into tags. Any instrumentation code would only need to deal with the baggage item and would get the tag automatically for free:
Tracer tracer = new ProxyTracer(original)
.with(new AutoTagging("flow_id"));
The AutoBaggage
is pretty much the same as Auto-Tagging but in reverse, i.e. specific tags are automatically translated into baggage items.
SpanBuilder
since there is no way to add baggage items yet. It's therefore highly encouraged to only set tags on Span
instances directly.
OpenTracing does not propagate tags to child spans, unless they are explicitly set on the child span. Since tags can't be extracted from a span one would need to carry relevant tags out of band from parent span to child span by hand. That can be a cumbersome task if both locations are relative far away from each other, e.g. a servlet filter (parent span) and a client interceptor (child span).
The TagPropagation
plugin eases this pain by automatically carrying over certain tags from parent to child spans:
Tracer tracer = new ProxyTracer(original)
.with(new TagPropagation("payment_method", "sales_channel", "country"));
Tag propagation in will not carry tags across application boundaries, i.e. they are not part of the span context and won't be transferred like baggage would.
If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.
To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For more details, check the contribution guidelines.