1. Overview

 

In this article, we're introducing Spring Cloud Sleuth – a powerful tool for enhancing logs in any application, but especially in a system built up of multiple services.

And for this writeup we're going to focus on using Sleuth in a monolith application, not across microservices.

We've all had the unfortunate experience of trying to diagnose a problem with a scheduled task, a multi-threaded operation, or a complex web request. Often, even when there is logging, it is hard to tell what actions need to be correlated together to create a single request.

This can make diagnosing a complex action very difficult or even impossible. Often resulting in solutions like passing a unique id to each method in the request to identify the logs.

In comes Sleuth. This library makes it possible to identify logs pertaining to a specific job, thread, or request. Sleuth integrates effortlessly with logging frameworks like Logback and SLF4J to add unique identifiers that help track and diagnose issues using logs.

Let's take a look at how it works.

2. Setup

 

We'll start by creating a Spring Boot web project in our favorite IDE and adding this dependency to our pom.xml file:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

Our application runs with Spring Boot and the parent pom provides versions for each entry. The latest version of this dependency can be found here: spring-cloud-starter-sleuth. To see the entire POM check out the project on Github.

Additionally, let's add an application name to instruct Sleuth to identify this application's logs.

In our application.properties file add this line:

spring.application.name=Baeldung Sleuth Tutorial

3. Sleuth Configurations

 

Sleuth is capable of enhancing logs in many situations. Starting with version 2.0.0, Spring Cloud Sleuth uses Brave as the tracing library that adds unique ids to each web request that enters our application. Furthermore, the Spring team has added support for sharing these ids across thread boundaries.

Traces can be thought of like a single request or job that is triggered in an application. All the various steps in that request, even across application and thread boundaries, will have the same traceId.

Spans, on the other hand, can be thought of as sections of a job or request. A single trace can be composed of multiple spans each correlating to a specific step or section of the request. Using trace and span ids we can pinpoint exactly when and where our application is as it processes a request. Making reading our logs much easier.

In our examples, we will explore these capabilities in a single application.

3.1. Simple Web Request

 

First, let's create a controller class to be an entry point to work with:

@RestController
public class SleuthController { @GetMapping("/")
public String helloSleuth() {
logger.info("Hello Sleuth");
return "success";
}
}

Let's run our application and navigate to “http://localhost:8080”. Watch the logs for output that looks like:

2017-01-10 22:36:38.254  INFO
[Baeldung Sleuth Tutorial,4e30f7340b3fb631,4e30f7340b3fb631,false] 12516
--- [nio-8080-exec-1] c.b.spring.session.SleuthController : Hello Sleuth

This looks like a normal log, except for the part in the beginning between the brackets. This is the core information that Spring Sleuth has added. This data follows the format of:

[application name, traceId, spanId, export]

  • Application name – This is the name we set in the properties file and can be used to aggregate logs from multiple instances of the same application.
  • TraceId – This is an id that is assigned to a single request, job, or action. Something like each unique user initiated web request will have its own traceId.
  • SpanId – Tracks a unit of work. Think of a request that consists of multiple steps. Each step could have its own spanId and be tracked individually. By default, any application flow will start with same TraceId and SpanId.
  • Export – This property is a boolean that indicates whether or not this log was exported to an aggregator like ZipkinZipkin is beyond the scope of this article but plays an important role in analyzing logs created by Sleuth.

By now, you should have some idea of the power of this library. Let's take a look at another example to further demonstrate how integral this library is to logging.

3.2. Simple Web Request With Service Access

 

Let's start by creating a service with a single method:

@Service
public class SleuthService { public void doSomeWorkSameSpan() {
Thread.sleep(1000L);
logger.info("Doing some work");
}
}

Now let's inject our service into our controller and add a request mapping method that accesses it:

@Autowired
private SleuthService sleuthService; @GetMapping("/same-span")
public String helloSleuthSameSpan() throws InterruptedException {
logger.info("Same Span");
sleuthService.doSomeWorkSameSpan();
return "success";
}

Finally, restart the application and navigate to “http://localhost:8080/same-span”. Watch for log output that looks like:

2017-01-10 22:51:47.664  INFO
[Baeldung Sleuth Tutorial,b77a5ea79036d5b9,b77a5ea79036d5b9,false] 12516
--- [nio-8080-exec-3] c.b.spring.session.SleuthController : Same Span
2017-01-10 22:51:48.664 INFO
[Baeldung Sleuth Tutorial,b77a5ea79036d5b9,b77a5ea79036d5b9,false] 12516
--- [nio-8080-exec-3] c.baeldung.spring.session.SleuthService : Doing some work

