Pular para o conteúdo
Quiz
← Início

Observabilidade

100 questões

Certas
0
Erradas
0
Não respondidas
100
Dificuldade
Número de questões

100 questões neste filtro

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 , 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 .

As palavras usadas no resto do resumo
PalavraSignificado simplesExemplo
TelemetriaTudo que o sistema envia sobre si mesmoUma linha de log, um número, um trace
InstrumentaçãoO código (seu ou de uma biblioteca) que produz a telemetriaUma linha que conta cada requisição
BackendO lugar que guarda e mostra a telemetriaPrometheus, Loki, Tempo, Grafana
ServiçoUm programa pequeno que faz uma tarefaorders, inventory, gateway
IncidenteUm momento em que uma falha prejudica os usuáriosO checkout falha por 10 minutos
seu código --> instrumentação --> telemetria
                                       |
                                       v
                         guarda (backend) --> telas
                                              e alertas
                                       |
                                       v
                                uma pessoa entende
Do código ao entendimento
↑ Voltar ao sumário

Monitoramento 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

TipoO que éExemploQuem resolve
Known-unknownUma pergunta que você sabia que fariaA CPU passou de 90%?Monitoramento: um painel ou um alerta
Unknown-unknownUma falha que ninguém imaginou, então não tem painel nem alertaSó um cliente em uma versão do app falhaObservabilidade: você investiga mesmo assim

Cardinalidade e dimensionalidade

é 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.

é 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

  1. Comece pela visão geral do que parece errado (por exemplo, requisições lentas).
  2. Confira o que você já sabe.
  3. Procure os campos cujos valores diferem entre o grupo ruim e o resto.
  4. Filtre por esses campos, e agora você olha um grupo menor.
  5. 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 . 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ário

Os 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.

Três sinais, três tarefas
SinalO que éBom paraRuim para
MétricaUm número medido ao longo do tempo, com nome e labelsQuanto? Qual a tendência? AlertasO detalhe de uma requisição
LogUm registro de um evento, com hora e camposO que aconteceu, com qual mensagem?Tendências baratas de longo prazo
TraceA história de UMA requisição entre serviços, feita de spansPara 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 é 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.

Tempo de cada span de uma requisição (milissegundos, 'tempo próprio')
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 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.

↑ Voltar ao sumário

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 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"}
Uma requisição, uma linha estruturada

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 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
Escolhe o stream, mantém linhas com 'timeout', lê o JSON, mantém status 500 ou mais
  • {service="orders"} escolhe os streams pelos labels indexados.
  • |= "timeout" mantém só as linhas que contêm o texto.
  • | json transforma os campos do JSON em labels que podem ser comparados. Campos aninhados são achatados com _, então http.status vira http_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.
↑ Voltar ao sumário

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.

TipoO que éExemploComo ler
CounterUm total que só sobe (volta a zero quando o processo reinicia)http_requests_totalUse rate() ou increase(), não o valor bruto
GaugeUm valor que sobe e desceTamanho de fila, memória em uso, conexões abertasLeia o valor atual, a média ou o máximo
HistogramContagens de observações dentro de faixas (buckets)http_request_duration_secondshistogram_quantile() estima percentis
SummaryPercentis calculados dentro do appQuantil 0,99 pronto de uma instânciaNã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"}  100
Buckets cumulativos: 100 observações no total

Para 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])
  )
)
rate em cada bucket, soma por le, quantil por último

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.

Número máximo de séries de um counter
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

Exportado para o Prometheus, um UpDownCounter aparece como gauge
InstrumentoComo os valores são registradosExemplo
CounterSó somaRequisições atendidas
UpDownCounterSoma e subtrai; saldos de instâncias podem ser somadosRequisições em andamento
HistogramRegistra uma distribuição de valoresDuração da requisição
GaugeO valor absoluto atual, não aditivoTemperatura
↑ Voltar ao sumário

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    840
Um trace como árvore. A indentação significa 'filho de'

Lendo: 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 . 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)
A versão 00 tem quatro campos separados por hífens

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

