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:

EndpointO que ele ofereceRecomendação de exposição
healthStatus agregado da aplicação e de suas dependênciasPúblico sem detalhes; detalhes para usuários autenticados
infoVersão do build, commit do git, dados customizadosPode ser exposto internamente sem problema
metrics / prometheusMeters do Micrometer, em JSON ou no formato texto do PrometheusExpor só para o scraper
loggersLer e alterar níveis de log em tempo de execuçãoAutenticado
env, configpropsConfiguração efetiva e propriedades vinculadasAutenticado, com valores mascarados
beans, conditions, mappingsIntrospecção do contexto para depuraçãoAutenticado, ou apenas local
threaddump, heapdumpDiagnóstico sob cargaAutenticado; heap dumps contêm segredos
shutdownParada ordenada via POSTDesabilitado, 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.
Configuração no Spring Boot: properties, YAML, profiles e @ConfigurationPropertiesConfiguração externalizada no Spring Boot 3: precedência das fontes, YAML e profiles, @ConfigurationProperties tipado, config import e segredos.Iniciante9 min de leituraEmpacotar e fazer deploy de Spring Boot 3: jar executável, contêineres e imagens nativasEmpacote um serviço Spring Boot 3 em jar executável, crie uma imagem em camadas ou com buildpacks, ajuste a JVM no contêiner e desligue sem perder requisições.Intermediário9 min de leituraSpring Cloud para microsserviços: Config Server, discovery, gateway e resiliênciaO que o Spring Cloud acrescenta ao Spring Boot 3: configuração centralizada, service discovery, load balancing no cliente, API gateway e circuit breakers.Avançado9 min de leituraDevOpsFundamentos de Kubernetes: arquitetura, Pods, Deployments e ServicesComo um cluster Kubernetes é montado, o que um Pod realmente é e como Deployments, Services, ConfigMaps e labels formam uma aplicação em execução.Intermediário10 min de leituraDadosGerenciamento centralizado de logs com Elasticsearch, Logstash e KibanaLogs estruturados, Beats e Elastic Agent, ingest pipelines e grok, index templates, retenção com ILM, dashboards no Kibana e dimensionamento do cluster.Intermediário9 min de leituraArquiteturaPerformance de aplicações: latência, percentis, profiling e a JVMUm método baseado em evidências para deixar aplicações rápidas: lei de Little, percentis, profiling, GC, acesso ao banco de dados e cache.Avançado12 min de leitura