Spring Cloud Sleuth em um aplicativo monolítico

Spring Cloud Sleuth em um aplicativo monolítico

1. Visão geral

Neste artigo, estamos apresentandoSpring Cloud Sleuth - uma ferramenta poderosa para aprimorar os registros em qualquer aplicativo, mas especialmente em um sistema construído com vários serviços

E para este artigo, vamos nos concentrar no uso de Sleuthin a monolith application, not across microservices.

Todos nós já tivemos a infeliz experiência de tentar diagnosticar um problema com uma tarefa agendada, uma operação multithread ou uma solicitação da web complexa. Muitas vezes, mesmo quando há registro em log, é difícil dizer quais ações precisam ser correlacionadas para criar uma única solicitação.

Isso pode tornardiagnosing a complex action very difficult ou mesmo impossível. Freqüentemente resultam em soluções como a transmissão de um ID exclusivo para cada método na solicitação para identificar os logs.

ChegaSleuth. Essa biblioteca torna possível identificar logs pertencentes a um trabalho, thread ou solicitação específica. Sleuth integra-se facilmente com estruturas de registro comoLogbackeSLF4J para adicionar identificadores exclusivos que ajudam a rastrear e diagnosticar problemas usando registros.

Vamos dar uma olhada em como funciona.

2. Configuração

Começaremos criando um projeto da webSpring Boot em nosso IDE favorito e adicionando esta dependência ao nosso arquivopom.xml:


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

Nosso aplicativo é executado comSpring Boote o pom pai fornece versões para cada entrada. A versão mais recente desta dependência pode ser encontrada aqui:spring-cloud-starter-sleuth. Para ver o POM completo, verifique o projeto emGithub.

Além disso, vamos adicionar um nome de aplicativo para instruirSleuth a identificar os logs deste aplicativo.

Em nosso arquivoapplication.properties, adicione esta linha:

spring.application.name=example Sleuth Tutorial

3. Configurações de detetive

Sleuth é capaz de aprimorar os logs em muitas situações. A partir da versão 2.0.0, Spring Cloud Sleuth usaBrave como a biblioteca de rastreamento que adiciona ids exclusivos a cada solicitação da web que entra em nosso aplicativo. Além disso, a equipe do Spring adicionou suporte para compartilhar esses IDs através dos limites do encadeamento.

Os rastreamentos podem ser considerados como uma única solicitação ou tarefa que é acionada em um aplicativo. Todas as várias etapas dessa solicitação, mesmo nos limites do aplicativo e do encadeamento, terão o mesmo traceId.

Por outro lado, os spans podem ser considerados seções de um trabalho ou solicitação. Um único rastreamento pode ser composto de vários períodos, cada um correlacionado a uma etapa ou seção específica da solicitação. Usando identificações de rastreamento e extensão, podemos identificar exatamente quando e onde está nosso aplicativo enquanto processa uma solicitação. Tornando a leitura de nossos logs muito mais fácil.

Em nossos exemplos, exploraremos esses recursos em um único aplicativo.

3.1. Solicitação da web simples

Primeiro, vamos criar uma classe de controlador para ser um ponto de entrada para trabalhar:

@RestController
public class SleuthController {

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

Vamos executar nosso aplicativo e navegar até “http: // localhost: 8080”. Observe os logs para obter resultados parecidos com:

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

Parece um log normal, exceto pela parte no começo entre os colchetes. Esta é a informação principal queSpring Sleuth adicionou. Esses dados seguem o formato de:

[nome do aplicativo, traceId, spanId, exportação]

  • Application name - este é o nome que definimos no arquivo de propriedades e pode ser usado para agregar logs de várias instâncias do mesmo aplicativo.

  • TraceId - Este é um id atribuído a uma única solicitação, trabalho ou ação. Algo como cada solicitação da web iniciada por um usuário único terá seu própriotraceId.

  • SpanId - rastreia uma unidade de trabalho. Pense em uma solicitação que consiste em várias etapas. Cada etapa pode ter seu própriospanId e ser rastreada individualmente. Por padrão, qualquer fluxo de aplicativo começará com o mesmo TraceId e SpanId.

