Spring Boot e Spring Cloud
Spring Boot Actuator e observabilidade: health, métricas, traces e logs
Intermediário8 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Você vai saber expor exatamente os endpoints de management de que um serviço precisa, configurar as probes de liveness e readiness, publicar métricas da aplicação e de negócio no Prometheus, propagar o contexto de trace entre serviços e até os logs, e fechar toda essa superfície para que a observabilidade não vire um vazamento de informações.
O que você vai aprender
- Habilitar e expor endpoints do Actuator de forma seletiva e protegê-los
- Configurar grupos de health e as probes de liveness e readiness do Kubernetes, e escrever um HealthIndicator customizado
- Publicar métricas da JVM, HTTP e de negócio via Micrometer para o Prometheus
- Adicionar tracing distribuído com Micrometer Tracing e correlacionar traces com as linhas de log
- Explicar a Observation API e instrumentar com ela um caminho de código crítico
Um serviço que você não consegue observar é um serviço que você não consegue operar. O Spring Boot Actuator transforma um jar em algo que um operador pode interrogar: ele está saudável, o que está fazendo, com que rapidez, e qual foi a requisição que falhou às 03:14? Esta página cobre os quatro pilares como o Boot 3 os implementa: endpoints de management, health e probes, métricas com Micrometer, e traces com Micrometer Tracing e correlação de logs. No caminho, trata da parte que a maioria dos tutoriais pula: não expor tudo isso na internet pública.
Endpoints: habilitar, expor e proteger
Adicione o spring-boot-starter-actuator. Cada endpoint tem dois interruptores independentes: habilitado (o bean existe?) e exposto (ele é acessível via HTTP ou JMX?). Todos os endpoints, exceto shutdown, vêm habilitados por padrão; via HTTP, só o health é exposto. A exposição é uma lista:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,loggers
base-path: /actuator # padrão; mude se / já estiver ocupado por um prefixo de API
endpoint:
health:
show-details: when-authorized
env:
show-values: when-authorized # o Boot 3 mascara por padrão todos os valores de env e configprops
server:
port: 9090 # opcional: tráfego de management em uma porta separada
Os endpoints que vale a pena conhecer:
| Endpoint | O que ele oferece | Recomendação de exposição |
|---|---|---|
health | Status agregado da aplicação e de suas dependências | Público sem detalhes; detalhes para usuários autenticados |
info | Versão do build, commit do git, dados customizados | Pode ser exposto internamente sem problema |
metrics / prometheus | Meters do Micrometer, em JSON ou no formato texto do Prometheus | Expor só para o scraper |
loggers | Ler e alterar níveis de log em tempo de execução | Autenticado |
env, configprops | Configuração efetiva e propriedades vinculadas | Autenticado, com valores mascarados |
beans, conditions, mappings | Introspecção do contexto para depuração | Autenticado, ou apenas local |
threaddump, heapdump | Diagnóstico sob carga | Autenticado; heap dumps contêm segredos |
shutdown | Parada ordenada via POST | Desabilitado, a menos que você realmente precise |
Rodar os endpoints de management em uma porta separada (management.server.port) é o hardening mais simples: a porta principal fica ligada ao load balancer e a porta de management apenas à rede do cluster. Quando a aplicação usa Spring Security, os endpoints na porta principal são protegidos pela mesma filter chain, e os matchers de EndpointRequest permitem escrever a regra com precisão:
@Bean
SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.anyRequest().hasRole("OPS"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
O endpoint info fica vazio até você alimentá-lo. Os plugins do Maven e do Gradle podem gerar informações de build (o goal build-info do spring-boot-maven-plugin, springBoot { buildInfo() } no Gradle), o plugin git-commit-id adiciona os dados do commit e qualquer bean InfoContributor adiciona entradas customizadas. Com info.env.enabled=true, as propriedades info.* também viram entradas.
Health e probes
O health agrega os beans HealthIndicator. O Boot autoconfigura um para cada dependência que conhece: o DataSource, Redis, RabbitMQ, Kafka, Elasticsearch, espaço em disco e outros. Cada um retorna UP, DOWN, OUT_OF_SERVICE ou UNKNOWN; o agregado é DOWN se qualquer contribuidor estiver DOWN, e o status HTTP é 503 para DOWN e OUT_OF_SERVICE, e 200 nos demais casos.
Um indicador customizado é um bean pequeno:
@Component
class PaymentGatewayHealthIndicator implements HealthIndicator {
private final PaymentGatewayClient gateway;
PaymentGatewayHealthIndicator(PaymentGatewayClient gateway) {
this.gateway = gateway;
}
@Override
public Health health() {
try {
Duration latency = gateway.ping();
return Health.up().withDetail("latency", latency.toMillis() + "ms").build();
} catch (Exception ex) {
return Health.down(ex).build(); // a mensagem da exceção só aparece com show-details
}
}
}
O nome do bean sem o sufixo HealthIndicator vira a chave do contribuidor (paymentGateway). Mantenha os indicadores leves: eles rodam a cada probe, e os orquestradores fazem probes com frequência. Faça cache do resultado por alguns segundos se a verificação for uma chamada de rede.
Liveness e readiness
O Kubernetes distingue duas perguntas, e sua configuração de health também deveria. O Boot expõe dois grupos de health, liveness e readiness, em /actuator/health/liveness e /actuator/health/readiness. Eles são habilitados automaticamente quando a aplicação detecta que está rodando no Kubernetes (pela variável de ambiente KUBERNETES_SERVICE_HOST) e podem ser forçados com management.endpoint.health.probes.enabled=true.
Por padrão, o grupo liveness contém apenas o LivenessStateHealthIndicator, que reflete o LivenessState da própria aplicação e não toca em sistemas externos. O grupo readiness contém apenas o ReadinessStateHealthIndicator, que passa para REFUSING_TRAFFIC durante a inicialização e o desligamento. Você adiciona explicitamente à readiness as dependências que importam:
management:
endpoint:
health:
group:
readiness:
include: readinessState,db,paymentGateway
liveness:
include: livenessState
Não coloque o banco de dados na liveness. Se o banco cair, reiniciar todos os pods não vai trazê-lo de volta; só vai gerar tempestades de reconexão. A readiness é a alavanca certa: o pod para de receber tráfego e volta quando a dependência se recupera.
O trecho de deployment correspondente:
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 9090 }
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 9090 }
periodSeconds: 5
O código da aplicação pode alternar os estados com AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC), por exemplo enquanto um cache aquece. Com server.shutdown=graceful, o Boot primeiro marca a readiness como recusando tráfego, espera as requisições em andamento terminarem e só então para, o que combina naturalmente com rolling updates.
Métricas com Micrometer
As métricas do Actuator são construídas sobre o Micrometer, uma fachada neutra em relação a fornecedores, do mesmo jeito que o SLF4J é para logging. O Boot registra meters para a JVM (memória, garbage collection, threads, carregamento de classes), o sistema (CPU, descritores de arquivo), requisições do servidor HTTP, chamadas de RestClient/WebClient, pools de DataSource, caches, executors e mais. GET /actuator/metrics/http.server.requests mostra o meter com suas tags; aprofunde com ?tag=uri:/api/orders&tag=status:500.
Para enviá-las para algum lugar, adicione um registry. Para o Prometheus:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
e exponha prometheus; o scraper lê /actuator/prometheus no formato texto de exposição. Registries para OpenTelemetry (OTLP), Datadog, New Relic, Graphite, InfluxDB e outros seguem o mesmo padrão e podem coexistir.
É nas métricas de negócio que a observabilidade se paga. Injete o MeterRegistry:
@Service
class CheckoutService {
private final Counter ordersPlaced;
private final Timer paymentTimer;
CheckoutService(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("shop.orders.placed")
.description("Orders successfully placed")
.register(registry);
this.paymentTimer = Timer.builder("shop.payment.duration")
.publishPercentileHistogram() // permite que o Prometheus calcule quantis
.register(registry);
}
Order checkout(Cart cart) {
Payment payment = paymentTimer.record(() -> gateway.charge(cart.total()));
Order order = orders.create(cart, payment);
ordersPlaced.increment();
return order;
}
}
Tags adicionam dimensões (.tag("channel", "web")), e cada valor distinto de tag cria uma nova série temporal; nunca use como tag valores ilimitados, como ids de cliente ou texto livre. @Timed em um método funciona quando o TimedAspect está registrado como bean e o AOP está no classpath. Tags comuns a todos os meters (management.metrics.tags.application=shop, region=eu-west-1) tornam os dashboards filtráveis entre serviços.
Os percentis merecem uma observação: publishPercentiles os calcula na aplicação, e eles não podem ser agregados entre instâncias; publishPercentileHistogram publica buckets para que o sistema de monitoramento calcule os quantis sobre toda a frota. Prefira o histograma para tudo o que você for agregar.
Tracing e a Observation API
As métricas dizem que o percentil 99 disparou; os traces dizem qual chamada downstream causou isso. O Spring Boot 3 substituiu o Spring Cloud Sleuth pelo Micrometer Tracing, uma fachada sobre uma implementação de tracer. Escolha um bridge e um exporter:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
management:
tracing:
sampling:
probability: 0.1 # padrão 0.1; 1.0 em desenvolvimento
otlp:
tracing:
endpoint: http://otel-collector:4318/v1/traces
Com isso configurado, requisições HTTP de entrada, chamadas de RestClient/WebClient, JDBC (via a biblioteca datasource-micrometer) e interações com Kafka e RabbitMQ geram spans, e o header W3C traceparent propaga o contexto entre serviços (o B3 está disponível via management.tracing.propagation.type). micrometer-tracing-bridge-brave com Zipkin é a combinação alternativa.
O conceito que unifica tudo por baixo é a Observation API, do Micrometer 1.10. Uma Observation é uma unidade de trabalho que, ao ser iniciada e encerrada, produz um timer e um span e o que mais os handlers registrados fizerem. Instrumentar uma única vez o seu próprio caminho crítico entrega os dois sinais:
Observation.createNotStarted("shop.checkout", observationRegistry)
.lowCardinalityKeyValue("channel", cart.channel()) // vira tag de métrica e atributo do span
.highCardinalityKeyValue("cart.id", cart.id()) // só no span, nunca como tag de métrica
.observe(() -> checkoutService.checkout(cart));
@Observed em um método faz o mesmo de forma declarativa quando o ObservedAspect está registrado. A divisão entre baixa e alta cardinalidade codifica diretamente na API a regra de tags da seção de métricas.
Correlacionando logs com traces
Com o tracing ativo, o Boot 3 coloca traceId e spanId no MDC de logging e os inclui no pattern padrão do console como [app,traceId,spanId]. Copiar um trace id de uma linha de log para a interface de tracing, ou de um trace para a busca de logs, fecha o ciclo entre os dois sistemas. Se você customizar logging.pattern.console, mantenha %X{traceId} e %X{spanId} nele, ou use logging.pattern.correlation (Boot 3.1+) para alterar só esse trecho.
O logging estruturado completa o trabalho: o Boot 3.4 consegue gravar linhas JSON no formato Elastic Common Schema ou Logstash com logging.structured.format.console=ecs, de modo que uma plataforma de logs indexa trace ids e campos customizados sem parsing por regex. Devolver o trace id aos clientes nas respostas de erro, como sugerido no guia de tratamento de erros, transforma um chamado de suporte em uma consulta direta.
Erros comuns
Expor * e esquecer disso. O management.endpoints.web.exposure.include=* de um tutorial deixa env, heapdump e threaddump acessíveis a qualquer um que encontre /actuator. Liste os endpoints de que você precisa e coloque o resto atrás de autenticação ou de uma porta separada.
Colocar dependências externas na probe de liveness. Uma queda do banco de dados vira um loop de restarts em toda a frota. A liveness fica interna; a readiness carrega as dependências.
Health checks caros. Um indicador que roda uma query sobre uma tabela grande, ou chama uma API de terceiros sem timeout, transforma a própria probe em um problema de carga. Dê um timeout a cada verificação e faça cache dos resultados.
Tags de alta cardinalidade. Uma tag userId em http.server.requests cria uma série temporal por usuário e pode esgotar tanto o registry quanto o backend de monitoramento. Use valores de alta cardinalidade só em spans.
Amostrar 100 % em produção sem planejamento de capacidade. Traces são volumosos. Comece com a probabilidade padrão e aumente-a para serviços específicos ou durante incidentes, ou use tail-based sampling no collector.
Supor que as métricas aparecem sem registry. O endpoint metrics mostra os meters, mas nada é exportado até que uma dependência de registry seja adicionada; o endpoint prometheus não existe sem micrometer-registry-prometheus.
Principais pontos
- Os endpoints do Actuator vêm habilitados por padrão, mas só o
healthé exposto via HTTP; exponha uma lista explícita, proteja o resto e considere uma porta de management separada. - A liveness deve refletir apenas o estado do próprio processo; a readiness inclui as dependências de que uma requisição precisa, para que falhas desviem o tráfego em vez de reiniciar pods.
- O Micrometer fornece de fábrica métricas de JVM, HTTP e de pools; adicione um registry (Prometheus, OTLP e outros) para exportá-las e alguns meters de negócio com boas tags para torná-las significativas.
- O Micrometer Tracing substitui o Sleuth no Boot 3; escolha um bridge (OpenTelemetry ou Brave) e um exporter, defina a probabilidade de amostragem, e o contexto se propaga automaticamente.
- A Observation API instrumenta um caminho de código uma única vez e gera tanto um timer quanto um span; mantenha valores de alta cardinalidade fora das tags de métricas.
- Trace ids no MDC e logs JSON estruturados ligam logs, métricas e traces em um único caminho de investigação.
Fontes e leituras complementares
Perguntas frequentes
- Quais endpoints do Actuator são expostos via HTTP por padrão?
- Apenas o health. Todos os outros endpoints (info, metrics, env, beans, loggers, threaddump etc.) ficam habilitados, mas não são expostos na web até você listá-los em management.endpoints.web.exposure.include. Via JMX, a exposição padrão também se limita ao health desde o Boot 2.2. Esse padrão conservador existe porque endpoints como env e heapdump revelam a configuração e o conteúdo da memória.
- Qual a diferença entre liveness e readiness?
- A liveness responde a “este processo quebrou sem chance de recuperação?”, e uma probe com falha faz o Kubernetes reiniciar o contêiner, por isso ela não pode depender de sistemas externos. A readiness responde a “esta instância pode receber tráfego agora?”, e uma falha apenas remove o pod dos endpoints do Service, o que faz dela o lugar certo para refletir uma conexão perdida com o banco de dados ou uma fase de aquecimento.
- Ainda preciso do Spring Cloud Sleuth para tracing?
- Não. O Spring Cloud Sleuth chegou ao fim da vida com o Spring Boot 2.x. No Boot 3, o tracing é fornecido pelo Micrometer Tracing, com bridges para OpenTelemetry ou Brave, e autoconfigurado pelo Actuator. A API mudou (Observation e Tracer em vez dos tipos Span do Sleuth), mas o prefixo de propriedades management.tracing.* cobre a amostragem e a propagação.
- Como altero um nível de log em produção sem reiniciar?
- Exponha o endpoint loggers e envie um POST para /actuator/loggers/com.example.shop com um corpo JSON como {"configuredLevel": "DEBUG"}. A mudança é imediata e se perde no restart. Proteja o endpoint com autenticação, já que ele pode gerar volume de log suficiente para afetar o serviço.