Spring Cloud Sleuth dans une application Monolith

Spring Cloud Sleuth dans une application Monolith

1. Vue d'ensemble

Dans cet article, nous présentonsSpring Cloud Sleuth - un outil puissant pour améliorer les journaux dans n'importe quelle application, mais surtout dans un système constitué de plusieurs services.

Et pour cet article, nous allons nous concentrer sur l'utilisation de Sleuthin a monolith application, not across microservices.

Nous avons tous eu la malchance d’essayer de diagnostiquer un problème avec une tâche planifiée, une opération multithread ou une requête Web complexe. Souvent, même en cas de journalisation, il est difficile de déterminer les actions à corréler pour créer une seule demande.

Cela peut rendrediagnosing a complex action very difficult voire impossible. Cela aboutit souvent à des solutions telles que la transmission d'un identifiant unique à chaque méthode de la demande pour identifier les journaux.

En vientSleuth. Cette bibliothèque permet d'identifier les journaux relatifs à un travail, un thread ou une demande spécifique. Sleuth s'intègre sans effort aux cadres de journalisation tels queLogback etSLF4J pour ajouter des identificateurs uniques qui aident à suivre et diagnostiquer les problèmes à l'aide de journaux.

Voyons comment cela fonctionne.

2. Installer

Nous allons commencer par créer un projet WebSpring Boot dans notre IDE préféré et ajouter cette dépendance à notre fichierpom.xml:


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

Notre application fonctionne avecSpring Boot et le pom parent fournit des versions pour chaque entrée. La dernière version de cette dépendance peut être trouvée ici:spring-cloud-starter-sleuth. Pour voir l'intégralité du POM, consultez le projet surGithub.

De plus, ajoutons un nom d’application pour indiquer àSleuth d’identifier les journaux de cette application.

Dans notre fichierapplication.properties, ajoutez cette ligne:

spring.application.name=example Sleuth Tutorial

3. Configurations de détective

Sleuth est capable d'améliorer les journaux dans de nombreuses situations. À partir de la version 2.0.0, Spring Cloud Sleuth utiliseBrave comme bibliothèque de suivi qui ajoute des identifiants uniques à chaque requête Web qui entre dans notre application. En outre, l'équipe Spring a pris en charge le partage de ces identifiants entre les threads.

Les traces peuvent être considérées comme une seule demande ou une tâche déclenchée dans une application. Le même identifiant de trace est attribué à toutes les étapes de cette demande, même au-delà des limites de l'application et du thread.

Les étendues, en revanche, peuvent être considérées comme des sections d'un travail ou d'une demande. Une seule trace peut être composée de plusieurs étendues, chacune étant en corrélation avec une étape ou une section spécifique de la demande. En utilisant les identifiants de trace et d’intervalle, nous pouvons déterminer exactement où et quand notre application se trouve au moment où elle traite une demande. Faciliter la lecture de nos journaux.

Dans nos exemples, nous allons explorer ces capacités dans une seule application.

3.1. Demande Web simple

Commençons par créer une classe de contrôleur comme point d’entrée avec lequel travailler:

@RestController
public class SleuthController {

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

Exécutons notre application et accédez à "http: // localhost: 8080". Regardez les journaux pour une sortie qui ressemble à:

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

Cela ressemble à un journal normal, à l’exception de la partie située au début entre les crochets. Ce sont les informations de base queSpring Sleuth a ajoutées. Ces données suivent le format de:

[nom de l'application, traceId, spanId, export]

  • Application name - C'est le nom que nous avons défini dans le fichier de propriétés et peut être utilisé pour agréger les journaux de plusieurs instances de la même application.

  • TraceId - Il s'agit d'un identifiant attribué à une seule demande, tâche ou action. Quelque chose comme chaque requête Web initiée par un utilisateur unique aura ses proprestraceId.

  • SpanId - Suit une unité de travail. Pensez à une demande comportant plusieurs étapes. Chaque étape peut avoir ses propresspanId et être suivie individuellement. Par défaut, tous les flux d’application commencent par les mêmes TraceId et SpanId.

  • Export - Cette propriété est une valeur booléenne qui indique si ce journal a été exporté vers un agrégateur tel queZipkin. Zipkin sort du cadre de cet article mais joue un rôle important dans l'analyse des journaux créés parSleuth.

A présent, vous devriez avoir une idée du pouvoir de cette bibliothèque. Jetons un coup d'œil à un autre exemple pour démontrer davantage à quel point cette bibliothèque fait partie intégrante de la journalisation.

3.2. Requête Web simple avec accès au service

Commençons par créer un service avec une seule méthode:

@Service
public class SleuthService {

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

Maintenant, injectons notre service dans notre contrôleur et ajoutons une méthode de mappage de requêtes qui y accède:

@Autowired
private SleuthService sleuthService;

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

Enfin, redémarrez l'application et accédez à «http: // localhost: 8080 / same-span». Surveillez la sortie du journal qui ressemble à:

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

Notez que les identifiants de trace et d'étendue sont les mêmes entre les deux journaux, même si les messages proviennent de deux classes différentes. Cela rend simple l'identification de chaque journal lors d'une requête en recherchant lestraceId de cette requête.

C'est le comportement par défaut, une requête obtient un seultraceId etspanId. Mais nous pouvons ajouter manuellement des étendues comme bon nous semble. Jetons un œil à un exemple qui utilise cette fonctionnalité.

3.3. Ajout manuel d'une étendue

Pour commencer, ajoutons un nouveau contrôleur:

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

Et maintenant, ajoutons la nouvelle méthode dans notre 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");
}

Notez que nous avons également ajouté un nouvel objet,Tracer. L'instancetracer est créée parSpring Sleuth lors du démarrage et est mise à disposition de notre classe via l'injection de dépendances.

Les traces doivent être démarrées et arrêtées manuellement. Pour ce faire, le code qui s'exécute dans unspan créé manuellement est placé dans un bloctry-finally pour garantir que lespan est fermé quel que soit le succès de l'opération. Notez également que la nouvelle plage doit être placée dans la portée.

Redémarrez l'application et accédez à «http: // localhost: 8080 / new-span». Surveillez la sortie du journal qui ressemble à:

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

Nous pouvons voir que le troisième journal partage lestraceId avec les autres, mais il a unspanId unique. Cela peut être utilisé pour localiser différentes sections dans une seule demande pour un traçage plus fin.

Voyons maintenant la prise en charge deSleuth’s pour les threads.

3.4. Extension des runnables

Pour démontrer les capacités de thread deSleuth, ajoutons d'abord une classe de configuration pour configurer un 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);
    }
}

