paint-brush
Traçage OpenTelemetry dans Spring Boot : choisir entre Java Agent et Micrometer pourpar@nfrankel
193 lectures

Traçage OpenTelemetry dans Spring Boot : choisir entre Java Agent et Micrometer pour

par Nicolas Fränkel8m2024/08/11
Read on Terminal Reader

Trop long; Pour lire

Cet article compare les solutions de traçage OpenTelemetry dans une application Spring Boot utilisant Java Agent v1, Java Agent v2 et Micrometer Tracing. Il met en évidence les différences de configuration, de fonctionnalités et de capacités de traçage, offrant un aperçu de l'efficacité et de l'application de chaque approche pour l'observabilité.
featured image - Traçage OpenTelemetry dans Spring Boot : choisir entre Java Agent et Micrometer pour
Nicolas Fränkel HackerNoon profile picture

Ma démonstration d'OpenTelemetry Tracing comprend deux composants Spring Boot. L'un utilise l'agent Java, et j'ai remarqué un comportement différent lorsque je l'ai récemment mis à niveau de la version 1.x à la version 2.x. Dans l'autre, j'utilise Micrometer Tracing car je compile en natif GraalVM, et il ne peut pas traiter les agents Java.



Dans cet article, je souhaite comparer ces trois approches : l’agent Java v1, l’agent Java v2 et le traçage micrométrique.

L'application de base et son infrastructure

J'utiliserai la même application de base : une simple application Spring Boot, codée en Kotlin. Elle offre un seul point de terminaison.


  • La fonction au-delà du point de terminaison est nommée entry()
  • Il appelle une autre fonction nommée intermediate()
  • Ce dernier utilise une instance WebClient , le remplacement de RestTemplate , pour effectuer un appel au point de terminaison ci-dessus
  • Pour éviter une boucle infinie, je passe un en-tête de requête personnalisé : si la fonction entry() le trouve, elle ne continue pas plus loin


Exemple de diagramme de séquence d'application


Cela se traduit par le code suivant :


 @SpringBootApplication class Agent1xApplication @RestController class MicrometerController { private val logger = LoggerFactory.getLogger(MicrometerController::class.java) @GetMapping("/{message}") fun entry(@PathVariable message: String, @RequestHeader("X-done") done: String?) { logger.info("entry: $message") if (done == null) intermediate() } fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() } }


Pour chaque configuration, je vérifierai deux étapes : l'étape principale, avec OpenTelemetry activé, et une étape de personnalisation pour créer des étendues internes supplémentaires.

Traçage au micromètre

Micrometer Tracing est issu de Micrometer , une « façade d'observabilité d'application indépendante du fournisseur ».


Micrometer Tracing fournit une façade simple pour les bibliothèques de traceurs les plus populaires, vous permettant d'instrumenter votre code d'application basé sur JVM sans dépendance vis-à-vis du fournisseur. Il est conçu pour ajouter peu ou pas de frais généraux à votre activité de collecte de traçage tout en maximisant la portabilité de votre effort de traçage.


-- Site de traçage micrométrique


Pour démarrer avec Micrometer Tracing, il faut ajouter quelques dépendances :


  • Actionneur de démarrage à ressort, org.springframework.boot:spring-boot-starter-actuator
  • Le micromètre traçant lui-même, io.micrometer:micrometer-tracing
  • Un « pont » vers l'API backend de traçage de cible. Dans mon cas, il s'agit d'OpenTelemetry, d'où io.micrometer:micrometer-tracing-bridge-otel
  • Un exportateur concret vers le backend, io.opentelemetry:opentelemetry-exporter-otlp


Nous n’avons pas besoin d’une nomenclature car les versions sont déjà définies dans le parent Spring Boot.


Cependant, nous avons besoin de deux paramètres de configuration d'exécution : où les traces doivent-elles être envoyées et quel est le nom du composant. Ils sont régis par les variables MANAGEMENT_OTLP_TRACING_ENDPOINT et SPRING_APPLICATION_NAME .


 services: jaeger: image: jaegertracing/all-in-one:1.55 environment: - COLLECTOR_OTLP_ENABLED=true #1 ports: - "16686:16686" micrometer-tracing: build: dockerfile: Dockerfile-micrometer environment: MANAGEMENT_OTLP_TRACING_ENDPOINT: http://jaeger:4318/v1/traces #2 SPRING_APPLICATION_NAME: micrometer-tracing #3
  1. Activer le collecteur OpenTelemetry pour Jaeger
  2. URL complète vers le point de terminaison gRPC Jaeger OpenTelemetry
  3. Définir le nom du service OpenTelemetry


Voici le résultat :

Traces micrométriques sur Jaeger sans personnalisation


Sans aucune personnalisation, Micrometer crée des étendues lors de la réception et de l'envoi de requêtes HTTP.


Le framework doit injecter de la magie dans le RestClient pour l'envoi. Nous devons laisser le premier instancier le second pour cela :


 @SpringBootApplication class MicrometerTracingApplication { @Bean fun restClient(builder: RestClient.Builder) = builder.baseUrl("http://localhost:8080/done").build() }


