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.
J'utiliserai la même application de base : une simple application Spring Boot, codée en Kotlin. Elle offre un seul point de terminaison.
entry()
intermediate()
WebClient
, le remplacement de RestTemplate
, pour effectuer un appel au point de terminaison ci-dessusentry()
le trouve, elle ne continue pas plus loin
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.
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.
Pour démarrer avec Micrometer Tracing, il faut ajouter quelques dépendances :
org.springframework.boot:spring-boot-starter-actuator
io.micrometer:micrometer-tracing
io.micrometer:micrometer-tracing-bridge-otel
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
Voici le résultat :
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 .
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 :
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"
/v1/traces
Sans plus de configuration, on obtient les traces suivantes :
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
.
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 :
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.
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.
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