Take note that the trace and span ids are the same between the two logs even though the messages originate from two different classes. This makes it trivial to identify each log during a request by searching for the traceId of that request.

This is the default behavior, one request gets a single traceId and spanId. But we can manually add spans as we see fit. Let's take a look at an example that uses this feature.

3.3. Manually Adding a Span

 

To start, let's add a new controller:

@GetMapping("/new-span")
public String helloSleuthNewSpan() {
logger.info("New Span");
sleuthService.doSomeWorkNewSpan();
return "success";
}

And now let's add the new method inside our service:

@Autowired
private Tracer tracer;
// ...
public void doSomeWorkNewSpan() throws InterruptedException {
logger.info("I'm in the original span"); Span newSpan = tracer.nextSpan().name("newSpan").start();
try (SpanInScope ws = tracer.withSpanInScope(newSpan.start())) {
Thread.sleep(1000L);
logger.info("I'm in the new span doing some cool work that needs its own span");
} finally {
newSpan.finish();
} logger.info("I'm in the original span");
}

Note that we also added a new object, Tracer. The tracer instance is created by Spring Sleuth during startup and is made available to our class through dependency injection.

Traces must be manually started and stopped. To accomplish this, code that runs in a manually created span is placed inside a try-finally block to ensure the span is closed regardless of the operation's success. Also, notice that new span has to be placed in scope.

Restart the application and navigate to “http://localhost:8080/new-span”. Watch for the log output that looks like:

2017-01-11 21:07:54.924
INFO [Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false] 12516
--- [nio-8080-exec-6] c.b.spring.session.SleuthController : New Span
2017-01-11 21:07:54.924
INFO [Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false] 12516
--- [nio-8080-exec-6] c.baeldung.spring.session.SleuthService :
I'm in the original span
2017-01-11 21:07:55.924
INFO [Baeldung Sleuth Tutorial,9cdebbffe8bbbade,1e706f252a0ee9c2,false] 12516
--- [nio-8080-exec-6] c.baeldung.spring.session.SleuthService :
I'm in the new span doing some cool work that needs its own span
2017-01-11 21:07:55.924
INFO [Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false] 12516
--- [nio-8080-exec-6] c.baeldung.spring.session.SleuthService :
I'm in the original span

We can see that the third log shares the traceId with the others, but it has a unique spanId. This can be used to locate different sections in a single request for more fine-grained tracing.

Now let's take a look at Sleuth's support for threads.

 

3.4. Spanning Runnables

 

To demonstrate the threading capabilities of Sleuth let's first add a configuration class to set up a thread pool:

@Configuration
public class ThreadConfig { @Autowired
private BeanFactory beanFactory; @Bean
public Executor executor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor
= new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(1);
threadPoolTaskExecutor.setMaxPoolSize(1);
threadPoolTaskExecutor.initialize(); return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);
}
}

It is important to note here the use of LazyTraceExecutor. This class comes from the Sleuth library and is a special kind of executor that will propagate our traceIds to new threads and create new spanIds in the process.

Now let's wire this executor into our controller and use it in a new request mapping method:

@Autowired
private Executor executor; @GetMapping("/new-thread")
public String helloSleuthNewThread() {
logger.info("New Thread");
Runnable runnable = () -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("I'm inside the new thread - with a new span");
};
executor.execute(runnable); logger.info("I'm done - with the original span");
return "success";
}

With our runnable in place, let's restart our application and navigate to “http://localhost:8080/new-thread”. Watch for log output that looks like:

2017-01-11 21:18:15.949
INFO [Baeldung Sleuth Tutorial,96076a78343c364d,96076a78343c364d,false] 12516
--- [nio-8080-exec-9] c.b.spring.session.SleuthController : New Thread
2017-01-11 21:18:15.950
INFO [Baeldung Sleuth Tutorial,96076a78343c364d,96076a78343c364d,false] 12516
--- [nio-8080-exec-9] c.b.spring.session.SleuthController :
I'm done - with the original span
2017-01-11 21:18:16.953
INFO [Baeldung Sleuth Tutorial,96076a78343c364d,e3b6a68013ddfeea,false] 12516
--- [lTaskExecutor-1] c.b.spring.session.SleuthController :
I'm inside the new thread - with a new span

Much like the previous example we can see that all the logs share the same traceId. But the log coming from the runnable has a unique span that will track the work done in that thread. Remember that this happens because of the LazyTraceExecutor, if we were to use a normal executor we would continue to see the same spanId used in the new thread.

Now let's look into Sleuth's support for @Async methods.

3.5. @Async Support

 

To add async support let's first modify our ThreadConfig class to enable this feature:

