Spring Cloud Task As Kubernetes CronJob

Source Code for this blog can be found here

Refer this before you get started with Spring Cloud Task

Spring Cloud Task helps create short lived microservices for spring boot application, which can be preferably used for Kubernetes Job/Cronjob

There are two steps :

Enabling Task

Add @EnableTask Annotation to your Application class along with @SpringBootApplication

Implement Task

In spring boot we can execute any task just before before application finishes its startup, by providing an implementation of org.springframework.boot.CommandLineRunner or org.springframework.boot.ApplicationRunner, these task either should be a @Component or returned from @Bean method.

Spring Cloud Task Internal Implementation Detail

@EnableTask import TaskLifecycleConfiguration, which is a Configuration class for TaskRepository, TaskExplorer, TaskLifecycleListener and so on..

SimpleTaskAutoConfiguration is responsible for configuring TaskConfigurer (org.springframework.cloud.task.configuration.DefaultTaskConfigurer)

Hello World

https://start.spring.io/

Note : Here we are using H2 In memory database, which is fine for development and not for production, for production use a persistence database instance which stores all records or your task executions, DDLs can be found here

application.properties

spring.application.name=Trigger Batch process Job

spring.cloud.task.closecontextEnabled=true
spring.cloud.task.initialize-enabled=true
spring.cloud.task.single-instance-enabled=false
#spring.cloud.task.name=

logging.level.org.springframework.cloud.task=DEBUG
logging.level.com.org.lob=DEBUG

All the exposed properties are defined in class org.springframework.cloud.task.configuration.TaskProperties

TaskConfig.java

package com.org.lob.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class TaskConfig {
	
	
}

TriggerBatchProcessJob.java

package com.org.lob.job;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class TriggerBatchProcessJob implements CommandLineRunner {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(TriggerBatchProcessJob.class);

	@Override
	public void run(String... args) throws Exception {
		LOGGER.info("Hello, World!");		
	}
}

Run the application

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.0)

2021-05-29 16:49:25.670  INFO 12780 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : Starting TriggerBatchProcessJobApplication using Java 15.0.1 on DESKTOP-9AES3TT with PID 12780 (E:\eWorkspaces\job\lob-proj-job-trigger-batch-process\target\classes started by reach in E:\eWorkspaces\job\lob-proj-job-trigger-batch-process)
2021-05-29 16:49:25.672  INFO 12780 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : No active profile set, falling back to default profiles: default
2021-05-29 16:49:26.299  INFO 12780 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-05-29 16:49:26.427  INFO 12780 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-05-29 16:49:26.440 DEBUG 12780 --- [           main] o.s.c.t.c.SimpleTaskAutoConfiguration    : Using org.springframework.cloud.task.configuration.DefaultTaskConfigurer TaskConfigurer
2021-05-29 16:49:26.441 DEBUG 12780 --- [           main] o.s.c.t.c.DefaultTaskConfigurer          : No EntityManager was found, using DataSourceTransactionManager
2021-05-29 16:49:26.557 DEBUG 12780 --- [           main] o.s.c.t.r.s.TaskRepositoryInitializer    : Initializing task schema for h2 database
2021-05-29 16:49:26.638 DEBUG 12780 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Creating: TaskExecution{executionId=0, parentExecutionId=null, exitCode=null, taskName='Trigger Batch process Job', startTime=Sat May 29 16:49:26 IST 2021, endTime=null, exitMessage='null', externalExecutionId='null', errorMessage='null', arguments=[]}
2021-05-29 16:49:26.657  INFO 12780 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : Started TriggerBatchProcessJobApplication in 1.283 seconds (JVM running for 1.598)
2021-05-29 16:49:26.658  INFO 12780 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
Hello, World!
2021-05-29 16:49:26.672 DEBUG 12780 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Updating: TaskExecution with executionId=1 with the following {exitCode=0, endTime=Sat May 29 16:49:26 IST 2021, exitMessage='null', errorMessage='null'}
2021-05-29 16:49:26.674  INFO 12780 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC

