Resumo teórico
Imagine que você construiu um robô de brinquedo e, um dia, ele para de andar. Se ele não tem janela nem luzinhas, você precisa adivinhar o que está errado lá dentro. Se ele tem luzinhas, um contador de passos e um diário do que fez, você consegue olhar e entender. Observabilidade é isso: construir software de modo que, quando algo estranho acontece, você possa olhar os dados que ele já emite e explicar o que houve, sem adivinhar e sem mudar o programa antes.
Isso importa porque hoje um clique em um site pode passar por vários programinhas, chamados serviçosprogramas pequenos que fazem cada um uma tarefa e conversam entre si pela rede, em vários computadores. Quando o clique fica lento, ninguém sabe qual serviço culpar. Lojas online, bancos, apps de vídeo e jogos usam observabilidade para achar problemas rápido e para prometer aos usuários que o serviço vai funcionar (e saber quando essa promessa está em perigo).
Neste resumo você vai aprender: a diferença entre monitoramento e observabilidade, os três tipos de dado (logs, métricas e traces), como escrever logs que um computador consegue pesquisar, como as métricas funcionam e por que labels demais dão problema, como uma requisição é seguida por vários serviços, o que é o OpenTelemetry, como prometer confiabilidade com SLIs, SLOs e orçamentos de erro, como alertar pessoas sem acordá-las à toa, os checklists RED, USE e sinais de ouro, como o profiling acha código lento e como Prometheus, Grafana, Loki e Tempo se encaixam.
O panorama: telemetria e vocabulário
Pense em uma médica. Ela não pode abrir você para ver por dentro, então usa um termômetro, um monitor de batimentos e o seu relato de como você se sente. Software é igual: não dá para olhar dentro de um programa em execução, então ele envia sinais. Todos esses sinais juntos se chamam telemetriadados que um sistema em execução envia sobre si mesmo, para que pessoas o entendam de longe.
| Palavra | Significado simples | Exemplo |
|---|---|---|
| Telemetria | Tudo que o sistema envia sobre si mesmo | Uma linha de log, um número, um trace |
| Instrumentação | O código (seu ou de uma biblioteca) que produz a telemetria | Uma linha que conta cada requisição |
| Backend | O lugar que guarda e mostra a telemetria | Prometheus, Loki, Tempo, Grafana |
| Serviço | Um programa pequeno que faz uma tarefa | orders, inventory, gateway |
| Incidente | Um momento em que uma falha prejudica os usuários | O checkout falha por 10 minutos |
seu código --> instrumentação --> telemetria
|
v
guarda (backend) --> telas
e alertas
|
v
uma pessoa entendeMonitoramento contra observabilidade
Imagine uma escola com um alarme em cada porta. O alarme toca se uma porta abrir à noite. Isso é monitoramento: você decide antes o que pode dar errado e coloca um vigia ali. Mas e se acontecer algo esquisito que ninguém imaginou, como uma janela abrir por causa do vento? Nenhum alarme foi feito para isso.
A palavra observabilidade vem da teoria de controle, um ramo da engenharia. Lá, um sistema é observável se você consegue descobrir o que acontece dentro dele olhando só o que ele emite. Para software, significa: você consegue entender e explicar qualquer estado em que o sistema entre, até um que nunca viu, usando a telemetria que ele já emite. Se você precisa publicar código novo só para investigar, faltava observabilidade.
Known-unknowns e unknown-unknowns
| Tipo | O que é | Exemplo | Quem resolve |
|---|---|---|---|
| Known-unknown | Uma pergunta que você sabia que faria | A CPU passou de 90%? | Monitoramento: um painel ou um alerta |
| Unknown-unknown | Uma falha que ninguém imaginou, então não tem painel nem alerta | Só um cliente em uma versão do app falha | Observabilidade: você investiga mesmo assim |
Cardinalidade e dimensionalidade
Cardinalidadequantos valores diferentes um campo pode ter é sobre um campo. O campo http_method tem poucos valores (GET, POST e mais alguns): baixa cardinalidade. O campo user_id em um serviço com milhões de usuários tem milhões de valores: alta cardinalidade. Os campos de alta cardinalidade são os mais úteis para depurar, porque apontam um único caso entre milhões.
Dimensionalidadequantos campos diferentes cada evento carrega é sobre quantos campos existem. Um evento largo (wide event) é um registro com centenas de campos (cliente, versão, região, rota, duração e assim por diante). Observabilidade precisa dos dois: alta cardinalidade e alta dimensionalidade.
Por que agregados escondem problemas
Um agregado responde 'como está o sistema em geral?'. Se um cliente, em uma versão do app, falha em toda requisição, a taxa global de erro ainda pode ser 0,1% e o painel parece bem. O problema se esconde dentro da média. Para achá-lo você precisa guardar os dados com seus campos e poder fatiar por qualquer combinação deles.
A pré-agregação tem o mesmo problema no tempo. Se você guarda só um contador de requisições por endpoint e status, decidiu antes do incidente quais perguntas são permitidas. Um campo que não virou label na hora de gravar não pode ser recuperado depois. Guardar o evento bruto, com todos os campos, e agregar na hora da consulta deixa você inventar a pergunta durante a investigação.
O laço central de análise
- Comece pela visão geral do que parece errado (por exemplo, requisições lentas).
- Confira o que você já sabe.
- Procure os campos cujos valores diferem entre o grupo ruim e o resto.
- Filtre por esses campos, e agora você olha um grupo menor.
- Repita até a causa ficar clara.
Esse laço é guiado por dados, não por palpites da experiência. Ele não exige que você conheça o sistema de antemão, por isso funciona até para falhas totalmente novas.
Por que um repositório de eventos aceita um id de cliente
Um banco de séries temporais guarda dados por sérieum fluxo de números ao longo do tempo, identificado pelo nome da métrica mais um conjunto de labels. Todo valor novo de label cria uma série nova, então o índice cresce com a cardinalidade. Um repositório de eventos (geralmente colunar, ou seja, guarda cada campo em sua própria coluna) grava uma linha por evento. A cardinalidade de um campo não muda o custo de gravar, e o agrupamento acontece na hora da consulta.
↑ Voltar ao sumárioOs três sinais: logs, métricas e traces
A telemetria vem em três tipos principais, chamados sinais. Pense em uma viagem de carro. O painel do carro mostra velocidade e combustível: números ao longo do tempo. O diário de viagem diz 'às 15h paramos para almoçar': coisas que aconteceram. O mapa com o seu trajeto mostra cada passo de uma viagem. Eles respondem perguntas diferentes.
| Sinal | O que é | Bom para | Ruim para |
|---|---|---|---|
| Métrica | Um número medido ao longo do tempo, com nome e labels | Quanto? Qual a tendência? Alertas | O detalhe de uma requisição |
| Log | Um registro de um evento, com hora e campos | O que aconteceu, com qual mensagem? | Tendências baratas de longo prazo |
| Trace | A história de UMA requisição entre serviços, feita de spans | Para onde foi o tempo? Qual serviço? | Tendências de meses |
Métricas
Uma métrica é um número ao longo do tempo: quantas requisições, quanta memória, quanto tempo levou. Cada sérieum fluxo de números ao longo do tempo, identificado pelo nome da métrica mais um conjunto de labels é identificada por um nome e um conjunto de labels. Como as métricas guardam agregados e não cada evento, elas são pequenas e rápidas, o que as torna ideais para tendências e alertas.
O custo de uma métrica depende do número de séries e da frequência de leitura (a frequência de scrape), não do tráfego. Se o tráfego cresce dez vezes, um contador de requisições custa quase o mesmo. Logs e traces produzem dados para cada requisição, então o custo deles acompanha o volume de tráfego.
Traces
Um trace conta a história de uma requisição. Cada operação vira um span, com início, duração e um ponteiro para o span pai. Todos os spans da mesma requisição compartilham um trace id. Um trace mostra a ordem das chamadas e para onde o tempo foi, o que nenhum agregado mostra.
- gateway (trabalho próprio)
- 40 ms
- orders (trabalho próprio)
- 40 ms
- db.insert
- 60 ms
- inventory GET /stock (trabalho próprio)
- 40 ms
- inventory stock.read
- 720 ms
O gráfico usa os números de um trace que levou 900 ms. Um span pai inclui o tempo dos filhos, então a raiz é sempre a mais longa. O tempo próprio (self time) é a duração de um span menos a duração dos filhos diretos. O span com maior tempo próprio, em geral uma folha da árvore, é onde o tempo foi gasto de verdade. Aqui é stock.read, com 720 ms.
Logs
Um log é o registro de algo que aconteceu. Logs dizem 'por quê' e 'com qual mensagem'. São o sinal mais detalhado e também o mais caro de guardar, já que há uma linha para cada evento.
Fazendo os sinais trabalharem juntos
Os sinais ficam muito mais fortes quando compartilham identificadores. Se o logger escreve o trace id e o span id do contexto ativo em cada linha, o log deixa de ser texto solto: ele pertence a uma requisição, e as ferramentas conseguem abrir os logs a partir de um span e o trace a partir de uma linha de log.
Uma métrica diz 'algo está lento', mas não qual requisição. Um exemplarum trace id guardado junto de uma amostra de uma métrica, para você pular do número para uma requisição de exemplo real resolve: o sistema de métricas guarda o trace id de uma requisição ao lado de uma amostra. No gráfico, você clica no ponto e abre o trace.
O que um histograma pode e não pode dizer
Um histograma cumulativo conta quantas requisições levaram até 0,5 s, até 2,5 s e assim por diante. Dele você sabe quantas e que fatia, o que basta para painéis e alertas. Você não sabe quais requisições, onde dentro da requisição nem com qual mensagem. Para isso você precisa de traces e logs.
Amostragem e estimativa de totais
Guardar todos os traces é caro, então muitos sistemas guardam só uma fração: isso é amostragem (sampling). Suponha que guardamos 1 trace em 100 e uma consulta acha 480 traces armazenados de /pay. Cada trace guardado representa cerca de 100 requisições reais, então a estimativa é 480 × 100 = 48.000. Para isso, a taxa de amostragem precisa ser registrada em cada evento guardado. Métricas não têm esse problema, porque contam toda requisição antes de qualquer amostragem.
Logs estruturados
Um log de texto livre é como uma carta: 'Requisição de checkout de alice para 2 canetas'. Uma pessoa lê fácil, mas um computador não consegue perguntar facilmente 'quantas requisições levaram mais de 500 ms?'. Você teria que escrever uma expressão regularum padrão de busca escrito em uma mini-linguagem especial que quebra assim que alguém muda a redação da frase.
Um log estruturado é como um formulário com caixinhas. Cada registro é uma lista de pares nome e valor, normalmente em JSON ou logfmt. Uma ferramenta consegue filtrar por status, somar duration_ms ou agrupar por route sem se importar com nenhuma frase.
{"ts":"2026-10-07T17:03:22.418Z","level":"info",
"route":"/orders","status":200,
"duration_ms":1240,"correlation_id":"7f3a"}O correlation id
Quando muitas requisições acontecem ao mesmo tempo, as linhas delas se misturam no log. Um correlation id (ou request id) é um valor criado quando a requisição entra no sistema e repetido em toda linha de log sobre essa requisição, em todo serviço por onde ela passa. Aí uma busca por esse valor devolve a história inteira.
O id só funciona se atravessar cada fronteira entre processos. Por HTTP ele vai em um header, por exemplo X-Correlation-Id. Por uma fila ele vai em um header ou propriedade da mensagem: o produtor preenche e o consumidor lê antes de processar. Nada faz isso por mágica. Seu código ou uma biblioteca de instrumentação precisa copiar o valor.
Os dois lados precisam funcionar: quem chama injeta o id na chamada, e quem recebe extrai e passa a usá-lo. Um serviço cria um id novo só quando é o ponto de entrada, ou seja, quando a requisição chegou sem um. Se todo serviço cria o seu, os logs ficam corretos dentro de cada serviço mas cortados entre eles.
Bons hábitos com os campos
- Hora em UTC e ISO 8601 (RFC 3339), como
2026-10-07T17:03:22.418Z, com milissegundos ou melhor. Todas as máquinas significam o mesmo instante, e a ordem alfabética coincide com a ordem do tempo. - Nunca escreva segredos: senhas, tokens, números de cartão e dados pessoais ficam de fora, mesmo como exemplo. Logs costumam ter controle de acesso mais fraco que o banco de dados.
- Escreva o que ajuda uma investigação: identificadores, rota, status, duração.
Pesquisando com LogQL
O Lokium sistema que guarda logs e indexa apenas os labels deles usa uma linguagem de consulta chamada LogQL. Uma consulta é um seletor de streams entre chaves seguido de um pipeline, e cada etapa recebe só o que a anterior deixou passar.
{service="orders"} |= "timeout" | json | status >= 500{service="orders"}escolhe os streams pelos labels indexados.|= "timeout"mantém só as linhas que contêm o texto.| jsontransforma os campos do JSON em labels que podem ser comparados. Campos aninhados são achatados com_, entãohttp.statusvirahttp_status.| status >= 500é uma comparação de números, não uma expressão regular frágil. O mesmo vale para durações, por exemplo| duration_ms > 500.
Tipos de métrica e cardinalidade
O Prometheus tem alguns tipos de métrica, como instrumentos de medir diferentes em uma cozinha: um counter é um contador de cliques na porta, um gauge é um termômetro, um histograma é um conjunto de potes separados por tamanho.
| Tipo | O que é | Exemplo | Como ler |
|---|---|---|---|
| Counter | Um total que só sobe (volta a zero quando o processo reinicia) | http_requests_total | Use rate() ou increase(), não o valor bruto |
| Gauge | Um valor que sobe e desce | Tamanho de fila, memória em uso, conexões abertas | Leia o valor atual, a média ou o máximo |
| Histogram | Contagens de observações dentro de faixas (buckets) | http_request_duration_seconds | histogram_quantile() estima percentis |
| Summary | Percentis calculados dentro do app | Quantil 0,99 pronto de uma instância | Não pode ser combinado entre instâncias |
Counters e taxa
O valor bruto de um counter diz pouco. O que importa é a velocidade com que ele cresce. A taxa é a mudança dividida pelo tempo. Se um counter era 1200 em t = 0 s e 1500 em t = 60 s, o aumento é 300, e 300 / 60 = 5 requisições por segundo. A função rate() faz isso sobre uma janela, com ajustes para resets e para as bordas. increase() é o mesmo sem dividir pelo tempo.
Quando o valor de um counter diminui, o Prometheus entende que o processo reiniciou e conta a amostra seguinte inteira como aumento. Para as amostras 980, 1000, 15, 40: os passos são 20, depois 15 (após o reset), depois 25, então o total é 60. Por isso você aplica rate() primeiro e soma depois. Se somar antes os counters de várias instâncias, os resets ficam escondidos.
Histogramas em detalhe
Um histograma clássico tem buckets cujos limites você escolhe no código. Cada bucket é uma série _bucket{le="limite"} e é cumulativo: conta toda observação menor ou igual ao limite (le quer dizer 'less than or equal', menor ou igual). O último bucket é le="+Inf". Há ainda _sum (o total dos valores observados) e _count (o número de observações), e todos são counters.
..._bucket{le="0.1"} 40
..._bucket{le="0.25"} 70
..._bucket{le="0.5"} 90
..._bucket{le="1"} 100
..._bucket{le="+Inf"} 100Para achar o percentil 80 (quantil 0,8) dessa tabela: o total é 100, então procuramos a posição 80. Até 0,25 há 70 e até 0,5 há 90, então a posição 80 está dentro do bucket de 0,25 a 0,5, que tem 90 - 70 = 20 observações. Supomos que as observações se espalham de modo uniforme no bucket: (80 - 70) / 20 = 0,5 do caminho. A estimativa é 0,25 + 0,5 × (0,5 - 0,25) = 0,375 s. É só uma estimativa: o erro depende da largura do bucket.
Histogram contra summary
Um summary calcula quantis na aplicação e os expõe prontos, por instância. Um histogram expõe contagens por bucket, e o quantil é estimado depois, no servidor. Percentis não são aditivos: a média, soma ou máximo do p99 de cada instância não é o p99 do serviço todo. As contagens dos buckets são aditivas. Então, para obter o percentil de um serviço inteiro, use um histogram, com esta receita:
histogram_quantile(0.99,
sum by (le) (
rate(http_request_duration_seconds_bucket[5m])
)
)Cardinalidade: contando séries
Cada combinação diferente de valores de labels é uma série temporal própria, com seu custo de memória e de índice. O máximo é o produto das quantidades de valores. Um counter com method (4 valores), status (5 valores) e instance (3 instâncias) pode gerar 4 × 5 × 3 = 60 séries. Some um label user_id com 10.000 valores e vira 600.000.
- method × status × instance
- 60 séries
- o mesmo + user_id (10.000 valores)
- 600,000 séries
Um histogram clássico multiplica isso ainda mais, porque cada combinação de labels carrega uma série por bucket (os limites finitos mais +Inf) mais _sum e _count. Com 10 limites finitos, route (20 valores) e method (3 valores) você tem (10 + 1 + 2) = 13 séries por combinação, 20 × 3 = 60 combinações, então 13 × 60 = 780 séries por instância.
Instrumentos de métrica no OpenTelemetry
| Instrumento | Como os valores são registrados | Exemplo |
|---|---|---|
| Counter | Só soma | Requisições atendidas |
| UpDownCounter | Soma e subtrai; saldos de instâncias podem ser somados | Requisições em andamento |
| Histogram | Registra uma distribuição de valores | Duração da requisição |
| Gauge | O valor absoluto atual, não aditivo | Temperatura |
Tracing distribuído: spans e propagação de contexto
Imagine passar um bilhetinho na sala, da primeira carteira até a última. Cada criança que encosta nele escreve a hora e o nome. No fim você vê o caminho todo e quem segurou o bilhete por muito tempo. Um trace distribuído faz isso para uma requisição que atravessa vários serviços.
Trace, span e a árvore
Um trace é o registro de uma requisição enquanto ela atravessa o sistema. Ele é feito de spans. Um span é uma unidade de trabalho com início e duração. Cada span tem três identificadores: o trace id (igual para a requisição toda), o seu próprio span id e o span id do seu pai. O backend agrupa os spans por trace id e monta uma árvore seguindo os ids dos pais. Sem um trace id compartilhado, spans de serviços diferentes seriam eventos soltos.
O único span sem pai é o span raiz (root span). Ele representa a requisição inteira, vista de onde ela entrou no sistema. Todos os outros são seus filhos, netos e assim por diante.
span início fim
gateway GET /checkout 0 900 ms
orders POST /orders 20 880
orders db.insert 30 90
inventory GET /stock 100 860
inventory stock.read 120 840Lendo: gateway é a raiz. A duração dele é 900 ms, mas o tempo próprio é só 900 - 860 = 40 ms. O tempo próprio de POST /orders é 860 - (60 + 760) = 40 ms, o de db.insert é 60 ms, o de GET /stock é 760 - 720 = 40 ms e o de stock.read é 720 ms. A parte lenta é stock.read, mesmo que seis linhas pareçam lentas.
Propagação de contexto
Como três programas diferentes concordam sobre o mesmo trace id? Quem chama injeta o trace id e o próprio span id nos headers da chamada. Quem recebe extrai esses valores e os usa como pai dos seus spans. Dentro de um processo, o contexto passa de função em função. Entre processos, ele viaja nos headers do protocolo. Isso se chama propagação de contexto.
O padrão é o W3C Trace Contextuma regra pública de como escrever os headers de trace, para que qualquer ferramenta consiga lê-los. O header se chama traceparent:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
| | | |
| trace id (32 dígitos hex) | flags (2)
versão (2) parent id (16 dígitos hex)O parent id é o span id de quem chamou, que vira o pai do span criado por quem recebe. Um id feito só de zeros é inválido. O último campo guarda flags, e uma delas é a flag sampled, que diz se o trace está sendo gravado.
Span kind
| Span kind | Papel | Exemplo |
|---|---|---|
CLIENT | Faz uma chamada síncrona | orders chamando inventory |
SERVER | Recebe e trata uma chamada síncrona | inventory tratando a requisição HTTP |
PRODUCER | Envia um job ou mensagem assíncrona | Publicar em uma fila |
CONSUMER | Recebe um job ou mensagem assíncrona | Um worker lendo a fila |
INTERNAL | Trabalho que fica dentro do processo | Um cálculo |
Atributos, baggage e links
- Atributos descrevem um span e são exportados com ele. Não vão para lugar nenhum além disso.
- Baggage é um conjunto de pares chave e valor que acompanha o contexto de serviço em serviço, para que quem está adiante possa lê-lo. Como ele vai em headers para qualquer destino da chamada, nunca coloque dados sensíveis no baggage.
- Span links apontam para outros contextos de span, no mesmo trace ou em outros. Servem para trabalho assíncrono com várias causas. Um worker que processa 50 mensagens em um span de lote liga esse span às 50 requisições que as publicaram, porque um pai normal só tem um.
Relógios mentem um pouco
A árvore vem dos ids, que são exatos. Mas a posição de cada span na linha do tempo vem do relógio da máquina que o criou. Relógios de máquinas diferentes se desalinham, então um span filho pode parecer começar antes do pai. A duração de cada span continua confiável, porque é medida em um único relógio.
Amostragem
Gravar todo trace custa muito, então guardamos alguns. Se guardamos 1 em 20 e escrevemos a taxa 20 em cada trace guardado, então 350 traces de erro armazenados representam 350 × 20 = 7.000 requisições reais com erro. Somar as taxas dá a estimativa do total.
| Tipo | Quando decide | Bom | Custo |
|---|---|---|---|
| Head sampling | No início da requisição, sem saber como ela termina | Barato e simples | Pode descartar os traces interessantes |
| Tail sampling | Depois que o trace está completo, olhando tudo | Pode guardar 'todo trace com erro' | Segura spans na memória; todos os spans de um trace precisam chegar ao mesmo lugar |
Com tail sampling e várias instâncias de collector, uma camada antes delas precisa rotear pelo trace id, não em round-robin. Se os spans de um trace vão para instâncias diferentes, cada instância vê só parte do trace e decide errado.
Um sampler parent-based usa a regra própria só para spans raiz. Quando existe um pai, ele repete a decisão do pai, lida da flag sampled do traceparent. Com o gateway em razão 0,1 e o orders em 0,5, o orders ainda grava só os traces que o gateway escolheu, ou seja, 100 de cada 1.000 requisições, completos nos dois serviços. A decisão tomada na entrada vale para o trace inteiro.
OpenTelemetry
Imagine que cada país tivesse um plugue de energia diferente. Você precisaria de um carregador novo a cada viagem. Antes do OpenTelemetry, cada fornecedor de monitoramento tinha seu jeito de coletar dados. O OpenTelemetry (muitas vezes escrito OTel) é o plugue universal: um conjunto aberto e neutro de especificações e ferramentas para produzir e transportar telemetria. Ele não guarda nem desenha nada. Guardar, consultar e mostrar é trabalho de um backend que você escolhe.
| Peça | O que faz |
|---|---|
| API | Um conjunto pequeno e estável de chamadas que o código usa para dizer o que poderia ser medido |
| SDK | A implementação: amostragem, processadores, exportadores; a aplicação o configura |
| OTLP | O protocolo que transporta traces, métricas e logs |
| Collector | Um programa que recebe, processa e exporta telemetria |
| Convenções semânticas | Nomes combinados para atributos |
API contra SDK
As bibliotecas dependem só da API, que é leve e estável. A aplicação decide se e como medir ao configurar o SDK. Se uma biblioteca de banco de dados cria spans pela API e a aplicação nunca instala o SDK, as chamadas não fazem nada. Esse é o ponto: uma biblioteca pode ser instrumentada sem impor custo a ninguém.
Resource e convenções semânticas
Um resource é o conjunto de atributos que descreve quem produz a telemetria, e vale para todos os spans, métricas e logs daquele programa. O mais importante é service.name. Sem ele o SDK usa unknown_service, e os dados de serviços diferentes se misturam no backend. Você pode defini-lo com a variável OTEL_SERVICE_NAME.
Se cada time escolhe seus próprios nomes (status, statusCode, http_status), uma consulta que cruza serviços precisa conhecer todos. As convenções semânticas fixam um vocabulário comum, como http.request.method, http.response.status_code e service.name. A mesma consulta e o mesmo painel passam a servir para qualquer serviço.
OTLP e portas
O OTLP tem duas variantes de transporte. O gRPC usa a porta 4317. O HTTP usa a porta 4318, e cada sinal tem seu próprio caminho, como /v1/traces. Apontar um exportador HTTP para a porta do gRPC, ou o contrário, é um motivo muito comum de a telemetria nunca chegar.
O Collector
O Collector fica entre as aplicações e os backends. As aplicações falam só OTLP com um endereço. Tudo que é específico de um fornecedor mora na configuração do Collector. Para trocar ou adicionar um backend você edita o Collector e não precisa publicar os serviços de novo.
receivers:
otlp:
protocols:
http:
processors:
batch:
exporters:
otlp/tempo:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/tempo]Instrumentação automática e manual
A instrumentação automática (zero-code) intercepta bibliotecas conhecidas, como servidores HTTP, clientes de banco de dados e filas, e entrega o esqueleto do trace sem mudar código. Ela não conhece o seu negócio: o total de um pedido, o plano de um cliente, uma etapa de cálculo. Esses dados entram pela instrumentação manual com a API. As duas convivem no mesmo trace.
Programas curtos: descarregue antes de sair
O batch span processor guarda os spans terminados em uma fila e os envia em grupos de tempos em tempos, para a aplicação não ficar lenta a cada span. O preço: spans recentes ainda estão na memória quando o processo termina. Em um script de vida curta, desligue o provider (shutdown) antes de sair (isso esvazia a fila), ou use o simple processor, que exporta cada span assim que ele termina.
Logs no OpenTelemetry
Para logs, o OpenTelemetry não pede que você reescreva suas chamadas de log. A Logs API existe para que as bibliotecas de log já existentes se conectem ao SDK por uma ponte (bridge). Todo registro de log escrito enquanto um span está ativo recebe o trace id e o span id daquele contexto. Com esses dois campos o backend pula de uma linha de log para o seu trace e volta.
↑ Voltar ao sumárioSLI, SLO e orçamentos de erro
Digamos que uma barraquinha de limonada promete 'pelo menos 99 de cada 100 copos serão gelados'. Para cumprir a promessa você precisa contar os copos, precisa de uma meta e precisa saber quantos copos mornos ainda pode servir. A confiabilidade funciona do mesmo jeito, com três palavras que parecem iguais e são diferentes.
| Palavra | Significa | O que é | Exemplo |
|---|---|---|---|
| SLI | Service level indicator | A medição: eventos bons divididos por eventos válidos | Requisições sem erro / todas as requisições |
| SLO | Service level objective | A meta interna para o SLI em uma janela de tempo | 99,9% em 30 dias |
| SLA | Service level agreement | O contrato com o cliente, com consequências | Abaixo de 99,5%, devolve 10% da fatura |
O SLO costuma ser mais rígido que o SLA. Assim o time percebe o problema e age antes de o contrato ser quebrado.
Orçamento de erro
O orçamento de erro (error budget) é o que o SLO permite falhar: 1 menos o SLO. Com 99,9%, o orçamento é 0,1%. Pense nele como um cofrinho de falhas que você pode gastar com lançamentos, experimentos e surpresas.
- SLO por requisições: com 1.000.000 de requisições válidas e meta de 99,9%, 0,001 × 1.000.000 = 1.000 requisições podem falhar.
- SLO por tempo: 99,9% de minutos bons em 30 dias (43.200 minutos) permite 0,001 × 43.200 = 43,2 minutos ruins.
- SLO 99%
- 432 minutos
- SLO 99,9%
- 43.2 minutos
- SLO 99,99%
- 4.32 minutos
- SLO 99,999%
- 0.432 minutos
Cada nove a mais divide o orçamento por dez. Um time não deve escolher 100%. O orçamento seria zero, então nenhum deploy, experimento ou manutenção poderia dar errado, e o time pararia de mudar o sistema. Além disso, o usuário não distingue 100% de 99,99%, porque a rede e o próprio celular dele falham com mais frequência que isso.
Quando o orçamento acaba antes do fim da janela, o time põe esforço em estabilidade em vez de funcionalidades novas. Enquanto sobra orçamento, o time pode publicar e experimentar. O orçamento transforma a briga entre velocidade e confiabilidade em uma decisão tomada com um número.
Orçamento consumido
Um exemplo: SLO de 99,5%, com 2.000.000 de requisições válidas e 6.000 falhas na janela. As falhas permitidas são 0,005 × 2.000.000 = 10.000. Então 6.000 / 10.000 = 60% do orçamento já foi. A mesma resposta sai da taxa de erro (0,3%) dividida pela fração do orçamento (0,5%).
Burn rate
O burn rate (taxa de queima) diz quantas vezes mais rápido que o permitido o orçamento está sendo gasto: taxa de erro observada dividida por (1 - SLO). Com burn rate 1, o orçamento dura exatamente a janela do SLO. Para um SLO de 99,9% e 0,5% de erros na última hora: 0,005 / 0,001 = 5. Nesse ritmo o orçamento de 30 dias acaba em 30 / 5 = 6 dias.
Com burn rate B, um orçamento inteiro dura janela / B. Se 40% já foi usado e o burn rate fica fixo em 6, os 60% restantes duram 0,6 × 720 horas / 6 = 72 horas. Esse cálculo diz se um problema pode esperar o horário comercial ou precisa acordar alguém.
Escolhendo a unidade do SLI
Imagine um dia com 1.440 minutos e 1.000.000 de requisições. Durante a noite o serviço ficou fora por 36 minutos, e só chegaram 2.000 requisições (todas falharam). Por tempo: (1.440 - 36) / 1.440 = 97,5% bom. Por requisições: (1.000.000 - 2.000) / 1.000.000 = 99,8% bom. O mesmo incidente dá dois números. Medido por tempo, todo minuto pesa igual. Medido por requisições, cada minuto pesa o tráfego que teve, o que acompanha melhor o impacto nos usuários.
SLI de latência e percentis
Um SLI de latência também é uma razão: requisições abaixo do limite divididas pelo total. Com um histograma cumulativo, o numerador é o bucket do limite e o denominador é o bucket +Inf. Se 9.700 de 10.000 requisições levaram até 0,3 s, o SLI é 97%. Contra uma meta de '95% em até 300 ms', passa.
Um percentil diz 'abaixo de qual valor ficam p% das medições'. Com a convenção do nearest-rank (posto mais próximo), o percentil p é o valor na posição ⌈p × n⌉ contando a partir de 1. Para 10 valores ordenados (20, 22, 25, 27, 30, 31, 35, 40, 48, 900 ms), o p90 está na posição 9: 48 ms. A média é 117,8 ms, um número que nenhuma requisição teve. Por isso SLIs de latência usam percentis ou a fatia abaixo de um limite, não a média.
↑ Voltar ao sumárioAlertas
Um detector de fumaça que toca toda vez que você faz torrada logo é ignorado. Um alerta é uma mensagem que pede a uma pessoa para agir. Se a maioria dos alertas não pede ação, as pessoas param de lê-los, e aí o único alerta de verdade se perde no ruído. Isso é a fadiga de alertas. Por isso um alerta que acorda uma pessoa deve ser raro, urgente e acionável.
Sintomas contra causas
| Tipo | Dispara quando | Exemplo |
|---|---|---|
| Baseado em sintoma | O usuário está sofrendo: erros, lentidão, dados errados | Taxa de erro do checkout acima do objetivo |
| Baseado em causa | Um estado interno que pode levar a problemas | CPU acima de 90% |
Alertar sobre sintomas, com SLOs, gera menos alertas e cada um corresponde a um problema real. As causas ficam para a investigação.
Regras de alerta do Prometheus e a cláusula for
- alert: HighErrorRatio
expr: job:http_errors:ratio_rate5m > 0.05
for: 5m
labels:
severity: pageUm alerta passa por três estados. Inactive: a expressão não retorna nada. Pending: ela retorna algo, mas por menos tempo que for. Firing: ela retornou algo por pelo menos for. Se a expressão ficou verdadeira há 2 minutos e for é 5 minutos, o alerta está pending. A cláusula for evita acordar alguém por um pico que passa sozinho, ao preço de atrasar todo alerta por esse tempo.
O for exige que a expressão seja verdadeira em toda avaliação consecutiva. Uma única avaliação falsa zera a espera. Veja um serviço em que a cada 5 minutos há uma rajada de 60 segundos com metade das requisições falhando. No total 10% das requisições falham, mas com janela de 1 minuto e for: 10m o alerta nunca dispara, porque fica falso entre as rajadas. São 12 minutos de rajadas por hora queimando o orçamento sem alerta.
Alertas de burn rate
Um alerta de burn rate pergunta: 'o orçamento está sendo gasto rápido demais?'. Escolha uma fatia do orçamento e um tempo, e calcule o burn rate correspondente. Para abrir um ticket quando 5% do orçamento de um SLO de 99,9% (30 dias = 720 horas) for gasto em 6 horas: o serviço precisa estar 0,05 × 720 / 6 = 6 vezes mais rápido que o permitido. Em taxa de erro, isso é 6 × 0,1% = 0,6%. Outro caso conhecido: gastar 2% do orçamento em 1 hora significa burn rate 14,4.
- alert: ErrorBudgetBurn
expr: |
ratio_rate1h > (14.4 * 0.001)
and
ratio_rate5m > (14.4 * 0.001)
labels:
severity: pagePor que duas janelas? A janela longa (1 hora) mostra que uma parte relevante do orçamento foi gasta e protege contra picos breves. Sozinha, ela continuaria acima do limite por até uma hora depois que o problema acabou. A janela curta (5 minutos) confirma que a queima está acontecendo agora, então o alerta se apaga em minutos depois da recuperação.
Um alerta preditivo mede o ritmo de queima em uma janela de base e pergunta se, nesse ritmo, o orçamento acaba dentro de uma janela de antecipação (lookahead). Exemplo: sobram 2.000 falhas e houve 150 na última hora. 2.000 / 150 = 13,3 horas, ou seja, 13 h 20 min. Uma antecipação de 4 horas não dispararia. Uma de 24 horas dispararia.
Alertmanager
O Prometheus decide quais alertas estão disparando. O Alertmanager decide o que notificar. Três recursos reduzem o ruído:
| Recurso | O que faz | Exemplo |
|---|---|---|
| Grouping (agrupamento) | Junta alertas parecidos em uma notificação | 300 alertas InstanceDown viram uma mensagem listando 300 instâncias |
| Inhibition (inibição) | Silencia alertas enquanto um mais amplo já está disparando | Esconder 'serviço lento' enquanto 'datacenter fora' dispara |
| Silences (silenciamentos) | Muta alertas por um tempo escolhido por uma pessoa | Durante uma manutenção planejada |
RED, USE e sinais de ouro
Quando você abre um painel, o que deveria estar nele? Checklists ajudam. Existem três famosos, e cada um olha o sistema de um lugar diferente. Pense em um restaurante: RED é como os clientes se sentem (quantos pedidos, quantos errados, quanto esperam). USE é como estão os equipamentos da cozinha (fogão ocupado, pedidos esperando, pratos queimados).
| Método | Autor | Letras | Olha para |
|---|---|---|---|
| RED | Tom Wilkie | Rate, Errors, Duration | Serviços movidos a requisições, do lado de quem chama |
| USE | Brendan Gregg | Utilization, Saturation, Errors | Recursos (CPU, memória, disco, rede), por dentro |
| Quatro sinais de ouro | Livro SRE do Google | Latency, Traffic, Errors, Saturation | Sistemas voltados ao usuário |
O RED diz o que medir em todo serviço movido a requisições: a taxa de requisições, o número de erros e a duração (melhor como histograma). Esses três são a base de painéis padrão e de SLOs.
O USE é um checklist para achar gargalos: para cada recurso, olhe a utilização (quanto dele está ocupado), a saturação (quanto trabalho está na fila porque não coube) e os erros.
Os sinais de ouro são quase o RED com outros nomes: latency é a duração, traffic é a taxa, errors são os erros. O sinal a mais é a saturação, o quanto o serviço está perto do seu limite. O RED deixa a saturação para o USE, aplicado aos recursos.
Utilização não é saturação
Imagine um host com 4 núcleos de CPU, todos ocupados 100% do tempo, e uma média de 12 threads prontas esperando por um núcleo. A utilização é 100%. O 12 é a saturação: trabalho que não coube e espera em uma fila. Um recurso a 100% sem fila está no limite. Um recurso a 100% com uma fila longa já está atrasando todo mundo que depende dele.
Qual método para quê
- Uma API HTTP de checkout é um serviço movido a requisições: use RED ou os sinais de ouro.
- Os discos do host do banco de dados por baixo são recursos: use USE.
- Em um sistema real, use os dois, cada um na sua camada.
Latência e erros se contaminam
Suponha que uma dependência caia. 40% das requisições passam a devolver HTTP 500 em 5 ms, e o resto ainda leva 300 ms. A latência média vira 0,4 × 5 + 0,6 × 300 = 182 ms. O gráfico da média mostra uma 'melhora' de 300 ms para 182 ms, enquanto 40% dos usuários estão falhando. O conselho é medir a latência separada por resultado (sucesso contra erro) e ler os sinais juntos: a taxa de erro de 40% explica a falsa melhora.
- Antes da falha
- 300 ms
- Durante a falha (tudo misturado)
- 182 ms
- Só requisições com sucesso
- 300 ms
- Só requisições com falha
- 5 ms
Profiling
Um trace diz qual serviço estava lento. Mas dentro desse serviço, qual linha de código é a lenta? Pense em uma fotógrafa que tira 100 fotos por segundo da sua sala de aula cheia e anota quem está fazendo o quê em cada foto. Depois de um tempo ela sabe onde a turma gasta o tempo. Isso é um profiler.
Um profiler de CPU por amostragem (sampling) interrompe o programa muitas vezes por segundo e anota a pilha de chamadas naquele momento (a lista de funções em execução, uma chamando a próxima). Somar as amostras mostra onde o tempo de CPU se concentra. É barato o bastante para rodar em produção. Um trace diz onde uma requisição foi lenta entre serviços. Um profile diz qual código gastou a CPU dentro de um processo.
Flame graphs
Um flame graph desenha as amostras. Cada retângulo é uma função em uma pilha. O eixo vertical é a profundidade da pilha. A largura é a fatia das amostras que contêm aquela função. O eixo horizontal não é tempo: pilhas iguais são fundidas e ordenadas para facilitar a leitura. Então procure o que é largo, não o que está à esquerda.
root 1000
+- handleSearch 520
| +- parseQuery 60
| +- compileRegex 230
| +- rankResults 230
+- handleExport 400
| +- compileRegex 210
| +- writeCsv 190
+- gc 80O tempo próprio (self time) de um frame é a largura dele menos a largura dos filhos: o topo exposto da chama. Um flame graph separa a mesma função quando ela é chamada por caminhos diferentes. Aqui compileRegex aparece duas vezes, com 230 + 210 = 440 amostras, ou seja, 44% da CPU. Somar por função é o que o pprof top faz.
- compileRegex
- 440 amostras
- rankResults
- 230 amostras
- writeCsv
- 190 amostras
- gc
- 80 amostras
- parseQuery
- 60 amostras
Lendo o pprof: flat e cum
flat flat% cum cum%
0.05s 0.50% 9.80s 98.00% net/http.(*conn).serve
0.30s 3.00% 9.40s 94.00% main.buildRows
6.50s 65.00% 6.50s 65.00% main.normalizeKey
1.20s 12.00% 1.20s 12.00% runtime.mallocgcflaté o tempo gasto no código da própria função.cum(cumulativo) inclui também tudo que a função chama.- Funções perto da raiz, como o laço do servidor HTTP, têm
cumenorme eflatperto de zero. O ponto quente é a função comflatalto. Aqui émain.normalizeKey, com 6,50 s.
Noções de Prometheus, Grafana, Loki e Tempo
Uma biblioteca de escola tem prateleiras diferentes para livros, revistas e mapas, mas um só balcão onde você pede qualquer coisa. Em uma stack de observabilidade cada sinal tem seu próprio repositório, e uma ferramenta junta tudo em uma tela.
| Ferramenta | Sinal | Linguagem de consulta | Papel |
|---|---|---|---|
| Prometheus | Métricas | PromQL | Coleta e guarda números ao longo do tempo |
| Loki | Logs | LogQL | Guarda linhas de log, indexa labels |
| Tempo | Traces | TraceQL | Guarda traces |
| Grafana | Nenhum | Consulta as outras | Interface: dashboards; não guarda telemetria |
serviços --métricas--> Prometheus --+
--logs-----> Loki ----+--> Grafana
--traces---> Tempo ----+ (uma tela)Como o Grafana consulta os três, você pode pular de uma métrica para um trace e para os logs dele na mesma tela.
Prometheus: pull e scrape
Por padrão o Prometheus funciona no modelo pull (puxar). A cada intervalo configurado ele faz um scrape: uma requisição HTTP ao endpoint de métricas de cada alvo. Ele guarda as amostras com a hora da coleta. O serviço só precisa expor seus valores atuais. Um efeito útil: o próprio scrape mostra se um alvo está vivo, por meio da métrica up.
Loki: indexa labels, varre o conteúdo
O Loki indexa só os labels dos streams, não o conteúdo das linhas. O índice responde uma pergunta: quais streams existem em um intervalo de tempo. Todo o resto, incluindo |= e os parsers, é feito lendo e varrendo o conteúdo dos streams selecionados. Veja a consulta:
{env="prod"} |= "4bf92f3577b34da6a3ce929d0e0e4736"Ir de 15 minutos para 7 dias a deixa muito mais lenta, porque ela precisa varrer 7 dias de linhas de todos os streams prod. O custo depende de quantos dados o seletor e o intervalo deixam passar. Seletores mais específicos (acrescente service) e intervalos curtos deixam as consultas rápidas.
Tempo e TraceQL
No TraceQL, um par de chaves é um seletor de spans, e as condições dentro dele valem para um mesmo span. Os atributos têm escopo: resource. para quem emite, span. para a operação. Intrínsecos como duration, name e status vêm do próprio span.
{ resource.service.name = "inventory" && duration > 500ms }Juntando tudo
Agora vamos acompanhar um problema real desde o começo. Usuários dizem que o checkout está lento, mas só às vezes. Veja como as peças trabalham em equipe.
- Um alerta de SLO dispara. Um alerta de burn rate com duas janelas diz que o orçamento de latência está sendo gasto rápido demais (alertas, SLO).
- Um dashboard mostra RED e USE. Taxa e erros parecem normais, mas o histograma de duração tem uma cauda gorda (métricas, RED).
- Um exemplar abre um trace. O trace mostra que
inventory stock.readtem o maior tempo próprio (traces, tempo próprio). - Os logs desse span explicam. Pesquisar o trace id ou o correlation id no Loki devolve as linhas estruturadas dessa requisição exata (logs, correlação).
- Um profile aponta o código. Um flame graph mostra uma função com um frame largo (profiling).
| Pergunta | Melhor sinal ou ferramenta |
|---|---|
| Algo está errado, e quanto? | Métricas e SLOs |
| Alguém deve ser acordado? | Alerta de burn rate sobre um sintoma |
| Qual serviço e qual etapa foi lenta? | Trace, pelo tempo próprio |
| Por que aquela requisição falhou? | Log estruturado com o correlation id ou trace id |
| Qual função queima a CPU? | Profile de CPU e flame graph |
| Como tiro tudo isso do código? | OpenTelemetry (API, SDK, OTLP, Collector) |
Erros comuns
- Colocar um id com valores ilimitados (id de usuário, de requisição, trace id) em um label de métrica ou de Loki.
- Tirar a média ou somar percentis entre instâncias em vez de somar os buckets do histograma.
- Chamar de culpado o span mais longo em vez do span com mais tempo próprio.
- Esquecer de repassar os headers de trace, o que corta um trace em dois.
- Alertar sobre causas como CPU, o que acorda pessoas sem dor para o usuário.
- Escolher um SLO de 100%, que não deixa orçamento de erro.
- Definir um exporter do Collector mas não listá-lo em um pipeline.