1. 개요

이 기사에서는 모든 애플리케이션, 특히 여러 서비스로 구성된 시스템에서 로그를 향상시키는 강력한 도구인 Spring Cloud Sleuth를 소개합니다 .

그리고 이 글에서는 마이크로 서비스가 아닌 모놀리식 애플리케이션에서 Sleuth를 사용하는 데 중점을 둘 것입니다 .

우리 모두는 예약된 작업, 다중 스레드 작업 또는 복잡한 웹 요청으로 문제를 진단하려고 시도하는 불행한 경험을 했습니다. 종종 로깅이 있는 경우에도 단일 요청을 생성하기 위해 어떤 작업을 함께 연관시켜야 하는지 말하기 어렵습니다.

이로 인해 복잡한 작업을 진단하는 것이 매우 어렵 거나 불가능할 수 있습니다. 종종 로그를 식별하기 위해 요청의 각 메서드에 고유 ID를 전달하는 것과 같은 솔루션이 생성됩니다.

슬루스 가 온다 . 이 라이브러리를 사용하면 특정 작업, 스레드 또는 요청과 관련된 로그를 식별할 수 있습니다. Sleuth는 LogbackSLF4J 와 같은 로깅 프레임워크와 쉽게 통합되어 로그를 사용하여 문제를 추적하고 진단하는 데 도움이 되는 고유 식별자를 추가합니다.

작동 방식을 살펴보겠습니다.

2. 설정

우리가 가장 좋아하는 IDE에서 Spring Boot 웹 프로젝트를 생성하고 이 의존성을 pom.xml 파일에 추가하는 것으로 시작하겠습니다 .

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

우리 애플리케이션은 Spring Boot 로 실행되며 부모 pom은 각 항목에 대한 버전을 제공합니다. 이 의존성의 최신 버전은 spring-cloud-starter-sleuth 에서 찾을 수 있습니다 .

또한 Sleuth가 이 애플리케이션의 로그를 식별하도록 지시하는 애플리케이션 이름을 추가해 보겠습니다 .

application.properties 파일 에 다음 줄을 추가합니다.

spring.application.name=Baeldung Sleuth Tutorial

3. 탐정 구성

Sleuth 는 많은 상황에서 로그를 향상시킬 수 있습니다. 버전 2.0.0부터 Spring Cloud Sleuth는 애플리케이션에 들어오는 각 웹 요청에 고유 ID를 추가하는 추적 라이브러리로 Brave를 사용합니다. 또한 Spring 팀은 스레드 경계를 넘어 이러한 ID를 공유하기 위한 지원을 추가했습니다.

추적은 애플리케이션에서 트리거되는 단일 요청 또는 작업으로 생각할 수 있습니다. 해당 요청의 모든 다양한 단계는 애플리케이션 및 스레드 경계를 넘어서도 동일한 traceId를 갖습니다.

반면 스팬은 작업 또는 요청의 섹션으로 생각할 수 있습니다. 단일 추적은 각각 요청의 특정 단계 또는 섹션과 관련된 여러 범위로 구성될 수 있습니다. 추적 및 스팬 ID를 사용하여 애플리케이션이 요청을 처리할 때 애플리케이션이 있는 시기와 위치를 정확히 파악할 수 있습니다. 로그를 훨씬 쉽게 읽을 수 있습니다.

예제에서는 단일 애플리케이션에서 이러한 기능을 살펴봅니다.

3.1. 간단한 웹 요청

먼저 작업할 진입점이 될 컨트롤러 클래스를 생성해 보겠습니다.

@RestController
public class SleuthController {

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

응용 프로그램을 실행하고 "http://localhost:8080"으로 이동합니다. 다음과 같은 출력에 대한 로그를 확인합니다.

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

괄호 사이의 시작 부분을 제외하고는 일반 로그처럼 보입니다. Spring Sleuth 가 추가한 핵심 정보입니다 . 이 데이터는 다음 형식을 따릅니다.

[애플리케이션 이름, traceId, spanId, 내보내기]