Task Execution Listener

Refer this for more details

BatchProcessTaskListener.java

package com.org.lob.job;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.task.listener.annotation.AfterTask;
import org.springframework.cloud.task.listener.annotation.BeforeTask;
import org.springframework.cloud.task.listener.annotation.FailedTask;
import org.springframework.cloud.task.repository.TaskExecution;


public class BatchProcessTaskListener {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(BatchProcessTaskListener.class);

	@BeforeTask
    public void beforeTask(TaskExecution taskExecution) {
		LOGGER.debug("Starting Job `{}`", taskExecution.getTaskName());
    }

    @AfterTask
    public void afterTask(TaskExecution taskExecution) {
    	LOGGER.debug("Finished Job `{}`", taskExecution.getTaskName());
    }

    @FailedTask
    public void failedTask(TaskExecution taskExecution, Throwable throwable) {
    	LOGGER.error("Error for job `{}`", taskExecution.getTaskName(), throwable);
    }
}

TaskConfig.java

package com.org.lob.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.org.lob.job.BatchProcessTaskListener;

@Configuration
public class TaskConfig {

	@Bean
	public BatchProcessTaskListener taskListener() {
	    return new BatchProcessTaskListener();
	}

}

Run the Application

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.0)

2021-05-29 17:15:23.868  INFO 13004 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : Starting TriggerBatchProcessJobApplication using Java 15.0.1 on DESKTOP-9AES3TT with PID 13004 (E:\eWorkspaces\job\lob-proj-job-trigger-batch-process\target\classes started by reach in E:\eWorkspaces\job\lob-proj-job-trigger-batch-process)
2021-05-29 17:15:23.870 DEBUG 13004 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : Running with Spring Boot v2.5.0, Spring v5.3.7
2021-05-29 17:15:23.870  INFO 13004 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : No active profile set, falling back to default profiles: default
2021-05-29 17:15:24.548  INFO 13004 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-05-29 17:15:24.670  INFO 13004 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-05-29 17:15:24.681 DEBUG 13004 --- [           main] o.s.c.t.c.SimpleTaskAutoConfiguration    : Using org.springframework.cloud.task.configuration.DefaultTaskConfigurer TaskConfigurer
2021-05-29 17:15:24.682 DEBUG 13004 --- [           main] o.s.c.t.c.DefaultTaskConfigurer          : No EntityManager was found, using DataSourceTransactionManager
2021-05-29 17:15:24.805 DEBUG 13004 --- [           main] o.s.c.t.r.s.TaskRepositoryInitializer    : Initializing task schema for h2 database
2021-05-29 17:15:24.900 DEBUG 13004 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Creating: TaskExecution{executionId=0, parentExecutionId=null, exitCode=null, taskName='Trigger Batch process Job', startTime=Sat May 29 17:15:24 IST 2021, endTime=null, exitMessage='null', externalExecutionId='null', errorMessage='null', arguments=[]}
2021-05-29 17:15:24.913 DEBUG 13004 --- [           main] c.org.lob.job.BatchProcessTaskListener   : Starting Job `Trigger Batch process Job`
2021-05-29 17:15:24.922  INFO 13004 --- [           main] c.o.l.TriggerBatchProcessJobApplication  : Started TriggerBatchProcessJobApplication in 1.394 seconds (JVM running for 1.726)
2021-05-29 17:15:24.923  INFO 13004 --- [           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
2021-05-29 17:15:24.924  INFO 13004 --- [           main] com.org.lob.job.TriggerBatchProcessJob   : Hello, World!
2021-05-29 17:15:24.928 DEBUG 13004 --- [           main] c.org.lob.job.BatchProcessTaskListener   : Finished Job `Trigger Batch process Job`
2021-05-29 17:15:24.939 DEBUG 13004 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Updating: TaskExecution with executionId=1 with the following {exitCode=0, endTime=Sat May 29 17:15:24 IST 2021, exitMessage='null', errorMessage='null'}
2021-05-29 17:15:24.944  INFO 13004 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-05-29 17:15:24.949  INFO 13004 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Change To MySQL from H2

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.org.lob</groupId>
	<artifactId>lob-proj-job-trigger-batch-process</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>lob-proj-job-trigger-batch-process</name>
	<description>Spring Cloud Task as Kubernetes CronJob</description>
	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>2020.0.3-SNAPSHOT</spring-cloud.version>
	</properties>
	<dependencies>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-task</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<releases>
				<enabled>false</enabled>
			</releases>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

application.properties

spring.application.name=Trigger Batch process Job

spring.cloud.task.closecontextEnabled=true
spring.cloud.task.initialize-enabled=false
spring.cloud.task.single-instance-enabled=false
#spring.cloud.task.name=

# MySQL DB
spring.datasource.url=jdbc:mysql://${DB_SERVER}:${DB_PORT}/${DB_NAME}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASS}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

# RabbitMQ
spring.rabbitmq.host=${RMQ_HOST}
spring.rabbitmq.port=${RMQ_PORT}
spring.rabbitmq.username=${RMQ_USER}
spring.rabbitmq.password=${RMQ_PASS}

logging.level.org.springframework.cloud.task=DEBUG
logging.level.com.org.lob=DEBUG

Get the project from here, it has docker-compose.xml and Dockerfile, and execute the following commands

mvn clean package
docker compose up --build

Once the db is up you can run the application from eclipse as well.

Kubernetes CronJob

With the help of CI/CD pipeline and image can be easily created, and use in Kubernetes CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: tigger-batch-process
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: tigger-batch-process
            image: ${IMAGE_NAME}
            imagePullPolicy: IfNotPresent
          restartPolicy: OnFailure

The code achieves the following

Also See

References

Distributed Tracing with Spring Cloud Sleuth OTel And Grafana Tempo

Tempo

Lets start tempo following this

docker run -d --rm -p 6831:6831/udp -p 9411:9411 -p 55680:55680 -p 3100:3100 -p 14250:14250 --name tempo -v E:\practices\docker\tempo\tempo-local.yaml:/etc/tempo-local.yaml --network docker-tempo  grafana/tempo:latest --config.file=/etc/tempo-local.yaml

docker run -d --rm -p 16686:16686 --name tempo-query -v E:\practices\docker\tempo\tempo-query.yaml:/etc/tempo-query.yaml  --network docker-tempo  grafana/tempo-query:latest  --grpc-storage-plugin.configuration-file=/etc/tempo-query.yaml

spring-cloud-sleuth

https://start.spring.io/

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.2</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>org.springframework.seluth</groupId>
	<artifactId>boot-seluth-tempo-zipkin</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>boot-seluth-tempo-zipkin</name>
	<description>Project demonstrating boot seluth integration with Grafana Tempo : Zipkin</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- Sleuth with Brave tracer implementation -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-sleuth</artifactId>
			<exclusions>
				<!-- Exclude Brave (the default) -->
				<exclusion>
					<groupId>org.springframework.cloud</groupId>
					<artifactId>spring-cloud-sleuth-brave</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- Add OpenTelemetry tracer -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-sleuth-otel-autoconfigure</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-sleuth-zipkin</artifactId>
		</dependency>
		<dependency>
			<groupId>io.opentelemetry</groupId>
			<artifactId>opentelemetry-exporter-zipkin</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>2020.0.0</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-sleuth-otel-dependencies</artifactId>
				<!-- Provide the version of the Spring Cloud Sleuth OpenTelemetry project -->
				<version>1.0.0-M3</version>
				<scope>import</scope>
				<type>pom</type>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<!-- You 'll need those to add OTel support -->
	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<url>https://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<url>https://repo.spring.io/snapshot</url>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>

</project>

application.properties

spring.application.name=boot-seluth-tempo-zipkin
#spring.zipkin.service.name=boot-seluth-tempo-zipkin
spring.zipkin.baseUrl=http://localhost:9411
#spring.zipkin.sender.type=web
#spring.sleuth.sampler.probability=1.0

#logging.level.web=DEBUG
logging.level.io.opentelemetry=DEBUG
logging.level.root=INFO
#logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
logging.level.org.springframework.cloud=DEBUG

BootSeluthTempoZipkinApplication.java

package org.springframework.seluth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import io.opentelemetry.sdk.trace.samplers.Sampler;

@SpringBootApplication
public class BootSeluthTempoZipkinApplication {

	public static void main(String[] args) {
		SpringApplication.run(BootSeluthTempoZipkinApplication.class, args);
	}

	@Bean
	public Sampler defaultSampler() {
		return Sampler.alwaysOn();
	}
}

ThreadConfig.java

package org.springframework.seluth.config;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
@EnableAsync
@EnableScheduling
public class ThreadConfig extends AsyncConfigurerSupport implements SchedulingConfigurer {
	
	private BeanFactory beanFactory;
	
	public ThreadConfig(BeanFactory beanFactory) {
		this.beanFactory =  beanFactory;
	}
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.initialize();

        return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(schedulingExecutor());
    }

    @Bean(destroyMethod = "shutdown")
    public Executor schedulingExecutor() {
        return Executors.newScheduledThreadPool(1);
    }
}