  • Export - esta propriedade é um booleano que indica se este log foi ou não exportado para um agregador comoZipkin. Zipkin está além do escopo deste artigo, mas desempenha um papel importante na análise de logs criados porSleuth.

Até agora, você deve ter uma idéia do poder desta biblioteca. Vamos dar uma olhada em outro exemplo para demonstrar melhor como essa biblioteca é essencial para o registro.

3.2. Solicitação da Web simples com acesso ao serviço

Vamos começar criando um serviço com um único método:

@Service
public class SleuthService {

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

Agora vamos injetar nosso serviço em nosso controlador e adicionar um método de mapeamento de solicitação que o acessa:

@Autowired
private SleuthService sleuthService;

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

Por fim, reinicie o aplicativo e navegue para “http: // localhost: 8080 / same-span”. Observe a saída de log que se parece com:

2017-01-10 22:51:47.664  INFO
  [example 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
  [example Sleuth Tutorial,b77a5ea79036d5b9,b77a5ea79036d5b9,false] 12516
  --- [nio-8080-exec-3] c.example.spring.session.SleuthService  : Doing some work

Observe que os IDs de rastreamento e extensão são os mesmos entre os dois logs, mesmo que as mensagens sejam originárias de duas classes diferentes. Isso torna trivial identificar cada log durante uma solicitação, procurando portraceId dessa solicitação.

Este é o comportamento padrão, uma solicitação obtém um únicotraceIdespanId. Mas podemos adicionar manualmente extensões conforme entendermos. Vamos dar uma olhada em um exemplo que usa esse recurso.

3.3. Adicionando um Span Manualmente

Para começar, vamos adicionar um novo controlador:

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

E agora vamos adicionar o novo método dentro de nosso serviço:

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

Observe que também adicionamos um novo objeto,Tracer. A instânciatracer é criada porSpring Sleuth durante a inicialização e é disponibilizada para nossa classe por meio de injeção de dependência.

Os rastreios devem ser iniciados e parados manualmente. Para fazer isso, o código que é executado em umspan criado manualmente é colocado dentro de um blocotry-finally para garantir quespan seja fechado, independentemente do sucesso da operação. Além disso, observe que o novo período deve ser colocado no escopo.

Reinicie o aplicativo e navegue para "http: // localhost: 8080 / new-span". Observe a saída do log que se parece com:

2017-01-11 21:07:54.924
  INFO [example 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 [example Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false] 12516
  --- [nio-8080-exec-6] c.example.spring.session.SleuthService  :
  I'm in the original span
2017-01-11 21:07:55.924
  INFO [example Sleuth Tutorial,9cdebbffe8bbbade,1e706f252a0ee9c2,false] 12516
  --- [nio-8080-exec-6] c.example.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 [example Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false] 12516
  --- [nio-8080-exec-6] c.example.spring.session.SleuthService  :
  I'm in the original span

Podemos ver que o terceiro log compartilhatraceId com os outros, mas possui umspanId exclusivo. Isso pode ser usado para localizar seções diferentes em uma única solicitação para um rastreamento mais refinado.

Agora, vamos dar uma olhada no suporteSleuth’s para threads.

3.4. Spanning Runnables

Para demonstrar os recursos de threading deSleuth, vamos primeiro adicionar uma classe de configuração para definir um pool de threads:

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

É importante observar aqui o uso deLazyTraceExecutor. Esta classe vem da bibliotecaSleuth e é um tipo especial de executor que propagará nossotraceIds para novos encadeamentos e criará novosspanIds no processo.

Agora, vamos conectar este executor em nosso controlador e usá-lo em um novo método de mapeamento de solicitação:

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

Com nosso executável no lugar, vamos reiniciar nosso aplicativo e navegar para “http: // localhost: 8080 / new-thread”. Observe a saída de log que se parece com:

2017-01-11 21:18:15.949
  INFO [example 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 [example 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 [example Sleuth Tutorial,96076a78343c364d,e3b6a68013ddfeea,false] 12516
  --- [lTaskExecutor-1] c.b.spring.session.SleuthController      :
  I'm inside the new thread - with a new span

Muito parecido com o exemplo anterior, podemos ver que todos os logs compartilham o mesmotraceId. Mas o log proveniente do executável tem uma extensão única que acompanhará o trabalho realizado nesse segmento. Lembre-se de que isso acontece por causa doLazyTraceExecutor, se fôssemos usar um executor normal, continuaríamos a ver o mesmospanId usado no novo thread.

Agora, vamos dar uma olhada no suporteSleuth’s para métodos@Async.

3.5. Suporte@Async

Para adicionar suporte assíncrono, vamos primeiro modificar nossa classeThreadConfig para habilitar este recurso:

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

Observe que estendemosAsyncConfigurerSupport para especificar nosso executor assíncrono e usamosLazyTraceExecutor para garantir que traceIds e spanIds sejam propagados corretamente. Também adicionamos@EnableAsync ao topo de nossa classe.

Vamos agora adicionar um método assíncrono ao nosso serviço:

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

Agora vamos chamar este método de nosso controlador:

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

    return "success";
}

Por fim, vamos reiniciar nosso serviço e navegar para “http: // localhost: 8080 / async”. Observe a saída do log que se parece com:

2017-01-11 21:30:40.621
  INFO [example 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 [example 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 [example Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false] 10072
  --- [lTaskExecutor-1] c.example.spring.session.SleuthService  :
  Start Async Method
2017-01-11 21:30:41.622
  INFO [example Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false] 10072
  --- [lTaskExecutor-1] c.example.spring.session.SleuthService  :
  End Async Method

Podemos ver aqui que, assim como nosso exemplo executável,Sleuth propagatraceId para o método assíncrono e adiciona um spanId exclusivo.

Vamos agora trabalhar com um exemplo usando suporte de mola para tarefas agendadas.

3.6. Suporte@Scheduled

Finalmente, vamos ver comoSleuth funciona com os métodos@Scheduled. Para fazer isso, vamos atualizar nossa classeThreadConfig para habilitar o agendamento:

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

Observe que implementamos a interfaceSchedulingConfigurer e substituímos seu método configureTasks. Também adicionamos@EnableScheduling ao topo de nossa classe.

A seguir, vamos adicionar um serviço para nossas tarefas programadas:

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

Nesta classe, criamos uma única tarefa agendada com um atraso fixo de 30 segundos.

Vamos agora reiniciar nosso aplicativo e esperar que nossa tarefa seja executada. Assista ao console para obter resultados como este:

2017-01-11 21:30:58.866
  INFO [example 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 [example Sleuth Tutorial,3605f5deaea28df2,3605f5deaea28df2,false] 10072
  --- [pool-1-thread-1] c.b.spring.session.SchedulingService     :
  End work from scheduled task

Podemos ver aqui queSleuth criou novos ids de rastreamento e span para nossa tarefa. Cada instância de uma tarefa obterá seu próprio rastreamento e extensão por padrão.

4. Conclusão

Concluindo, vimos comoSpring Sleuth pode ser usado em uma variedade de situações dentro de um único aplicativo da web. Podemos usar essa tecnologia para correlacionar facilmente logs de uma única solicitação, mesmo quando essa solicitação abrange vários threads.

Agora podemos ver comoSpring Cloud Sleuth pode nos ajudar a manter nossa sanidade ao depurar um ambiente multi-thread. Ao identificar cada operação emtraceId e cada etapa emspanId, podemos realmente começar a quebrar nossa análise de tarefas complexas em nossos logs.

Mesmo se não formos para a nuvem,Spring Sleuth provavelmente é uma dependência crítica em quase todos os projetos; é perfeito para integrar eis a massive addition of value.

A partir daqui, você pode querer investigar outros recursos deSleuth. Ele pode suportar rastreamento em sistemas distribuídos usandoRestTemplate, em protocolos de mensagens usados ​​porRabbitMQeRedise por meio de um gateway como o Zuul.

Como sempre, você pode encontrar o código-fonteover on Github.