Começar
Aparência

Vale na hora e fica lembrada neste navegador.

A plataforma

14 · Observabilidade

Toda aplicação nasce cumprindo um contrato de telemetria. A pilha que recebe os sinais é do toolbox; a aplicação só emite, do jeito combinado. A biblioteca que cumpre o contrato é escolha da stack.

O contrato

O contrato de observabilidade: toda aplicação escreve log JSON no stdout, uma linha por evento, que o Alloy coleta e entrega ao Loki; expõe métricas no formato Prometheus numa porta interna, que o Prometheus lê pela rede do Kamal; manda traces por OTLP ao Alloy, que entrega ao Tempo; e responde GET /up com 200, sem login, para o healthcheck do kamal-proxy. Tudo chega ao Grafana. toda aplicaçãocumpre o contratostdoutlog JSON, um evento por linha/metricsporta interna, PrometheusOTLPtraces/up200, sem logintoolboxAlloyLokiTempoPrometheusGrafanakamal-proxycoletalê, pela rede kamalhealthchecka biblioteca que cumpre o contrato é escolha da stack
SinalContratoO que não fazer
LogsContrato: JSON no stdout, um evento por linha, com @timestamp, log.level, log.logger, message, service.name, e traceId/spanId quando houver traceO que não fazer: arquivo de log; exporter de log próprio; dado pessoal ou segredo no log
MétricasContrato: formato Prometheus numa porta interna separada, não publicada no Dockerfile nem roteada pelo proxyO que não fazer: push de métricas; métricas na porta pública
TracesContrato: OTLP para o Alloy do toolbox; propagação W3C traceparent; requisição HTTP, banco e chamadas de saída instrumentadosO que não fazer: falar direto com o Tempo; agent externo sem ADR
SaúdeContrato: GET /up → 200 trivial, sem autenticação, na porta da aplicaçãoO que não fazer: expor o health detalhado publicamente
IdentificaçãoContrato: service.name = nome da aplicação; deployment.environment = local | productionO que não fazer: nomes diferentes entre Kamal, métricas e traces
ResiliênciaContrato: coletor fora do ar não derruba requisição nem boot (export não bloqueante)O que não fazer: —

O que cada stack decide

LacunaExemplo (Java/Spring)
{{LACUNA:obs.log}}Exemplo (Java/Spring): log estruturado nativo do Spring Boot, formato ECS
{{LACUNA:obs.metricas}}Exemplo (Java/Spring): Micrometer + micrometer-registry-prometheus em /actuator/prometheus
{{LACUNA:obs.traces}}Exemplo (Java/Spring): Micrometer Tracing + bridge OpenTelemetry + exporter OTLP; instrumentação do pool JDBC
{{LACUNA:obs.porta}}Exemplo (Java/Spring): 9090 (MANAGEMENT_SERVER_PORT)

Prova

A change observabilidade (Fase 2) entrega, por aplicação, um teste de integração do contrato: uma requisição gera um trace com span de banco; a chamada de saída leva traceparent; o log é JSON e tem traceId; as métricas só respondem na porta interna; /up responde sem login; sem coletor, as requisições seguem 200.

Local

docker compose --profile observability up -d sobe um Tempo e um Grafana mínimos (sem login). As aplicações mandam OTLP para o Tempo; sem o perfil, o export falha em silêncio. O guia observabilidade-local.md mostra como ver um trace, as métricas e o log de uma requisição.

Em produção

Em produção, chegam sem trabalho, assim que a aplicação é implantada: os logs de todo container, no Loki; CPU, memória e rede por container; e o tráfego HTTP por serviço, das métricas do kamal-proxy. Dependem de um passo no toolbox, no portão 3: as métricas da aplicação (job_name no Prometheus, com alias de rede estável e porta interna), os traces (receiver OTLP no Alloy) e os dashboards da aplicação, em JSON versionado no toolbox. chega sozinhoassim que a aplicação é implantadalogs de todo containerno LokiCPU, memória e redepor containertráfego HTTPpor serviço, do kamal-proxypede um passo no toolboxno portão 3, antes do 1º deploymétricas da aplicaçãojob_name no Prometheustracesreceiver OTLP no AlloydashboardsJSON versionado no toolbox

O ARCHITECTURE.md de cada stack tem uma seção "Acompanhamento no Grafana" com essas duas listas, preenchidas para as aplicações dela.

No portão 3: o que levar ao toolbox

Três passos, do mantenedor do toolbox, com os valores do contrato da plataforma. O guia de implantação da stack traz estes blocos já preenchidos.

1. As métricas. Em apps/observability/prometheus/prometheus.yml, em scrape_configs, um job_name igual ao nome de cada aplicação, porque é por ele que o painel filtra:

YAML
  # <produto>
  - job_name: <app>
    static_configs:
      - targets: ["<app>:<porta de métricas>"]

O prometheus.yml é um arquivo montado pelo accessory prometheus, e o kamal deploy não o reenvia. De dentro de apps/observability/:

Terminal
kamal accessory reboot prometheus
sleep 30   # a primeira coleta
kamal accessory exec prometheus --reuse "wget -qO- --post-data query=up http://localhost:9090/api/v1/query" \
  | grep -o '"job":"<app>"},"value":\[[^]]*\]'

O valor "1" diz que o Prometheus alcança a aplicação. "0" é um alvo que não responde, e nenhuma linha quer dizer que o arquivo novo não subiu.

2. Os traces. As aplicações já mandam OTLP sobre HTTP para http://toolbox-observability-alloy:4318. No Alloy (apps/observability/alloy/config.alloy), o receptor e a entrega ao Tempo, quando o toolbox tiver um:

alloy
otelcol.receiver.otlp "default" {
  http {
    endpoint = "0.0.0.0:4318"
  }
  output {
    traces = [otelcol.processor.batch.default.input]
  }
}

otelcol.processor.batch "default" {
  output {
    traces = [otelcol.exporter.otlp.tempo.input]
  }
}

otelcol.exporter.otlp "tempo" {
  client {
    endpoint = "toolbox-observability-tempo:4317"
    tls {
      insecure = true
    }
  }
}

O config.alloy também é montado: depois dele, kamal accessory reboot alloy.

3. O painel. A stack versiona o JSON do painel do produto, que usa a fonte de dados de uid prometheus, a mesma do toolbox. O mantenedor o copia para apps/observability/grafana/dashboards/. Como ele entra na imagem do Grafana, basta o kamal deploy do toolbox. As taxas usam janelas de 5 minutos, então o painel precisa de uns minutos de dados.