Nous pouvons créer des intervalles manuels de plusieurs manières, notamment via l'API OpenTelemetry elle-même. Cependant, la configuration nécessite beaucoup de code standard. Le moyen le plus simple est l' API Observation de Micrometer. Son principal avantage est d'utiliser une API unique qui gère à la fois les métriques et les traces .


Diagramme de classe de l'API d'observation

Voici le code mis à jour :


 class MicrometerController( private val restClient: RestClient, private val registry: ObservationRegistry ) { @GetMapping("/{message}") fun entry(@PathVariable message: String, @RequestHeader("X-done") done: String?) { logger.info("entry: $message") val observation = Observation.start("entry", registry) if (done == null) intermediate(observation) observation.stop() } fun intermediate(parent: Observation) { logger.info("intermediate") val observation = Observation.createNotStarted("intermediate", registry) .parentObservation(parent) .start() restClient.get() .header("X-done", "true") .retrieve() .toBodilessEntity() observation.stop() } }


Les appels d’observation ajoutés se reflètent sur les traces générées :


Traces micrométriques sur Jaeger avec l'API Observation

Agent OpenTelemetry v1

Une alternative à Micrometer Tracing est l' agent Java générique OpenTelemetry . Son principal avantage est qu'il n'a d'impact ni sur le code ni sur les développeurs ; l'agent est une préoccupation purement axée sur l'exécution.


 java -javaagent:opentelemetry-javaagent.jar agent-one-1.0-SNAPSHOT.jar


L'agent respecte la configuration d'OpenTelemetry avec les variables d'environnement :


 services: agent-1x: build: dockerfile: Dockerfile-agent1 environment: OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 #1 OTEL_RESOURCE_ATTRIBUTES: service.name=agent-1x #2 OTEL_METRICS_EXPORTER: none #3 OTEL_LOGS_EXPORTER: none #4 ports: - "8081:8080"
  1. Définissez le protocole, le domaine et le port. La bibliothèque ajoute /v1/traces
  2. Définir le nom du service OpenTelemetry
  3. N'exportez ni les métriques ni les journaux


Sans plus de configuration, on obtient les traces suivantes :


Agent v1 trace sur Jaeger sans personnalisation


L'agent suit automatiquement les requêtes, reçues et envoyées, ainsi que les fonctions marquées avec des annotations liées à Spring . Les traces sont correctement imbriquées les unes dans les autres, en fonction de la pile d'appels. Pour tracer des fonctions supplémentaires, nous devons ajouter une dépendance à notre base de code, io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations . Nous pouvons désormais annoter des fonctions précédemment non tracées avec l'annotation @WithSpan .

Diagramme de classe @WithSpan


La partie value() contrôle l'étiquette de la trace, tandis que le kind se traduit par un attribut span.kind . Si la valeur est définie sur une chaîne vide, ce qui est la valeur par défaut, elle renvoie le nom de la fonction. Pour mes besoins, les valeurs par défaut sont suffisantes.


 @WithSpan fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() }


Il génère la nouvelle trace intermediate() attendue :

Agent v1 trace sur Jaeger avec des annotations

Agent OpenTelemetry v2

OpenTelemetry a publié une nouvelle version majeure de l'agent en janvier de cette année. J'ai mis à jour ma démo avec celle-ci ; les traces ne sont désormais créées que lorsque l'application reçoit et envoie des requêtes.

Agent v2 trace sur Jaeger sans personnalisation


Comme pour la version précédente, nous pouvons ajouter des traces avec l'annotation @WithSpan . La seule différence est qu'il faut également annoter la fonction entry() . Elle n'est pas tracée par défaut.

Agent v2 trace sur Jaeger avec des annotations


Discussion

Spring a connu du succès pour deux raisons : il a simplifié les solutions complexes, c'est-à-dire les EJB 2, et a fourni une couche d'abstraction par rapport aux bibliothèques concurrentes. Micrometer Tracing a commencé comme une couche d'abstraction par rapport à Zipkin et Jaeger, et cela avait tout son sens. Cet argument devient discutable avec OpenTelemetry pris en charge par la plupart des bibliothèques dans les langages de programmation et les collecteurs de traces. L'API Observation est toujours un avantage considérable de Micrometer Tracing, car elle utilise une seule API sur Metrics et Traces.


Du côté de l'agent Java, la configuration d'OpenTelemetry est similaire sur toutes les piles technologiques et bibliothèques - variables d'environnement. J'ai été un peu déçu lors de la mise à niveau de la v1 vers la v2, car le nouvel agent n'est pas compatible Spring : les fonctions annotées Spring ne sont pas tracées par défaut.


Au final, c'est une sage décision. Il est bien mieux d'être explicite sur les intervalles que vous souhaitez que d'en supprimer certains que vous ne voulez pas voir.


Merci à Jonatan Ivanov pour son aide et sa critique .


Le code source complet de cet article peut être trouvé sur GitHub :

Pour aller plus loin :


Initialement publié sur A Java Geek le 3 août 2024