8

This is my code :

 @Scheduled(cron = "30 3 * * * *")
    public void myCron() {
        //we don't care what we do here
    }

I want to know if it is possible to add a tracking id (or other information) on my @Scheduled automatically.

The id will be different each time @Scheduled is triggered.

I want to do this to avoid to duplicate code like :

@Scheduled(cron = "10 3 * * * *")
public void myCron() {
    MDC.put("myId", UUID.randomUUID().toString());
    //we don't care what we do here
}
@Scheduled(cron = "30 3 * * * *")
public void mySecondCron() {
    MDC.put("myId", UUID.randomUUID().toString());
    //we don't care what we do here
}

I tired to implements SchedulingConfigurer but SchedulingConfigurer#configureTasks is too late too add behavior on taks because the task (runnable) is already created

Thanks

ahammani
  • 83
  • 1
  • 5
  • Welcome to `StackOverflow`, please be a bit more specific when asking a question: *What have you tried so far with a code example? ([I downvoted because there is no code](http://idownvotedbecau.se/nocode/))* / *What do you expect?* / *What error do you get?* **For Help take a look at "[How to ask](https://stackoverflow.com/help/how-to-ask)"** – Hille Dec 21 '17 at 09:39
  • As you can see from docs, you cannot put it in `@Scheduled` annotation. Just to be clear, you want to schedule multiple tasks at the same time, and you want to differentiate between those two in logs. is that correct? – pvpkiran Dec 21 '17 at 10:00
  • @pvpkiran i want to schedule multiple task but i don't care if it's on the same time. I want some "ScheduledDecorator" to add behavior before run the task. And yes i want to differentiate them in logs – ahammani Dec 21 '17 at 10:03
  • 1
    what kinda behaviour? – pvpkiran Dec 21 '17 at 10:05
  • @pvpkiran This kind `MDC.put("myId", UUID.randomUUID().toString());` – ahammani Dec 21 '17 at 10:08
  • 1
    Honestly I can't understand what you're trying to achieve. Do you want the same 'id' in your two crons or what exactly? – prettyvoid Dec 21 '17 at 10:32
  • @prettyvoid Sorry if i doesn't explain it well. What i want is to track all my scheduled event. That's mean if `myCron()` is triggered, i want to associate it with an id "Id1". If `myCron()` is triggered a second time i want to associate it with an other id like "Id42". Is it more clear ? – ahammani Dec 21 '17 at 10:40
  • @ahammani so just generate a uuid inside the cron? That way each trigger will have a unique id, no? – prettyvoid Dec 21 '17 at 10:47
  • @prettyvoid yes i can do that and it works. But if someone add an other `@Scheduled` and forgot to generate an uuid inside the cron then we lost the tracking. I want a generic/automatic way to do what you said – ahammani Dec 21 '17 at 10:50

2 Answers2

6

You can try to implement custom TaskScheduler and register it in SchedulingConfigurer.configureTasks. ConcurrentTaskScheduler can be considered as an example. Unfortunately this class isn't well-designed for inheritance, otherwise decorateTask method will be protected. So you need to override all methods to add one additional Runnable decorator with your logic. Something like this one :

@Configuration
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer {

    public static class MyTaskScheduler extends ConcurrentTaskScheduler {

        public MyTaskScheduler() {
        }

        public MyTaskScheduler(ScheduledExecutorService scheduledExecutor) {
            super(scheduledExecutor);
        }

        public MyTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
            super(concurrentExecutor, scheduledExecutor);
        }

        // TODO override other methods

        @Override
        public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
            return super.schedule(decorateTask(task), trigger);
        }

        private Runnable decorateTask(Runnable task) {
            // not 100% sure about safety of this cast
            return new MyRunnable((ScheduledMethodRunnable) task);
        }

        private static class MyRunnable implements Runnable {

            private final ScheduledMethodRunnable runnable;
            private final AtomicLong counter = new AtomicLong();

            public MyRunnable(ScheduledMethodRunnable runnable) {
                this.runnable = runnable;
            }

            @Override
            public void run() {
                System.out.println(runnable.getMethod().toGenericString() + " " + counter.incrementAndGet());
                runnable.run();
            }
        }
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        TaskScheduler taskScheduler = new MyTaskScheduler(Executors.newScheduledThreadPool(10));
        taskRegistrar.setTaskScheduler(taskScheduler);
    }

    @Scheduled(cron = "0/1 * * * * *")
    public void test() {
        System.out.println("Running task in thread " + Thread.currentThread().getId());
    }
}
Mikita Harbacheuski
  • 2,193
  • 8
  • 16
0

You can also use AOP to intercept code before it enters any method annotated with @Scheduled and set the MDC. For good measure, it clears the MDC upon exiting the method.

Note the atExecutionTimeInMyNamespace pointcut, where you can optionally put in your own package namespace to limit to just use of @Scheduled in your own code (i.e. excluding its use in any 3rd party libs, however unlikely that may be).

@Aspect
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ScheduledTaskTracingAspect {

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

  @Pointcut("execution(* com.mycompany..*(..))")
  public void atExecutionTimeInMyNamespace() {}

  @Around("methodAnnotatedWithScheduled() && atExecutionTimeInMyNamespace()")
  public Object connectionAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

    MDC.put("myId", UUID.randomUUID().toString());

    try {
      return joinPoint.proceed();
    }
    finally {
      // Might as well clear all the MDC, not just the "myId"
      MDC.clear();
    }
  }

}
David Siegal
  • 4,518
  • 2
  • 17
  • 15