Il est important de noter ici l'utilisation deLazyTraceExecutor. Cette classe vient de la bibliothèqueSleuth et est un type spécial d'exécuteur qui propage nostraceId vers de nouveaux threads et crée de nouveauxspanId dans le processus.

Maintenant, connectons cet exécuteur à notre contrôleur et utilisons-le dans une nouvelle méthode de mappage de requêtes:

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

Avec notre exécutable en place, redémarrons notre application et accédez à "http: // localhost: 8080 / new-thread". Surveillez la sortie du journal qui ressemble à:

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

Tout comme l'exemple précédent, nous pouvons voir que tous les journaux partagent les mêmestraceId. Mais le journal provenant du runnable a une étendue unique qui permet de suivre le travail effectué dans ce fil. Rappelez-vous que cela se produit à cause desLazyTraceExecutor, si nous devions utiliser un exécuteur normal, nous continuerions à voir les mêmesspanId utilisés dans le nouveau thread.

Examinons maintenant la prise en charge deSleuth’s pour les méthodes@Async.

3.5. Prise en charge de@Async

Pour ajouter la prise en charge asynchrone, modifions d'abord notre classeThreadConfig pour activer cette fonctionnalité:

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

Notez que nous étendonsAsyncConfigurerSupport pour spécifier notre exécuteur asynchrone et utilisonsLazyTraceExecutor pour nous assurer que les tracesIds et spanIds sont propagés correctement. Nous avons également ajouté@EnableAsync en tête de notre classe.

Ajoutons maintenant une méthode asynchrone à notre service:

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

Appelons maintenant cette méthode depuis notre contrôleur:

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

    return "success";
}

Enfin, redémarrons notre service et accédez à "http: // localhost: 8080 / async". Surveillez la sortie du journal qui ressemble à:

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

Nous pouvons voir ici que tout comme notre exemple exécutable,Sleuth propage lestraceId dans la méthode async et ajoute un spanId unique.

Passons maintenant à un exemple d'utilisation de la prise en charge du printemps pour les tâches planifiées.

3.6. Prise en charge de@Scheduled

Enfin, voyons commentSleuth fonctionne avec les méthodes@Scheduled. Pour ce faire, mettons à jour notre classeThreadConfig pour activer la planification:

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

Notez que nous avons implémenté l'interfaceSchedulingConfigurer et remplacé sa méthode configureTasks. Nous avons également ajouté@EnableScheduling en tête de notre classe.

Ensuite, ajoutons un service pour nos tâches planifiées:

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

Dans cette classe, nous avons créé une seule tâche planifiée avec un délai fixe de 30 secondes.

Redémarons maintenant notre application et attendons que notre tâche soit exécutée. Regardez la console pour une sortie comme celle-ci:

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

Nous pouvons voir ici queSleuth a créé de nouveaux identifiants de trace et d'étendue pour notre tâche. Chaque instance d'une tâche recevra par défaut sa propre trace et sa propre étendue.

4. Conclusion

En conclusion, nous avons vu commentSpring Sleuth peut être utilisé dans diverses situations au sein d'une même application Web. Nous pouvons utiliser cette technologie pour corréler facilement les journaux d'une seule demande, même lorsque cette demande s'étend sur plusieurs threads.

À présent, nous pouvons voir commentSpring Cloud Sleuth peut nous aider à garder notre santé mentale lors du débogage d'un environnement multi-thread. En identifiant chaque opération dans untraceId et chaque étape dans unspanId, nous pouvons vraiment commencer à décomposer notre analyse des travaux complexes dans nos journaux.

Même si nous n'utilisons pas le cloud,Spring Sleuth est probablement une dépendance critique dans presque tous les projets; il est facile d’intégrer et deis a massive addition of value.

À partir de là, vous souhaiterez peut-être étudier d'autres fonctionnalités deSleuth. Il peut prendre en charge le traçage dans les systèmes distribués utilisantRestTemplate, sur les protocoles de messagerie utilisés parRabbitMQ etRedis, et via une passerelle comme Zuul.

Comme toujours, vous pouvez trouver le code sourceover on Github.