@Configuration
@EnableAsync
public class ThreadConfig extends AsyncConfigurerSupport { //...
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(1);
threadPoolTaskExecutor.setMaxPoolSize(1);
threadPoolTaskExecutor.initialize(); return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);
}
}

Note that we extend AsyncConfigurerSupport to specify our async executor and use LazyTraceExecutor to ensure traceIds and spanIds are propagated correctly. We have also added @EnableAsync to the top of our class.

 

Let's now add an async method to our service:

@Async
public void asyncMethod() {
logger.info("Start Async Method");
Thread.sleep(1000L);
logger.info("End Async Method");
}

Now let's call into this method from our controller:

@GetMapping("/async")
public String helloSleuthAsync() {
logger.info("Before Async Method Call");
sleuthService.asyncMethod();
logger.info("After Async Method Call"); return "success";
}

Finally, let's restart our service and navigate to “http://localhost:8080/async”. Watch for the log output that looks like:

2017-01-11 21:30:40.621
INFO [Baeldung Sleuth Tutorial,c187f81915377fff,c187f81915377fff,false] 10072
--- [nio-8080-exec-2] c.b.spring.session.SleuthController :
Before Async Method Call
2017-01-11 21:30:40.622
INFO [Baeldung Sleuth Tutorial,c187f81915377fff,c187f81915377fff,false] 10072
--- [nio-8080-exec-2] c.b.spring.session.SleuthController :
After Async Method Call
2017-01-11 21:30:40.622
INFO [Baeldung Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false] 10072
--- [lTaskExecutor-1] c.baeldung.spring.session.SleuthService :
Start Async Method
2017-01-11 21:30:41.622
INFO [Baeldung Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false] 10072
--- [lTaskExecutor-1] c.baeldung.spring.session.SleuthService :
End Async Method

We can see here that much like our runnable example, Sleuth propagates the traceId into the async method and adds a unique spanId.

Let's now work through an example using spring support for scheduled tasks.

3.6. @Scheduled Support

 

Finally, let's look at how Sleuth works with @Scheduled methods. To do this let's update our ThreadConfig class to enable scheduling:

@Configuration
@EnableAsync
@EnableScheduling
public class ThreadConfig extends AsyncConfigurerSupport
implements SchedulingConfigurer { //... @Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(schedulingExecutor());
} @Bean(destroyMethod = "shutdown")
public Executor schedulingExecutor() {
return Executors.newScheduledThreadPool(1);
}
}

Note that we have implemented the SchedulingConfigurer interface and overridden its configureTasks method. We have also added @EnableScheduling to the top of our class.

Next, let's add a service for our scheduled tasks:

 
@Service
public class SchedulingService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired
private SleuthService 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");
}
}

In this class, we have created a single scheduled task with a fixed delay of 30 seconds.

Let's now restart our application and wait for our task to be executed. Watch the console for output like this:

2017-01-11 21:30:58.866
INFO [Baeldung Sleuth Tutorial,3605f5deaea28df2,3605f5deaea28df2,false] 10072
--- [pool-1-thread-1] c.b.spring.session.SchedulingService :
Start some work from the scheduled task
2017-01-11 21:30:58.866
INFO [Baeldung Sleuth Tutorial,3605f5deaea28df2,3605f5deaea28df2,false] 10072
--- [pool-1-thread-1] c.b.spring.session.SchedulingService :
End work from scheduled task

We can see here that Sleuth has created new trace and span ids for our task. Each instance of a task will get it's own trace and span by default.

4. Conclusion

 

In conclusion, we have seen how Spring Sleuth can be used in a variety of situations inside a single web application. We can use this technology to easily correlate logs from a single request, even when that request spans multiple threads.

By now we can see how Spring Cloud Sleuth can help us keep our sanity when debugging a multi-threaded environment. By identifying each operation in a traceId and each step in a spanId we can really begin to break down our analysis of complex jobs in our logs.

Even if we don't go to the cloud, Spring Sleuth is likely a critical dependency in almost any project; it's seamless to integrate and is a massive addition of value.

From here you may want to investigate other features of Sleuth. It can support tracing in distributed systems using RestTemplate, across messaging protocols used by RabbitMQ and Redis, and through a gateway like Zuul.

As always you can find the source code over on Github.