Greeting.java

package org.springframework.seluth.greet;

public class Greeting {

	private final long id;
	private final String content;

	public Greeting(long id, String content) {
		this.id = id;
		this.content = content;
	}

	public long getId() {
		return id;
	}

	public String getContent() {
		return content;
	}
}

GreetingService.java

package org.springframework.seluth.greet;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.Tracer.SpanInScope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class GreetingService {
	private static final Logger logger = LoggerFactory.getLogger(GreetingService.class);
	
	@Autowired
	private Tracer tracer;
	
	public void doSomeWorkSameSpan() throws InterruptedException {
		logger.info("Doing some work");
		TimeUnit.SECONDS.sleep(1);        
    }
	
	public void doSomeWorkNewSpan() throws InterruptedException {
	    logger.info("I'm in the original span");

	    Span newSpan = tracer.nextSpan().name("newSpan").start();
	    try (SpanInScope ws = tracer.withSpan(newSpan)) {
	    	TimeUnit.SECONDS.sleep(1); 
	        newSpan.tag("newSpan tag", "newSpan tag val");
	        logger.info("I'm in the new span doing some cool work that needs its own span");
	    } finally {
	        newSpan.end();
	    }

	    logger.info("I'm in the original span");
	}
	
	@Async
	public void asyncMethod() throws InterruptedException {
	    logger.info("Start Async Method");
	    TimeUnit.SECONDS.sleep(1);  
	    logger.info("End Async Method");
	}
}

SchedulingService.java

package org.springframework.seluth.greet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class SchedulingService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    private GreetingService sleuthService;

    @Scheduled(fixedDelay = 30000)
    public void scheduledWork() throws InterruptedException {
        logger.info("Start some work from the scheduled task");
        sleuthService.asyncMethod();
        logger.info("End work from scheduled task");
    }
}

GreetingController.java

package org.springframework.seluth.greet;

import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {
	
	private static final Logger logger = LoggerFactory.getLogger(GreetingService.class);

	private static final String template = "Hello, %s!";
	private final AtomicLong counter = new AtomicLong();
	
	private GreetingService greetingService;
	
	public GreetingController(GreetingService greetingService) {
		this.greetingService = greetingService;
	}

	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) throws InterruptedException {
		logger.info("Before Service Method Call");
		this.greetingService.doSomeWorkNewSpan();
		logger.info("After Service Method Call");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
	}
}

Tracing

Run BootSeluthTempoZipkinApplication

Copy one of the above trace ids and paste in tempo-query UI (Jaeger UI)

References