dcolazin
dcolazin

Reputation: 1074

How to retrieve the taskExecutionId during a job in Spring Batch?

(For logging purposes,) I need to retrieve the (task) execution id inside a job execution in Spring Cloud Data Flow. In particular, I have access to several "job" objects (JobLauncher, JobExplorer, JobRepository, JobExecution...), which don't seem to keep a correlation to the task. Moreover, something like @Value("{$spring.cloud.task.executionid:}") doesn't seem to work.

Upvotes: 1

Views: 806

Answers (2)

Henning
Henning

Reputation: 3889

You can use a Task Execution Listener.

You can let any bean implement TaskExecutionListener (or use the annotations @BeforeTask, @AfterTask, @FailedTask) and take the id from the TaskExecution that is passed as argument to its methods.

Upvotes: 2

mr.Penguin
mr.Penguin

Reputation: 1619

I don't know what you are willing to do exactly, but you could use spring AOP and log4j2 to achieve such task.

Start by adding log4j2 to your project and edit the log4j config file (log4j2.xml) where you could log the id of the running threads by specifying that in the pattern tag (|thread id:%tid|) :

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
    <Appenders>
        <Console name="LogToConsole" target="SYSTEM_OUT">
            <PatternLayout disableAnsi="false"  charset="UTF-8" pattern="%highlight{%-5p|%d{ISO8601}{GMT}|thread id:%tid|%X{Slf4jMDCFilter.UUID}| [%t] %logger{36} - %msg%n}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=blue, DEBUG=green bold, TRACE=blue}"/>
        </Console>
    </Appenders>
    <Loggers>
        <!-- avoid duplicated logs with additivity=false -->
        <Logger name="com.obs.dqsc.api" level="info" additivity="false">
            <AppenderRef ref="LogToConsole"/>
        </Logger>
        <Root level="info"  additivity="false">
            <AppenderRef ref="LogToConsole"/>
        </Root>
    </Loggers>
</Configuration>

And create a class for aspect login matter, I've already created one for my case that logs everything using a generic method, you could use it:

@Aspect
@Component
public class LoggingAspect {
    /**
     * @param joinPoint the execution of a method in a given layer
     * @return the execution result after proceeding the joinPoint
     * @throws Throwable if an exception occurred while proceeding a joinPoint
     */
    @Around("com.obs.dqsc.api.config.AspectConfig.allLayers() && com.obs.dqsc.api.config.AspectConfig.notToLog()")
    public Object logMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        final Class<?> targetClass = joinPoint.getTarget().getClass();
        final Logger logger = LoggerFactory.getLogger(targetClass);
        final String className = targetClass.getSimpleName();

        logger.info(getPreMessage(joinPoint, className));

        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        final Object retVal = joinPoint.proceed();
        stopWatch.stop();

        logger.info(getPostMessage(joinPoint, className, stopWatch.getTotalTimeMillis()));
        return retVal;
    }

    /**
     * @param joinPoint the execution of a method in a given layer
     * @param className the class where logger is based
     * @return the message to be printed in log4j appender
     */
    private static String getPreMessage(final JoinPoint joinPoint, final String className) {
        final StringBuilder builder = new StringBuilder()
                .append("Entered in ").append(className).append(".")
                .append(joinPoint.getSignature().getName())
                .append("(");
        appendTo(builder, joinPoint);
        return builder
                .append(")")
                .toString();
    }

    /**
     * @param joinPoint the execution of a method in a given layer
     * @param className the class where logger is based
     * @return the message to be printed in log4j appender
     */
    private static String getPostMessage(final JoinPoint joinPoint, final String className, final long millis) {
        return "Exit from " + className + "." +
                joinPoint.getSignature().getName() +
                "(..); Execution time: " +
                millis +
                " ms;"
                ;
    }

    /**
     * @param builder   to accumulate parameters of a method in one string
     * @param joinPoint the execution of a method
     */
    private static void appendTo(final StringBuilder builder, final JoinPoint joinPoint) {
        final Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (i != 0) {
                builder.append(", ");
            }
            builder.append(args[i]);
        }
    }

}

You will need also to create join points for the class above where you specify the packages or layers in your application you want to inspect:

@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.obs.dqsc.api.controller package or any sub-package
     * under that.
     */
    @Pointcut("within(com.obs.dqsc.api.controller..*)")
    public void initControllerLayer() {
        //nested comment to bypass sonar role
    }

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.obs.dqsc.api.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.obs.dqsc.api.service..*)")
    public void inServiceLayer() {
        //nested comment to bypass sonar role
    }

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.obs.dqsc.api.repository package or any sub-package
     * under that.
     */
    @Pointcut("within(com.obs.dqsc.api.repository..*)")
    public void initRepositoryLayer() {
        //nested comment to bypass sonar role
    }

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     * <p>
     * If you group service interfaces by functional area (for example,
     * in packages com.obs.dqsc.api.repository or com.obs.dqsc.api.service) then
     * the pointcut expression "execution(* com.obs.dqsc.api.service.*.*(..))"
     * could be used instead.
     * <p>
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.obs.dqsc.api.service.*.*(..))")
    public void businessServiceOperations() {
        //nested comment to bypass sonar role
    }

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.obs.dqsc.api.repository.*.*(..))")
    public void repositoryOperations() {
        //nested comment to bypass sonar role
    }

    /**
     * the execution of any method defined on the web layer if the method is defined
     * in a type in the com.obs.dqsc.api.controller package or any sub-package
     * under that.
     */
    @Pointcut("execution(* com.obs.dqsc.api.controller.*.*(..))")
    public void controllerOperations() {
        //nested comment to bypass sonar role
    }

    /**
     * exclude any class annotated with @NoLogging
     */
    @Pointcut("!@target(com.obs.dqsc.api.util.annotation.NoLogging)")
    public void notToLog(){
        //nested comment to bypass sonar role
    }

    /**
     * All functional layers of the application
     */
    @Pointcut("com.obs.dqsc.api.config.AspectConfig.initRepositoryLayer()" +
            "|| com.obs.dqsc.api.config.AspectConfig.inServiceLayer() " +
            "|| com.obs.dqsc.api.config.AspectConfig.initControllerLayer()" +
            "|| com.obs.dqsc.api.config.AspectConfig.repositoryOperations()" +
            "|| com.obs.dqsc.api.config.AspectConfig.businessServiceOperations()" +
            "|| com.obs.dqsc.api.config.AspectConfig.controllerOperations()"
            )
    public void allLayers() {
        //nested comment to bypass sonar role
    }

And that's all.

Don't forget to add required dependencies in your pom.xml for log4j2 and spring aop.

Here is a small overview of the dependencies you might need to include in your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

Finally the logs in the console will look like that: enter image description here

Hint: if you want to have UID per request, you must be interested in reading this blog

Upvotes: -1

Related Questions