Mi demostración de OpenTelemetry Tracing incluye dos componentes de Spring Boot. Uno usa el agente Java y noté un comportamiento diferente cuando lo actualicé recientemente de v1.x a v2.x. En el otro, uso Micrometer Tracing porque compilo en GraalVM nativo y no puede procesar agentes Java.
En esta publicación, quiero comparar estos tres enfoques: agente Java v1, agente Java v2 y Micrometer Tracing.
Usaré la misma aplicación base: una aplicación Spring Boot sencilla, codificada en Kotlin. Ofrece un único punto de conexión.
entry()
intermediate()
WebClient
, el reemplazo de RestTemplate
, para realizar una llamada al punto final anterior.entry()
lo encuentra, no continúa.
Se traduce al siguiente código:
@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() } }
Para cada configuración, comprobaré dos etapas: la etapa principal, con OpenTelemetry habilitado, y una etapa de personalización para crear tramos internos adicionales.
Micrometer Tracing proviene de Micrometer , una "fachada de observabilidad de aplicaciones independiente del proveedor".
Micrometer Tracing ofrece una interfaz sencilla para las bibliotecas de trazadores más populares, lo que le permite instrumentar el código de su aplicación basada en JVM sin depender de un proveedor. Está diseñado para agregar poca o ninguna sobrecarga a su actividad de recopilación de trazadores y, al mismo tiempo, maximizar la portabilidad de su esfuerzo de trazado.
Para comenzar con el seguimiento micrométrico, es necesario agregar algunas dependencias:
org.springframework.boot:spring-boot-starter-actuator
io.micrometer:micrometer-tracing
io.micrometer:micrometer-tracing-bridge-otel
io.opentelemetry:opentelemetry-exporter-otlp
No necesitamos una lista de materiales porque las versiones ya están definidas en el elemento principal de Spring Boot.
Sin embargo, necesitamos dos parámetros de configuración en tiempo de ejecución: dónde se deben enviar los seguimientos y cuál es el nombre del componente. Estos parámetros se controlan mediante las variables MANAGEMENT_OTLP_TRACING_ENDPOINT
y 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
Aquí está el resultado:
Sin ninguna personalización, Micrometer crea intervalos al recibir y enviar solicitudes HTTP.
El marco debe inyectar magia en RestClient
para realizar el envío. Debemos dejar que el primero cree una instancia del segundo para ello:
@SpringBootApplication class MicrometerTracingApplication { @Bean fun restClient(builder: RestClient.Builder) = builder.baseUrl("http://localhost:8080/done").build() }
Podemos crear intervalos manuales de varias formas, una de ellas a través de la propia API de OpenTelemetry. Sin embargo, la configuración requiere una gran cantidad de código repetitivo. La forma más sencilla es la API de observación de Micrometer. Su principal beneficio es utilizar una única API que administra tanto las métricas como los rastros .
Aquí está el código actualizado:
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() } }
Las llamadas de observación agregadas se reflejan en los rastros generados:
Una alternativa a Micrometer Tracing es el agente genérico OpenTelemetry Java . Su principal beneficio es que no afecta ni al código ni a los desarrolladores; el agente es una preocupación puramente de tiempo de ejecución.
java -javaagent:opentelemetry-javaagent.jar agent-one-1.0-SNAPSHOT.jar
El agente respeta la configuración de OpenTelemetry con variables de entorno:
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
Sin más configuración obtenemos los siguientes rastros:
El agente rastrea automáticamente las solicitudes, tanto recibidas como enviadas, así como las funciones marcadas con anotaciones relacionadas con Spring . Los rastros se anidan correctamente uno dentro del otro, de acuerdo con la pila de llamadas. Para rastrear funciones adicionales, necesitamos agregar una dependencia a nuestra base de código, io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations
. Ahora podemos anotar funciones no rastreadas previamente con la anotación @WithSpan
.
La parte value()
controla la etiqueta del seguimiento, mientras que el kind
se traduce como un atributo span.kind
. Si el valor se establece en una cadena vacía, que es el valor predeterminado, se muestra el nombre de la función. Para mis propósitos, los valores predeterminados son suficientes.
@WithSpan fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() }
Produce el nuevo rastro intermediate()
esperado:
OpenTelemetry lanzó una nueva versión principal del agente en enero de este año. Actualicé mi demostración con ella; ahora los rastros solo se crean cuando la aplicación recibe y envía solicitudes.
Al igual que en la versión anterior, podemos agregar trazas con la anotación @WithSpan
. La única diferencia es que también debemos anotar la función entry()
. No se traza de forma predeterminada.
Spring tuvo éxito por dos razones: simplificó soluciones complejas, es decir , EJBs 2, y proporcionó una capa de abstracción sobre las bibliotecas de la competencia. Micrometer Tracing comenzó como una capa de abstracción sobre Zipkin y Jaeger, y tenía todo el sentido. Este argumento se vuelve discutible cuando OpenTelemetry es compatible con la mayoría de las bibliotecas en todos los lenguajes de programación y recopiladores de seguimiento. La API de observación sigue siendo un beneficio considerable de Micrometer Tracing, ya que utiliza una única API sobre Metrics y Traces.
Del lado del agente de Java, la configuración de OpenTelemetry es similar en todas las bibliotecas y pilas de tecnología: variables de entorno. Me decepcioné un poco cuando actualicé de la versión 1 a la versión 2, ya que el nuevo agente no es compatible con Spring: las funciones anotadas con Spring no se rastrean de forma predeterminada.
Al final, es una decisión acertada. Es mucho mejor ser explícito sobre los intervalos que quieres que eliminar algunos que no quieres ver.
Gracias a Jonatan Ivanov por su ayuda y su reseña .
El código fuente completo de esta publicación se puede encontrar en GitHub:
Para ir más allá:
Publicado originalmente en A Java Geek el 3 de agosto de 2024