  • 애플리케이션 이름 – 속성 파일에서 설정한 이름이며 동일한 애플리케이션의 여러 인스턴스에서 로그를 집계하는 데 사용할 수 있습니다.
  • TraceId – 단일 요청, 작업 또는 작업에 할당되는 ID입니다. 각각의 고유한 사용자 시작 웹 요청과 같은 항목에는 자체 traceId 가 있습니다 .
  • SpanId – 작업 단위를 추적합니다. 여러 단계로 구성된 요청을 생각해보세요. 각 단계는 고유한 spanId를 가질 수 있으며 개별적으로 추적할 수 있습니다. 기본적으로 모든 애플리케이션 흐름은 동일한 TraceId 및 SpanId로 시작됩니다.
  • 내보내기 – 이 속성은 이 로그가 Zipkin 과 같은 수집기로 내보내졌는지 여부를 나타내는 부울입니다 . Zipkin 은 이 기사의 범위를 벗어나지만 Sleuth 에서 생성된 로그를 분석하는 데 중요한 역할을 합니다 .

지금쯤이면 이 라이브러리의 기능에 대해 어느 정도 이해하셨을 것입니다. 이 라이브러리가 로깅에 얼마나 통합되어 있는지 더 자세히 보여주기 위해 다른 예를 살펴보겠습니다.

3.2. 서비스 액세스가 있는 단순 웹 요청

단일 메서드로 서비스를 만들어 보겠습니다.

@Service
public class SleuthService {

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

이제 서비스를 컨트롤러에 주입하고 액세스하는 요청 매핑 메서드를 추가해 보겠습니다.

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

마지막으로 애플리케이션을 다시 시작하고 "http://localhost:8080/same-span"으로 이동합니다. 다음과 같은 로그 출력을 확인합니다.

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

메시지가 서로 다른 두 클래스에서 생성된 경우에도 추적 및 스팬 ID는 두 로그 간에 동일합니다. 이렇게 하면 해당 요청의 traceId를 검색하여 요청 중에 각 로그를 식별하는 것이 간단해집니다 .

이것이 기본 동작이며 하나의 요청이 단일 traceIdspanId 를 가져옵니다 . 그러나 적합하다고 생각되는 범위를 수동으로 추가할 수 있습니다. 이 기능을 사용하는 예를 살펴보겠습니다.

3.3. 스팬 수동 추가

시작하려면 새 컨트롤러를 추가해 보겠습니다.

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

이제 서비스 내부에 새 메서드를 추가해 보겠습니다.

@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");
}

새 개체 인 Tracer 도 추가했습니다 . 추적 인스턴스는 시작하는 동안 Spring Sleuth 에 의해 생성되며 의존성 주입을 통해 클래스에서 사용할 수 있습니다 .

추적은 수동으로 시작하고 중지해야 합니다. 이를 위해 수동으로 생성된 스팬 에서 실행되는 코드는 try-finally 블록 내에 배치되어 작업의 성공 여부에 관계없이 스팬이 닫히도록 합니다 . 또한 새 범위를 범위에 배치해야 합니다.

응용 프로그램을 다시 시작하고 "http://localhost:8080/new-span"으로 이동합니다. 다음과 같은 로그 출력을 확인합니다.

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

세 번째 로그는 다른 로그와 traceId를 공유 하지만 고유한 spanId를 가지고 있음 을 알 수 있습니다 . 보다 세분화된 추적을 위해 단일 요청에서 다른 섹션을 찾는 데 사용할 수 있습니다.

이제 스레드에 대한 Sleuth의 지원을 살펴보겠습니다 .

3.4. Runnable 스패닝

Sleuth 의 스레딩 기능을 시연하기 위해 먼저 구성 클래스를 추가하여 스레드 풀을 설정해 보겠습니다.

@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);
    }
}

여기에서 LazyTraceExecutor 의 사용에 주목하는 것이 중요합니다 . 이 클래스는 Sleuth 라이브러리에서 제공되며 traceId 를 새 스레드로 전파하고 프로세스에서 새 spanId 를 생성 하는 특별한 종류의 실행기입니다 .

이제 이 실행기를 컨트롤러에 연결하고 새 요청 매핑 메서드에서 사용하겠습니다.

@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";
}

