Spring Cloud Sleuth dalam Aplikasi Monolith

1. Gambaran keseluruhan

Dalam artikel ini, kami memperkenalkan Spring Cloud Sleuth - alat yang ampuh untuk meningkatkan log dalam aplikasi apa pun, tetapi terutama dalam sistem yang terdiri daripada pelbagai perkhidmatan.

Dan untuk penulisan ini, kita akan fokus menggunakan Sleuth dalam aplikasi monolit, bukan di seluruh perkhidmatan mikro .

Kita semua mempunyai pengalaman malang ketika berusaha mendiagnosis masalah dengan tugas yang dijadualkan, operasi multi-utas, atau permintaan web yang rumit. Selalunya, walaupun terdapat pembalakan, sukar untuk mengetahui tindakan apa yang perlu dihubungkan bersama untuk membuat satu permintaan.

Ini dapat menjadikan diagnosis tindakan yang kompleks sangat sukar atau bahkan mustahil. Selalunya menghasilkan penyelesaian seperti memberikan id unik ke setiap kaedah dalam permintaan untuk mengenal pasti log.

Di Sleuth datang . Perpustakaan ini memungkinkan untuk mengenal pasti log yang berkaitan dengan pekerjaan, utas, atau permintaan tertentu. Sleuth berintegrasi dengan mudah dengan kerangka kerja pembalakan seperti Logback dan SLF4J untuk menambahkan pengecam unik yang membantu mengesan dan mendiagnosis masalah menggunakan log.

Mari lihat bagaimana ia berfungsi.

2. Persediaan

Kami akan mulakan dengan membuat projek web Spring Boot di IDE kegemaran kami dan menambahkan kebergantungan ini ke fail pom.xml kami :

 org.springframework.cloud spring-cloud-starter-sleuth 

Aplikasi kami dijalankan dengan Spring Boot dan induk pom menyediakan versi untuk setiap entri. Versi terbaru dari ketergantungan ini boleh didapati di sini: spring-cloud-starter-sleuth. Untuk melihat keseluruhan POM, lihat projek di Github.

Selain itu, mari kita tambahkan nama aplikasi untuk mengarahkan Sleuth mengenal pasti log aplikasi ini.

Dalam fail application.properties kami tambahkan baris ini:

spring.application.name=Baeldung Sleuth Tutorial

3. Konfigurasi Sleuth

Sleuth mampu meningkatkan log dalam banyak keadaan. Bermula dengan versi 2.0.0, Spring Cloud Sleuth menggunakan Brave sebagai pustaka pelacak yang menambahkan id unik pada setiap permintaan web yang memasuki aplikasi kami. Tambahan pula, pasukan Spring telah menambahkan sokongan untuk berkongsi id ini melintasi sempadan.

Jejak boleh dianggap seperti satu permintaan atau pekerjaan yang dicetuskan dalam aplikasi. Semua pelbagai langkah dalam permintaan itu, bahkan melintasi batas aplikasi dan utas, akan mempunyai traceId yang sama.

Span, sebaliknya, boleh dianggap sebagai bahagian pekerjaan atau permintaan. Jejak tunggal dapat terdiri dari beberapa rentang yang masing-masing berkorelasi dengan langkah atau bahagian permintaan tertentu. Dengan menggunakan jejak dan span id, kita dapat menentukan dengan tepat kapan dan di mana aplikasi kita berada ketika memproses permintaan. Menjadikan membaca log kami lebih mudah.

Dalam contoh kami, kami akan meneroka keupayaan ini dalam satu aplikasi.

3.1. Permintaan Web Ringkas

Pertama, mari buat kelas pengawal untuk menjadi pintu masuk untuk bekerja dengan:

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

Mari jalankan aplikasi kami dan pergi ke "// localhost: 8080". Perhatikan log untuk output yang kelihatan seperti:

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

Ini kelihatan seperti log biasa, kecuali bahagian pada awal antara kurungan. Ini adalah maklumat utama yang telah ditambahkan oleh Spring Sleuth . Data ini mengikut format:

[nama aplikasi, traceId, spanId, eksport]

  • Nama aplikasi - Ini adalah nama yang kami tetapkan dalam fail sifat dan dapat digunakan untuk mengumpulkan log dari beberapa contoh aplikasi yang sama.
  • TraceId - Ini adalah id yang diberikan untuk satu permintaan, pekerjaan, atau tindakan. Sesuatu seperti setiap permintaan web yang dimulakan oleh pengguna unik akan mempunyai traceId tersendiri .
  • SpanId - Menjejaki unit kerja. Fikirkan permintaan yang terdiri daripada pelbagai langkah. Setiap langkah boleh mempunyai spanId sendiri dan dilacak secara individu. Secara lalai, sebarang aliran aplikasi akan dimulakan dengan TraceId dan SpanId yang sama.
  • Eksport - Properti ini adalah boolean yang menunjukkan sama ada log ini dieksport ke agregator atau tidak seperti Zipkin . Zipkin berada di luar ruang lingkup artikel ini tetapi memainkan peranan penting dalam menganalisis log yang dibuat oleh Sleuth .

Sekarang, anda pasti mempunyai idea mengenai kehebatan perpustakaan ini. Mari kita lihat contoh lain untuk menunjukkan lebih jauh seberapa penting perpustakaan ini dengan pembalakan.

3.2. Permintaan Web Mudah Dengan Akses Perkhidmatan

Mari mulakan dengan membuat perkhidmatan dengan satu kaedah:

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

Sekarang mari masukkan perkhidmatan kami ke pengawal kami dan tambahkan kaedah pemetaan permintaan yang mengaksesnya:

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

Akhirnya, mulakan semula aplikasi dan arahkan ke "// localhost: 8080 / same-span". Perhatikan output log yang kelihatan seperti:

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

Perhatikan bahawa jejak dan span id adalah sama antara dua log walaupun mesejnya berasal dari dua kelas yang berbeza. Ini menjadikannya sepele untuk mengenal pasti setiap log semasa permintaan dengan mencari traceId permintaan itu.

Ini adalah tingkah laku lalai, satu permintaan mendapat satu traceId dan spanId . Tetapi kita dapat menambahkan span secara manual mengikut kesesuaian. Mari kita lihat contoh yang menggunakan ciri ini.

3.3. Menambah Span Secara Manual

Untuk memulakan, mari tambahkan pengawal baru:

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

Dan sekarang mari kita tambahkan kaedah baru di dalam perkhidmatan kami:

@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 “//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 “//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 “//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.

Walaupun kita tidak pergi ke awan, Spring Sleuth kemungkinan merupakan pergantungan kritikal dalam hampir semua projek; ia lancar untuk disatukan dan merupakan penambahan nilai yang besar .

Dari sini anda mungkin ingin menyiasat ciri-ciri lain dari Sleuth . Ini dapat menunjang penelusuran dalam sistem terdistribusi menggunakan RestTemplate , di seluruh protokol pesanan yang digunakan oleh RabbitMQ dan Redis , dan melalui pintu gerbang seperti Zuul.

Seperti biasa anda boleh mendapatkan kod sumber di Github.