Description
As discussed in #30089 and #29977, Spring Framework does not intend to instrument @Async
or @EventListener
methods for Observations. Creating dedicated observations would require:
- an observation name: "event.processing", "async.execution" could work
- a metric: measuring the execution time
- actionable metadata: here, we could collect the name of the method being called and possibly the event type
- meaningful semantics: that's where things don't fit. An event being processed or a method being executed asynchronously doesn't tell much about the use case; this is really about the "how?" but not the "what?". Some instances are probably worthy of instrumentation, but with a custom observation that says more about the use case, such as "email.notification.processing"
Also, many Spring applications can process a high number of application events. By default, those are designed to be processed quickly and synchronously within the publisher thread. Measuring processing time there will waste CPU cycles and yield very small measurements that are within the error margin.
On the other hand, there are cases where an existing observation is already active. For example, a web application receives a request and publishes an event as a result. If the dispatching of application events is done on a different Thread, the current context (i.e. ThreadLocals) is lost. This means that the observation trace is not propagated and that the logging context (the MDC information) is not present during execution.
Such use cases are covered by the Micrometer Context Propagation library. This issue is about offering an opt-in, flexible way of propagating context for @Async
and @EventListener
annotated methods. Some applications might want to apply this globally, while others probably want to apply it selectively in order not to introduce unnecessary overhead in the application.
We can introduce here a new TaskDecorator
implementation that calls the Context Propagation library. If the application uses an ExecutorService
directly, wrapping it with the io.micrometer.context.ContextExecutorService
is the best choice.