실행 가능 항목이 있으면 애플리케이션을 다시 시작하고 "http://localhost:8080/new-thread"로 이동합니다. 다음과 같은 로그 출력을 확인합니다.

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

이전 예제와 마찬가지로 모든 로그가 동일한 traceId를 공유한다는 것을 알 수 있습니다 . 그러나 실행 파일에서 오는 로그에는 해당 스레드에서 수행된 작업을 추적하는 고유한 범위가 있습니다. 이는 LazyTraceExecutor 때문에 발생한다는 점을 기억하세요 . 일반 실행기를 사용하는 경우 새 스레드에서 사용된 것과 동일한 spanId를 계속 볼 수 있습니다.

이제 @Async 메서드에 대한 Sleuth의 지원을 살펴보겠습니다 .

3.5. @Async 지원

비동기 지원을 추가하려면 먼저 ThreadConfig 클래스를 수정하여 이 기능을 활성화합니다.

@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);
    }
}

AsyncConfigurerSupport를 확장하여 비동기 실행자를 지정하고 LazyTraceExecutor를 사용하여 traceId 및 spanId가 올바르게 전파되도록 합니다. 또한 클래스 상단에 @EnableAsync를 추가했습니다 .

이제 서비스에 비동기 메서드를 추가해 보겠습니다.

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

이제 컨트롤러에서 이 메서드를 호출해 보겠습니다.

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

마지막으로 서비스를 다시 시작하고 “http://localhost:8080/async”로 이동합니다. 다음과 같은 로그 출력을 확인합니다.

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

여기에서 실행 가능한 예제와 마찬가지로 Sleuth가 traceId를 비동기 메서드로 전파 하고 고유한 spanId를 추가하는 것을 볼 수 있습니다.

이제 예약된 작업에 대한 스프링 지원을 사용하는 예제를 살펴보겠습니다.

3.6. @예정된 지원

마지막으로 Sleuth가 @Scheduled 메서드 와 어떻게 작동하는지 살펴보겠습니다 . 이렇게 하려면 예약을 활성화하도록 ThreadConfig 클래스를 업데이트합니다.

@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);
    }
}

SchedulingConfigurer 인터페이스를 구현 하고 해당 configureTasks 메서드를 재정의했습니다. 또한 클래스 상단에 @EnableScheduling을 추가했습니다 .

다음으로 예약된 작업에 대한 서비스를 추가해 보겠습니다.

@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");
    }
}

이 수업에서는 고정 지연 시간이 30초인 단일 예약 작업을 만들었습니다.

이제 응용 프로그램을 다시 시작하고 작업이 실행될 때까지 기다리겠습니다. 콘솔에서 다음과 같은 출력을 확인하십시오.

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

여기에서 Sleuth가 작업을 위해 새 추적 및 스팬 ID를 생성했음을 알 수 있습니다 . 작업의 각 인스턴스는 기본적으로 자체 추적 및 범위를 가져옵니다.

4. 결론

결론적으로 Spring Sleuth가 단일 웹 애플리케이션 내부의 다양한 상황에서 어떻게 사용될 수 있는지 살펴보았습니다 . 요청이 여러 스레드에 걸쳐 있는 경우에도 이 기술을 사용하여 단일 요청의 로그를 쉽게 연관시킬 수 있습니다.

이제 Spring Cloud Sleuth가 다중 스레드 환경을 디버깅할 때 온전함을 유지하는 데 어떻게 도움이 되는지 확인할 수 있습니다. traceId 의 각 작업 과 spanId 의 각 단계를 식별하여 로그에서 복잡한 작업 분석을 분석할 수 있습니다.

우리가 클라우드로 가지 않더라도 Spring Sleuth는 거의 모든 프로젝트에서 중요한 의존성일 가능성이 높습니다. 통합이 원활하고 막대한 가치 추가입니다 .

여기에서 Sleuth 의 다른 기능을 조사할 수 있습니다 . 이는 RestTemplate을 사용하는 분산 시스템 , RabbitMQRedis 에서 사용하는 메시징 프로토콜 및 Zuul과 같은 게이트웨이를 통해 추적을 지원할 수 있습니다.

항상 그렇듯이 Github에서 소스 코드를 찾을 수 있습니다 .

Cloud footer banner