Com o kind, um backend consegue separar tempo de rede de tempo de processamento
Span kindPapelExemplo
CLIENTFaz uma chamada síncronaorders chamando inventory
SERVERRecebe e trata uma chamada síncronainventory tratando a requisição HTTP
PRODUCEREnvia um job ou mensagem assíncronaPublicar em uma fila
CONSUMERRecebe um job ou mensagem assíncronaUm worker lendo a fila
INTERNALTrabalho que fica dentro do processoUm 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.

TipoQuando decideBomCusto
Head samplingNo início da requisição, sem saber como ela terminaBarato e simplesPode descartar os traces interessantes
Tail samplingDepois que o trace está completo, olhando tudoPode 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.

↑ Voltar ao sumário

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çaO que faz
APIUm conjunto pequeno e estável de chamadas que o código usa para dizer o que poderia ser medido
SDKA implementação: amostragem, processadores, exportadores; a aplicação o configura
OTLPO protocolo que transporta traces, métricas e logs
CollectorUm programa que recebe, processa e exporta telemetria
Convenções semânticasNomes 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]
Os componentes são definidos primeiro, depois ligados na seção service

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ário

SLI, 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.

PalavraSignificaO que éExemplo
SLIService level indicatorA medição: eventos bons divididos por eventos válidosRequisições sem erro / todas as requisições
SLOService level objectiveA meta interna para o SLI em uma janela de tempo99,9% em 30 dias
SLAService level agreementO contrato com o cliente, com consequênciasAbaixo 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.
Minutos ruins permitidos em 30 dias (43.200 minutos)
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ário

Alertas

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

TipoDispara quandoExemplo
Baseado em sintomaO usuário está sofrendo: erros, lentidão, dados erradosTaxa de erro do checkout acima do objetivo
Baseado em causaUm estado interno que pode levar a problemasCPU 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: page
Dispara só se a expressão ficar verdadeira por 5 minutos

Um 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: page
Multi-janela: janela longa e curta com o mesmo limite

Por 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:

RecursoO que fazExemplo
Grouping (agrupamento)Junta alertas parecidos em uma notificação300 alertas InstanceDown viram uma mensagem listando 300 instâncias
Inhibition (inibição)Silencia alertas enquanto um mais amplo já está disparandoEsconder 'serviço lento' enquanto 'datacenter fora' dispara
Silences (silenciamentos)Muta alertas por um tempo escolhido por uma pessoaDurante uma manutenção planejada
↑ Voltar ao sumário

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étodoAutorLetrasOlha para
REDTom WilkieRate, Errors, DurationServiços movidos a requisições, do lado de quem chama
USEBrendan GreggUtilization, Saturation, ErrorsRecursos (CPU, memória, disco, rede), por dentro
Quatro sinais de ouroLivro SRE do GoogleLatency, Traffic, Errors, SaturationSistemas 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.

Latência média no exemplo
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
↑ Voltar ao sumário

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                       80
Amostras de 1.000 em que cada frame estava na pilha

O 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.

Amostras de tempo próprio por função (de 1.000)
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.mallocgc
Parte da saída do go tool pprof
  • flat é 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 cum enorme e flat perto de zero. O ponto quente é a função com flat alto. Aqui é main.normalizeKey, com 6,50 s.
↑ Voltar ao sumário

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.

FerramentaSinalLinguagem de consultaPapel
PrometheusMétricasPromQLColeta e guarda números ao longo do tempo
LokiLogsLogQLGuarda linhas de log, indexa labels
TempoTracesTraceQLGuarda traces
GrafanaNenhumConsulta as outrasInterface: dashboards; não guarda telemetria
serviços --métricas--> Prometheus --+
         --logs-----> Loki       ----+--> Grafana
         --traces---> Tempo      ----+  (uma tela)
Cada sinal tem seu repositório; o Grafana lê todos

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 }
Spans do serviço inventory que levaram mais de 500 ms. O resultado são os traces que os contêm
↑ Voltar ao sumário

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.

  1. 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).
  2. Um dashboard mostra RED e USE. Taxa e erros parecem normais, mas o histograma de duração tem uma cauda gorda (métricas, RED).
  3. Um exemplar abre um trace. O trace mostra que inventory stock.read tem o maior tempo próprio (traces, tempo próprio).
  4. 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).
  5. Um profile aponta o código. Um flame graph mostra uma função com um frame largo (profiling).
Qual ferramenta responde a qual pergunta
PerguntaMelhor 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.
↑ Voltar ao sumário