Minha demonstração do OpenTelemetry Tracing apresenta dois componentes do Spring Boot. Um usa o agente Java, e notei um comportamento diferente quando o atualizei recentemente da v1.x para a v2.x. No outro, estou usando o Micrometer Tracing porque compilo para o GraalVM nativo, e ele não consegue processar agentes Java.
Nesta postagem, quero comparar essas três abordagens: Java agent v1, Java agent v2 e Micrometer Tracing.
Usarei o mesmo aplicativo base: um aplicativo Spring Boot simples, codificado em Kotlin. Ele oferece um único endpoint.
entry()
intermediate()
WebClient
, a substituição de RestTemplate
, para fazer uma chamada para o ponto de extremidade acimaentry()
o encontrar, ela não prossegue.
Isso se traduz no seguinte 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 configuração, verificarei dois estágios: o estágio primário, com o OpenTelemetry habilitado, e um estágio de personalização para criar intervalos internos adicionais.
O Micrometer Tracing deriva do Micrometer , uma "fachada de observabilidade de aplicativos independente de fornecedor".
O Micrometer Tracing fornece uma fachada simples para as bibliotecas de rastreadores mais populares, permitindo que você instrumente seu código de aplicativo baseado em JVM sem bloqueio de fornecedor. Ele foi projetado para adicionar pouca ou nenhuma sobrecarga à sua atividade de coleta de rastreamento, ao mesmo tempo em que maximiza a portabilidade do seu esforço de rastreamento.
Para começar com o Micrometer Tracing, é necessário adicionar algumas dependências:
org.springframework.boot:spring-boot-starter-actuator
io.micrometer:micrometer-tracing
io.micrometer:micrometer-tracing-bridge-otel
io.opentelemetry:opentelemetry-exporter-otlp
Não precisamos de uma BOM porque as versões já estão definidas no pai do Spring Boot.
No entanto, precisamos de dois parâmetros de configuração de tempo de execução: para onde os traces devem ser enviados e qual é o nome do componente. Eles são governados pelas variáveis MANAGEMENT_OTLP_TRACING_ENDPOINT
e 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
Aqui está o resultado:
Sem nenhuma personalização, o Micrometer cria intervalos ao receber e enviar solicitações HTTP.
O framework precisa injetar mágica no RestClient
para envio. Devemos deixar o primeiro instanciar o último para isso:
@SpringBootApplication class MicrometerTracingApplication { @Bean fun restClient(builder: RestClient.Builder) = builder.baseUrl("http://localhost:8080/done").build() }
Podemos criar spans manuais de várias maneiras, uma por meio da própria API OpenTelemetry. No entanto, a configuração requer muito código boilerplate. A maneira mais direta é a API de Observação do Micrometer. Seu principal benefício é usar uma única API que gerencia métricas e traces .
Aqui está o código atualizado:
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() } }
As chamadas de observação adicionadas refletem nos traços gerados:
Uma alternativa ao Micrometer Tracing é o OpenTelemetry Java Agent genérico. Seu principal benefício é que ele não impacta nem o código nem os desenvolvedores; o agente é uma preocupação puramente de escopo de tempo de execução.
java -javaagent:opentelemetry-javaagent.jar agent-one-1.0-SNAPSHOT.jar
O agente obedece à configuração do OpenTelemetry com variáveis de ambiente:
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
Sem mais configurações, obtemos os seguintes rastros:
O agente rastreia automaticamente as solicitações, tanto recebidas quanto enviadas, bem como as funções marcadas com anotações relacionadas ao Spring . Os rastreamentos são corretamente aninhados uns dentro dos outros, de acordo com a pilha de chamadas. Para rastrear funções adicionais, precisamos adicionar uma dependência à nossa base de código, io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations
. Agora podemos anotar funções não rastreadas anteriormente com a anotação @WithSpan
.
A parte value()
governa o rótulo do trace, enquanto o kind
é traduzido como um atributo span.kind
. Se o value for definido como uma string vazia, que é o padrão, ele emite o nome da função. Para meus propósitos, valores padrão são bons o suficiente.
@WithSpan fun intermediate() { logger.info("intermediate") RestClient.builder() .baseUrl("http://localhost:8080/done") .build() .get() .header("X-done", "true") .retrieve() .toBodilessEntity() }
Ele produz o novo rastreamento esperado intermediate()
:
A OpenTelemetry lançou uma nova versão principal do agente em janeiro deste ano. Atualizei minha demo com ela; os traces agora são criados somente quando o aplicativo recebe e envia solicitações.
Quanto à versão anterior, podemos adicionar traces com a anotação @WithSpan
. A única diferença é que também precisamos anotar a função entry()
. Ela não é rastreada por padrão.
O Spring se tornou bem-sucedido por dois motivos: ele simplificou soluções complexas, ou seja , EJBs 2, e forneceu uma camada de abstração sobre bibliotecas concorrentes. O Micrometer Tracing começou como uma camada de abstração sobre Zipkin e Jaeger, e fazia todo o sentido. Este argumento se torna discutível com o OpenTelemetry sendo suportado pela maioria das bibliotecas em linguagens de programação e coletores de rastreamento. A API de observação ainda é um benefício considerável do Micrometer Tracing, pois usa uma única API sobre Métricas e Rastreamentos.
No lado do Java Agent, a configuração do OpenTelemetry é similar em todas as pilhas de tecnologia e bibliotecas - variáveis de ambiente. Fiquei um pouco decepcionado quando atualizei da v1 para a v2, pois o novo agente não é compatível com Spring: funções anotadas com Spring não são rastreadas por padrão.
No final, é uma decisão sábia. É muito melhor ser explícito sobre os spans que você quer do que remover alguns que você não quer ver.
Obrigado a Jonatan Ivanov por sua ajuda e sua avaliação .
O código-fonte completo desta postagem pode ser encontrado no GitHub:
Para ir mais longe:
Originalmente publicado em A Java Geek em 3 de agosto de 2024