Affected versions
Spring Boot 3.5.5 and 4.0.0-M2
Environment
Java 21 Windows 11 and Linux
Summary
In a Spring Boot application with observation enabled, a @Scheduled method logs with a trace ID as expected. However, when this method throws an exception, the exception is logged by TaskUtils.LoggingErrorHandler in the same thread without including the trace ID.
This inconsistency makes it difficult to correlate scheduled task failures with their corresponding traces or with earlier logs from the same task execution.
Expected behavior
All corresponding logs from the scheduled task, within and outside the @Scheduled method, are logged consistently with the same trace ID for proper correlation and observability.
Example Logs
2025-08-21T14:16:12.183+02:00 INFO 25048 --- [ scheduling-1] [68a70e0cfc771a5ef1c53fbefe459aa4-f1c53fbefe459aa4] de.example.Scheduler : scheduled with trace-id: 68a70e0cfc771a5ef1c53fbefe459aa4
2025-08-21T14:16:12.186+02:00 ERROR 25048 --- [ scheduling-1] [ ] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task
Minimal reproducible example
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Component
@RequiredArgsConstructor
@Slf4j
class Scheduler {
final Tracer tracer;
@Scheduled(fixedRate = 10_000)
public void scheduledWithTracer() {
log.info("scheduled with trace-id: {}", tracer.currentTraceContext().context().traceId());
throw new IllegalStateException("intended exception");
}
}
Repo with fully reproducible example
https://github.com/robeatoz/scheduler-with-trace-id
If I can help you in any way, please let me know. Thank you for your fantastic work!
Comment From: bclozel
Thanks for the sample application. I agree that this would be a generally useful addition. The problem is this logging is not performed a level above the actual instrumentation.
Scheduled tasks are instrumented at the level of the ScheduledAnnotationBeanPostProcessor, wrapping runnables as ScheduledMethodRunnable. This not only creates a meter for the task execution time, but also sets up an observation scope if tracing is configured. We chose to instrument scheduled tasks, because only those make sense from an application perspective: they are declared as annotated methods on Spring components, and we can collect useful data for the observation: the method and the enclosing class.
The error logging is here done by TaskUtils#decorateTaskWithErrorHandler which is called when submitting or scheduling a Runnable with a TaskScheduler. This is a much lower level contract, and while we could create a scope that would enclose the entire execution of the task + its error handling phase, instrumenting the tasks at this level does not bring much information for the actual observations. We are only given a Runnable.
The only way out of this would be to allow a special type of Runnable type that TaskScheduler could accept and detect. I'm not sure exposing the observability concerns at this level is a good tradeoff.
Pinging @jhoeller here just in case he has a better idea, otherwise I'm afraid we can only decline this enhancement request.