2222import brave .handler .MutableSpan ;
2323import brave .handler .SpanHandler ;
2424import brave .propagation .TraceContext ;
25+ import io .micrometer .context .ContextExecutorService ;
26+ import io .micrometer .context .ContextScheduledExecutorService ;
27+ import io .micrometer .context .ContextSnapshot ;
28+ import io .micrometer .context .ContextSnapshotFactory ;
2529import io .micrometer .tracing .Tracer ;
2630
31+ import java .util .concurrent .ExecutorService ;
32+ import java .util .concurrent .RejectedExecutionHandler ;
33+ import java .util .concurrent .ScheduledExecutorService ;
34+ import java .util .concurrent .ThreadFactory ;
35+ import java .util .function .Supplier ;
36+
2737import org .springframework .beans .factory .ObjectProvider ;
2838import org .springframework .beans .factory .config .ConfigurableListableBeanFactory ;
39+ import org .springframework .boot .autoconfigure .AutoConfigureBefore ;
2940import org .springframework .boot .autoconfigure .condition .ConditionalOnClass ;
3041import org .springframework .boot .autoconfigure .condition .ConditionalOnProperty ;
42+ import org .springframework .boot .autoconfigure .task .TaskExecutionAutoConfiguration ;
3143import org .springframework .context .annotation .Bean ;
3244import org .springframework .context .annotation .Configuration ;
3345import org .springframework .modulith .observability .ModuleEventListener ;
3446import org .springframework .modulith .observability .ModuleTracingBeanPostProcessor ;
3547import org .springframework .modulith .runtime .ApplicationModulesRuntime ;
48+ import org .springframework .scheduling .concurrent .ThreadPoolTaskScheduler ;
3649
3750/**
3851 * @author Oliver Drotbohm
3952 */
4053@ Configuration (proxyBeanMethods = false )
54+ @ AutoConfigureBefore (TaskExecutionAutoConfiguration .class )
4155@ ConditionalOnProperty (name = "management.tracing.enabled" , havingValue = "true" , matchIfMissing = true )
4256class ModuleObservabilityAutoConfiguration {
4357
@@ -53,6 +67,39 @@ static ModuleEventListener tracingModuleEventListener(ApplicationModulesRuntime
5367 return new ModuleEventListener (runtime , () -> tracer .getObject ());
5468 }
5569
70+ /**
71+ * Custom override of default {@link ThreadPoolTaskScheduler} to make sure asynchronous method invocations get the
72+ * {@link io.micrometer.tracing.handler.TracingObservationHandler.TracingContext} forwarded into threads spawned for
73+ * those methods. <em>The name of the bean is important for it to be picked up by the async invocation
74+ * infrastructure!</em>
75+ */
76+ @ Bean (name = "taskExecutor" , destroyMethod = "shutdown" )
77+ ThreadPoolTaskScheduler threadPoolTaskScheduler () {
78+
79+ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler () {
80+
81+ private static final long serialVersionUID = -3935299327010101697L ;
82+ private final Supplier <ContextSnapshot > captureAll = () -> ContextSnapshotFactory .builder ().build ().captureAll ();
83+
84+ @ Override
85+ protected ExecutorService initializeExecutor (ThreadFactory threadFactory ,
86+ RejectedExecutionHandler rejectedExecutionHandler ) {
87+
88+ return ContextExecutorService .wrap (super .initializeExecutor (threadFactory , rejectedExecutionHandler ),
89+ captureAll );
90+ }
91+
92+ @ Override
93+ public ScheduledExecutorService getScheduledExecutor () throws IllegalStateException {
94+ return ContextScheduledExecutorService .wrap (super .getScheduledExecutor (), captureAll );
95+ }
96+ };
97+
98+ threadPoolTaskScheduler .initialize ();
99+
100+ return threadPoolTaskScheduler ;
101+ }
102+
56103 /**
57104 * Brave-specific auto configuration.
58105 *
0 commit comments