My goal is to have only @Scheduled annotation on scheduled method and tracing should happen automatically (no matter if it's log.info() or exception).

I tried to achieve that by creating custom TaskDecorator but it doesn't work.

Scheduler configuration:

@Configuration
@RequiredArgsConstructor
@EnableScheduling
public class SchedulerConfiguration {
    private final ObservationRegistry observationRegistry;

    @Value("${spring.task.scheduling.pool.size}")
    private Integer taskSchedulingPoolSize;

    @Bean
    public TaskScheduler taskScheduler() {
        ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler(new ScheduledThreadPoolExecutor(taskSchedulingPoolSize));
        scheduler.setTaskDecorator(new ExecutorTaskDecorator(observationRegistry));
        scheduler.setErrorHandler(new ExceptionHandler());

        return scheduler;
    }
}

Observation configuration:

@Configuration
public class ObservationConfiguration {
    @Bean
    ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
        return new ObservedAspect(observationRegistry);
    }
}

Exception handler:

@Slf4j
public class ExceptionHandler implements ErrorHandler {
    @Override
    public void handleError(Throwable t) {
        log.error("Task threw an exception: {}", t.getMessage(), t);
    }
}
@Scheduled(initialDelay = 0, fixedDelay = 1000")
public void start() {
   throw new RunTimeException("test");
}

1st approach of task decorator:

public class ExecutorTaskDecorator implements TaskDecorator {
    private final ObservationRegistry observationRegistry;

    public ExecutorTaskDecorator(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @Override
    public Runnable decorate(Runnable runnable) {
        return Observation.createNotStarted("task.run", observationRegistry)
                .observe(() -> runnable::run);
    }
}

2nd approach of task decorator:

public class ExecutorTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        return runWithObserved(runnable);
    }

    @Observed
    private Runnable runWithObserved(Runnable runnable) {
        return () -> runnable.run();
    }
}

Both approaches don't work.

TraceId and SpanId occurs only when I add @Observed to start() method and wrap it with try catch block.

I'm using spring boot 3.1.3, spring-boot-starter-actuator (3.1.3) and micrometer-tracing-bridge-brave (1.1.4).

Am I missing something? Is it even possible to use it like that? For HTTP requests everything works fine.

1

There are 1 answers

1
clukz On

Thanks to @m-deinum advice I got this working using aspect.

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class ScheduledAspect {
    private final ObservationRegistry observationRegistry;

    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void annotatedWithScheduled() {}

    @Around("annotatedWithScheduled()")
    public Object wrapScheduled(ProceedingJoinPoint pjp) {
        var methodName = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();

        return Observation.createNotStarted(methodName, observationRegistry)
                .observe(() -> proceed(pjp));
    }

    private Object proceed(ProceedingJoinPoint pjp) {
        try {
            return pjp.proceed();
        } catch (Throwable t) {
            log.error("Task threw an exception: {}", t.getMessage(), t);
        }
        return null;
    }
}