Introduction
For any Observation to happen, you need to register ObservationHandler
objects through an ObservationRegistry
. An ObservationHandler
reacts only to supported implementations of an Observation.Context
and can create timers, spans, and logs by reacting to the lifecycle events of an Observation, such as:
-
start
- Observation has been started. Happens when theObservation#start()
method gets called. -
stop
- Observation has been stopped. Happens when theObservation#stop()
method gets called. -
error
- An error occurred while observing. Happens when theObservation#error(exception)
method gets called. -
event
- An event happened when observing. Happens when theObservation#event(event)
method gets called. -
scope started
- Observation opens a Scope. The Scope must be closed when no longer used. Handlers can create thread local variables on start that are cleared upon closing of the scope. Happens when theObservation#openScope()
method gets called. -
scope stopped
- Observation stops a Scope. Happens when theObservation.Scope#close()
method gets called.
Whenever any of these methods is called, an ObservationHandler
method (such as onStart(T extends Observation.Context ctx)
, onStop(T extends Observation.Context ctx)
, and so on) is called. To pass state between the handler methods, you can use the Observation.Context
.
This is how the Observation state diagram looks like:
Observation Observation
Context Context
Created ----------> Started ----------> Stopped
This is how the Observation Scope state diagram looks like:
Observation
Context
Scope Started ----------> Scope Finished
To make it possible to debug production problems an Observation needs additional metadata such as key-value pairs (also known as tags). You can then query your metrics or distributed tracing backend by those tags to find the required data. Tags can be either of high or low cardinality.
High cardinality means that a pair will have an unbounded number of possible values. An HTTP URL is a good example of such a key value (such as /example/user1234 , /example/user2345 etc.). Low cardinality means that a key value will have a bounded number of possible values. A templated HTTP URL is a good example of such a key value (such as /example/{userId} ).
|
To separate Observation lifecycle operations from an Observation configuration (such as names and low and high cardinality tags), you can use the ObservationConvention
that provides an easy way of overriding the default naming conventions.
The following example uses the Observation API:
static class Example {
private final ObservationRegistry registry;
Example(ObservationRegistry registry) {
this.registry = registry;
}
void run() {
Observation.createNotStarted("foo", registry)
.lowCardinalityKeyValue("lowTag", "lowTagValue")
.highCardinalityKeyValue("highTag", "highTagValue")
.observe(() -> System.out.println("Hello"));
}
}
Calling observe(() → …) leads to starting the Observation, putting it in scope, running the lambda, putting an error on the Observation if one took place, closing the scope and stopping the Observation.
|
Building Blocks
In this section we will describe a high overview of Micrometer Observation components. You can read more about those in this part of the documentation.
Glossary
-
ObservationRegistry
- registry containingObservation
related configuration (e.g. handlers, predicates, filters) -
ObservationHandler
- handles lifecycle of anObservation
(e.g. create a timer on observation start, stop it on observation stop) -
ObservationFilter
- mutates theContext
before theObservation
gets stopped (e.g. add a high cardinality key-value with the cloud server region) -
ObservationPredicate
- condition to disable creation of anObservation
(e.g. don’t create observations with given key-value) -
Context
(actuallyObservation.Context
) - a mutable map attached to anObservation
, passed between handlers (that way you can pass state without doing any thread locals) -
ObservationConvention
- mean to separate Observation lifecycle (starting, stopping, opening scopes) from adding metadata (such as observation name, key-value pairs). That way the naming of observations and metadata handling becomes a configuration problem (e.g. adding key-values does not require changing instrumentation code, but changing the convention)
Usage Flow
┌──────────────────┐┌─────────────────┐┌────────────────────┐┌───────┐┌─────────────────────┐
│ObservationHandler││ObservationFilter││ObservationPredicate││Context││ObservationConvention│
└┬─────────────────┘└┬────────────────┘└┬───────────────────┘└┬──────┘└┬────────────────────┘
┌▽───────────────────▽──────────────────▽┐ │ │
│ObservationRegistry │ │ │
└┬───────────────────────────────────────┘ │ │
┌▽────────────────────────────────────────────────────────────▽────────▽┐
│Observation │
└───────────────────────────────────────────────────────────────────────┘
-
ObservationHandler
,ObservationFilter
, andObservationPredicate
get registered on theObservationRegistry
-
ObservationHandler
can be composed together (e.g. via theFirstMatchingCompositeObservationHandler
- that can be useful when you group multiple handlers of the same type, e.g.TracingHandler
orMeterHandler
)
-
-
ObservationRegistry
andContext
are mandatory to create anObservation
-
You create an
Observation
either with name,ObservationRegistry
, andContext
or withObservationRegistry
andObservationConvention
-
-
When
Observation
calls its lifecycle methods, allObservationHandlers
that pass thesupportsContext
check with the correspondingContext
will have their lifecycle methods executed (e.g. if a handler has aninstanceof SenderContext
check insupportsContext
then it will be called only for such types of contexts)