Configuring with Micrometer Observation
Handler Configuration
For Micrometer Tracing to work with Micrometer Observation, you need to add a tracing-related ObservationHandler
.
The following example shows how to add and use a single DefaultTracingObservationHandler
:
Tracer tracer = Tracer.NOOP; // The real tracer will come from your tracer
// implementation (Brave /
// OTel)
Propagator propagator = Propagator.NOOP; // The real propagator will come from
// your tracer implementation (Brave /
// OTel)
MeterRegistry meterRegistry = new SimpleMeterRegistry();
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig()
// assuming that micrometer-core is on the classpath
.observationHandler(new DefaultMeterObservationHandler(meterRegistry))
// we set up a first matching handler that creates spans - it comes from
// Micrometer
// Tracing. We set up spans for sending and receiving data over the wire
// and a default one
.observationHandler(new ObservationHandler.FirstMatchingCompositeObservationHandler(
new PropagatingSenderTracingObservationHandler<>(tracer, propagator),
new PropagatingReceiverTracingObservationHandler<>(tracer, propagator),
new DefaultTracingObservationHandler(tracer)));
// Creating and starting a new observation
// via the `DefaultTracingObservationHandler` that will create a new Span and
// start it
Observation observation = Observation.start("my.operation", registry)
.contextualName("This name is more readable - we can reuse it for e.g. spans")
.lowCardinalityKeyValue("this.tag", "will end up as a meter tag and a span tag")
.highCardinalityKeyValue("but.this.tag", "will end up as a span tag only");
// Put the observation in scope
// This will result in making the previously created Span, the current Span - it's
// in ThreadLocal
try (Observation.Scope scope = observation.openScope()) {
// Run your code that you want to measure - still the attached Span is the
// current one
// This means that e.g. logging frameworks could inject to e.g. MDC tracing
// information
yourCodeToMeasure();
}
finally {
// The corresponding Span will no longer be in ThreadLocal due to
// try-with-resources block (Observation.Scope is an AutoCloseable)
// Stop the Observation
// The corresponding Span will be stopped and reported to an external system
observation.stop();
}
You can also use a shorter version to perform measurements by using the observe
method:
ObservationRegistry registry = ObservationRegistry.create();
Observation.createNotStarted("my.operation", registry)
.contextualName("This name is more readable - we can reuse it for e.g. spans")
.lowCardinalityKeyValue("this.tag", "will end up as a meter tag and a span tag")
.highCardinalityKeyValue("but.this.tag", "will end up as a span tag only")
.observe(this::yourCodeToMeasure);
This will result in the following Micrometer Metrics:
Gathered the following metrics
Meter with name <my.operation> and type <TIMER> has the following measurements
<[
Measurement{statistic='COUNT', value=1.0},
Measurement{statistic='TOTAL_TIME', value=1.011949454},
Measurement{statistic='MAX', value=1.011949454}
]>
and has the following tags <[tag(this.tag=will end up as a meter tag and a span tag)]>
It also results in the following trace view in (for example) Zipkin:
Ordered Handler Configuration
Micrometer Tracing comes with multiple ObservationHandler
implementations.
To introduce ordering, you can use the ObservationHandler.AllMatchingCompositeObservationHandler
to run logic for all ObservationHandler
instances that match the given predicate and ObservationHandler
. Use FirstMatchingCompositeObservationHandler
to run logic only for the first ObservationHandler
that matches the predicate.
The former can group handlers and the latter can be chosen to (for example) run only one matching TracingObservationHandler
.
Context Propagation with Micrometer Tracing
To make Context Propagation work with Micrometer Tracing, you need to manually register the proper ThreadLocalAccessor
, as follows:
ContextRegistry.getInstance().registerThreadLocalAccessor(new ObservationAwareSpanThreadLocalAccessor(tracer));
ContextRegistry.getInstance()
.registerThreadLocalAccessor(new ObservationAwareBaggageThreadLocalAccessor(registry, tracer));
The ObservationAwareSpanThreadLocalAccessor
is required to propagate manually created spans (not the ones that are governed by Observations). The ObservationAwareBaggageThreadLocalAccessor
is required to propagate baggage created by the user.
With Project Reactor one should set the values of Observation
, Span
or BaggageToPropagate
in the Reactor Context as follows:
// Setup example
ContextRegistry contextRegistry = ContextRegistry.getInstance();
ObservationAwareSpanThreadLocalAccessor accessor;
ObservationAwareBaggageThreadLocalAccessor observationAwareBaggageThreadLocalAccessor;
accessor = new ObservationAwareSpanThreadLocalAccessor(observationRegistry, getTracer());
observationAwareBaggageThreadLocalAccessor = new ObservationAwareBaggageThreadLocalAccessor(observationRegistry,
getTracer());
contextRegistry.loadThreadLocalAccessors()
.registerThreadLocalAccessor(accessor)
.registerThreadLocalAccessor(observationAwareBaggageThreadLocalAccessor);
Hooks.enableAutomaticContextPropagation();
// Usage example
Hooks.enableAutomaticContextPropagation();
Observation observation = Observation.start("parent", observationRegistry);
List<String> hello = Mono.just("hello")
.subscribeOn(Schedulers.single())
.flatMap(s -> {
Mono<List<String>> mono = Mono.defer(() -> Mono.just(Arrays.asList(
getTracer().getBaggage("tenant").get(),
getTracer().getBaggage("tenant2").get())
));
return mono.subscribeOn(Schedulers.parallel())
.contextWrite(ReactorBaggage.append("tenant", s + ":baggage")); // Appends baggage to existing one (tenant2:baggage2)
})
.contextWrite(Context.of(ObservationThreadLocalAccessor.KEY, observation, // Puts observation to Reactor Context
ObservationAwareBaggageThreadLocalAccessor.KEY, new BaggageToPropagate("tenant2", "baggage2") // Puts baggage to Reactor Context
))
.block();
Exemplars
To add support for exemplars instead of using the DefaultMeterObservationHandler
you should use the TracingAwareMeterObservationHandler
, as follows:
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig()
// Don't register the DefaultMeterObservationHandler...
// .observationHandler(new DefaultMeterObservationHandler(meterRegistry))
// ...instead register the tracing aware version
.observationHandler(new TracingAwareMeterObservationHandler<>(
new DefaultMeterObservationHandler(meterRegistry), tracer));