Spring Cloud Sleuth in a Monolith Application的更多相关文章

  1. springcloud(十二):使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪

    随着业务发展,系统拆分导致系统调用链路愈发复杂一个前端请求可能最终需要调用很多次后端服务才能完成,当整个请求变慢或不可用时,我们是无法得知该请求是由某个或某些后端服务引起的,这时就需要解决如何快读定位 ...

  2. Spring Cloud Sleuth服务链路追踪(zipkin)(转)

    这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件. 一.简介 Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案, ...

  3. SpringCloud(7)服务链路追踪Spring Cloud Sleuth

    1.简介 Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可.本文主要讲述服务追踪组件zipki ...

  4. 第八篇: 服务链路追踪(Spring Cloud Sleuth)

    一.简介 一个分布式系统由若干分布式服务构成,每一个请求会经过多个业务系统并留下足迹,但是这些分散的数据对于问题排查,或是流程优化都很有限.   要能做到追踪每个请求的完整链路调用,收集链路调用上每个 ...

  5. 浅尝Spring Cloud Sleuth

    Spring Cloud Sleuth提供了分布式追踪(distributed tracing)的一个解决方案.其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系.利用这些信息,可以方 ...

  6. Spring Cloud Sleuth超详细实战

    为什么需要Spring Cloud Sleuth 微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元.由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去 ...

  7. 史上最简单的SpringCloud教程 | 第九篇: 服务链路追踪(Spring Cloud Sleuth)

    这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件. 注意情况: 该案例使用的spring-boot版本1.5.x,没使用2.0.x, 另外本文图3 ...

  8. 服务链路追踪(Spring Cloud Sleuth)

    sleuth:英 [slu:θ] 美 [sluθ] n.足迹,警犬,侦探vi.做侦探 微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元.由于服务单元数量众多,业务的 ...

  9. Spring cloud系列十四 分布式链路监控Spring Cloud Sleuth

    1. 概述 Spring Cloud Sleuth实现对Spring cloud 分布式链路监控 本文介绍了和Sleuth相关的内容,主要内容如下: Spring Cloud Sleuth中的重要术语 ...

  10. Spring Cloud Sleuth进阶实战

    转载请标明出处: http://blog.csdn.net/forezp/article/details/76795269 本文出自方志朋的博客 为什么需要Spring Cloud Sleuth 微服 ...

随机推荐

  1. 什么是 vite ?

    vite 是尤雨溪团队开发的新一代前端构建工具,作者尤雨溪宣传的时候表示 vite 是下一代的构建工具,并表示自己再也不会 webpack 了 : vite 的优点主要有3点 : 第一:开发环境中,无 ...

  2. KubeKey 部署 K8s v1.28.8 实战

    在某些生产环境下,我们仅需要一个原生的 K8s 集群,无需部署 KubeSphere 这样的图形化管理控制台.在我们已有的技术栈里,已经习惯了利用 KubeKey 部署 KubeSphere 和 K8 ...

  3. 云原生周刊:Docker 推出 Docker Debug | 2023.10.9

    开源项目推荐 SchemaHero SchemaHero 是一个 Kubernetes Operator,用于各种数据库的声明式架构管理.SchemaHero 有以下目标: 数据库表模式可以表示为可以 ...

  4. HTML5+CSS3+JavaScript网页实战

    1. HTML5基础 HTML5,作为构建和呈现网页内容的标准标记语言,带来了许多革命性的变化.它不仅提供了更加语义化的标签,使得网页内容更具可读性和可访问性,还增加了对多媒体的原生支持,无需依赖第三 ...

  5. 《使用Gin框架构建分布式应用》阅读笔记:p251-p271

    <用Gin框架构建分布式应用>学习第14天,p251-p271总结,总21页. 一.技术总结 1.Docker & Docker Compose version: "3. ...

  6. Python比较2个json数据是否相等

    1.json数据转换成字典 dict1 = json.load(load_f1) dict2 = json.load(load_f2) 2.将两个字典按key排好序,然后使用zip()函数将两个字典对 ...

  7. Nginx 配置 浏览器显示真实接口地址

    server { listen 9780; server_name localhost; #charset koi8-r; access_log /var/log/nginx/host.access. ...

  8. delphi Image32 之 快速入门

    官方快速入门,加上了一些注解 [从WORD粘贴后失去了样式] TImage32 类是关键.TImage32 对象包含单个图像,所有图像操作都作用于此对象. uses Img32;  //引用单元 .. ...

  9. CommonsCollections7(基于ysoserial)

    环境准备 JDK1.8(8u421)我以本地的JDK8版本为准.commons-collections(3.x 4.x均可这里使用3.2版本) cc3.2: <dependency> &l ...

  10. 从0搭建一个FIFO模块-02(系统架构)

    一.异步FIFO需要注意的问题 所谓异步FIFO,指的是写时钟与读时钟可以不同步,读时钟可以比写时钟快,反之亦然.思考一下,这样会直接地造成两个问题: 由于异步FIFO的基本存储单元是双端口RAM,因 ...