OpenTelemetry (OTel) é uma especificação e um conjunto de bibliotecas neutras em relação a fornecedores para produzir telemetria - traces, métricas e logs - em um modelo de dados comum, para que o código de instrumentação não precise ser reescrito para cada agente proprietário de fornecedor de APM.
Noções Básicas de Observabilidade já introduziu os três pilares e mostrou snippets de OTel para cada um deles; esta página aprofunda um nível no próprio OTel - como ele é estruturado internamente, por que a API e o SDK são divididos em pacotes separados e como a identidade de uma única requisição percorre cada span que ela toca.
Entender este modelo muda a forma como você lê a documentação e o código do OTel: a maior parte da confusão sobre "por que meu span não está aparecendo" ou "por que preciso de ambos esses pacotes" remonta a ainda não ter essa estrutura em mente.
O OpenTelemetry define telemetria como um conjunto de sinais (traces, métricas, logs) produzidos através de uma API estável e neutra em relação a fornecedores, processados e exportados por um SDK configurado separadamente, para que a instrumentação e a escolha do backend sejam desacopladas.
Por que Importa: Sem um modelo compartilhado, cada fornecedor de APM exige sua própria instrumentação proprietária, travando seu código a um backend específico e duplicando o esforço em todos os serviços e linguagens que você executa.
Quando Usar Este Modelo: Instrumentar um novo serviço para tracing distribuído, decidir entre auto-instrumentação e instrumentação manual, conectar um pipeline de exportador/coletor e depurar por que spans de dois serviços não estão sendo vinculados em um único trace.
Limitações / Compromissos: O SDK adiciona sobrecarga de inicialização e uma superfície de dependência real; a divisão API/SDK é fácil de inverter (trazer o SDK onde apenas a API é necessária); atributos de span irrestritos e amostragem completa ficam caros rapidamente em escala.
Tópicos Relacionados: métricas importantes (o método RED), SLOs e orçamentos de erro, seleção de ferramentas de APM, o modelo de observabilidade mais amplo.
Telemetria, no vocabulário do OTel, vem em três sinais: traces (o caminho e o tempo de uma operação em um sistema), métricas (números agregados ao longo do tempo, como contagens de requisições ou histogramas de latência) e logs (eventos discretos e com timestamp).
Antes do OpenTelemetry, cada um desses geralmente exigia um SDK separado e específico do fornecedor - um tracer Datadog, um cliente Prometheus, um remetente de logs proprietário - cada um com sua própria API de instrumentação, o que significava que o código escrito para um backend precisava ser reescrito para mudar para outro.
A principal contribuição do OTel é padronizar a forma desses dados e a API usada para produzi-los, deixando para onde eles vão como uma preocupação plugável e substituível.
A unidade fundamental do tracing é o span: uma única operação nomeada com um tempo de início, um tempo de fim, um conjunto de atributos chave-valor e um status.
Um trace não é uma lista plana de spans - é uma árvore, onde cada span (exceto a raiz) tem exatamente um pai, formando uma hierarquia que espelha como o trabalho foi realmente aninhado.
Essa forma - uma span raiz, filhos se expandindo para cada sub-operação, alguns filhos abrangendo serviços completamente diferentes - é o que permite que uma UI de tracing reconstrua "onde os 180ms realmente foram" em vez de apenas "esta requisição levou 180ms".
Um recurso é metadados que descrevem o que produziu a telemetria - nome do serviço, versão, ambiente de implantação - anexado uma vez por processo em vez de ser repetido em cada span, para que um backend possa agrupar e filtrar a telemetria por serviço sem que cada span carregue campos redundantes.
A divisão entre a API (@opentelemetry/api) e o SDK (@opentelemetry/sdk-node) é a decisão estrutural mais importante no OTel, e é deliberada em vez de incidental.
Autores de bibliotecas - as pessoas que escrevem um driver de banco de dados ou um cliente HTTP que outras pessoas importarão - dependem apenas do pacote da API.
A API é uma interface estável, em sua maioria no-op: chamar tracer.startSpan() sem um SDK registrado simplesmente não faz nada e retorna imediatamente, a um custo negligenciável.
Autores de aplicações registram um SDK uma vez, em seu próprio ponto de entrada, que é o que realmente transforma essas chamadas no-op em spans reais que são processados e exportados para algum lugar.
Essa divisão existe para que a instrumentação de uma biblioteca não force um backend, exportador ou política de amostragem específicos para todas as aplicações que a importam - a biblioteca apenas descreve o que aconteceu; a aplicação decide o que fazer com essa descrição.
Instrumentação vem em dois sabores, e eles se compõem em vez de competir.
Auto-instrumentação funciona aplicando patches em módulos bem conhecidos (http, express, drivers de banco de dados comuns) no momento do carregamento do módulo, envolvendo seus internos para criar automaticamente spans para requisições, consultas e chamadas de saída sem nenhuma alteração de código em seus próprios manipuladores.
É por isso que o arquivo de registro de instrumentação tem que ser a primeira importação em seu ponto de entrada - se express for importado antes que o pacote de auto-instrumentação tenha aplicado patches nele, o patch não terá mais nada para interceptar.
Instrumentação manual é você chamando explicitamente tracer.startActiveSpan() em torno de uma peça de lógica de negócios que a auto-instrumentação não consegue ver, porque ela não tem como conhecer seu domínio: "capturou um pagamento", "aplicou um desconto", "reconciliou um pedido".
// propagação de contexto, ilustrada: o span filho herda o trace id do pai// automaticamente porque startActiveSpan executa o callback dentro do contexto desse spantracer.startActiveSpan("captureOrder", async (parent) => { // qualquer span iniciado dentro deste callback se tornará filho de `parent`, // mesmo através de um limite assíncrono aguardado - é isso que "contexto ativo" significa await tracer.startActiveSpan("chargeCard", async (child) => { child.end(); }); parent.end();});
Propagação de contexto é o mecanismo que mantém um trace coerente através de limites de processo: os identificadores do span ativo são serializados em um cabeçalho HTTP de saída (traceparent, de acordo com o padrão W3C Trace Context) na saída, e desserializados de volta em um contexto ativo no serviço receptor, que é exatamente o que permite que um trace abranja dois processos Node completamente independentes e ainda seja renderizado como uma árvore conectada.
Amostragem decide quais traces realmente são gravados e exportados, e é um verdadeiro trade-off de engenharia, não uma nota de rodapé: gravar cada span para cada requisição em alto tráfego é caro para armazenar e consultar, mas amostrar agressivamente demais significa que o único trace que você precisou durante um incidente nunca foi capturado.
A amostragem baseada em cabeça decide no início de um trace, de forma barata, antes de saber se algo vai dar errado; a amostragem baseada em cauda espera até que um trace seja concluído e pode decidir "manter este, foi lento ou com erro" com um sinal muito melhor, ao custo de precisar armazenar spans em algum lugar (geralmente um coletor) antes que a decisão de amostragem seja tomada.
Cardinalidade é uma preocupação para spans tanto quanto é para logs: anexar um ID de usuário bruto, um corpo de requisição completo ou qualquer valor efetivamente ilimitado como um atributo de span infla o custo de armazenamento e consulta na maioria dos modelos de precificação de backends, é por isso que as convenções semânticas do OTel pressionam por um vocabulário fixo e bem conhecido de nomes de atributos (http.status_code, db.system) em vez de campos de formato livre.
O coletor - um processo autônomo que recebe telemetria OTLP, pode agrupar, filtrar e reexportar para um ou mais backends - desacopla ainda mais seu aplicativo de qualquer fornecedor único: seu serviço exporta para um coletor local via OTLP, e a configuração do coletor (não o código do seu aplicativo) decide se esses dados acabam em Datadog, Honeycomb, Grafana Tempo, ou vários deles ao mesmo tempo.
É para cá que a evolução do OTel está se dirigindo: o sinal de Logs está amadurecendo para ficar ao lado de traces e métricas sob o mesmo modelo de recurso e contexto, para que uma linha de log, um span e uma métrica sobre a mesma requisição compartilhem cada vez mais o mesmo trace_id sem um mecanismo de correlação separado adicionado posteriormente.
Estratégia de Amostragem
Força
Fraqueza
Melhor Ajuste
Baseada em cabeça (taxa fixa)
Simples, barata, decidida instantaneamente no início do trace
Pode perder os traces lentos/com erro exatos que você realmente deseja
Serviços de alto volume onde uma amostra representativa é suficiente
Baseada em cauda (pós-hoc)
Pode garantir que traces com erro/lentos sejam sempre mantidos
Requer o buffer de spans em um coletor antes de decidir; mais infraestrutura
Serviços onde cada trace relevante para incidentes importa
Sempre ativa (sem amostragem)
Nada é nunca perdido
Custo de armazenamento/consulta escala linearmente com o tráfego
Serviços de baixo tráfego ou janelas de depuração de curta duração
"OpenTelemetry é um produto APM que eu instalo." É um padrão de instrumentação e um conjunto de bibliotecas para produzir telemetria; você ainda precisa apontar um exportador para um backend (um coletor, um APM SaaS, um armazenamento de código aberto) para realmente visualizar qualquer coisa.
"A auto-instrumentação captura tudo que preciso." Ela cobre bibliotecas e protocolos bem conhecidos (HTTP, drivers de banco de dados comuns), mas não tem visibilidade sobre sua própria lógica de negócios - operações específicas do domínio precisam de spans manuais para aparecerem em um trace.
"Importar @opentelemetry/api configura exportadores ou começa a enviar dados." A API sozinha é uma interface estável, em sua maioria no-op; nada é gravado ou exportado até que uma aplicação registre um SDK, o que é uma escolha de design deliberada para manter as bibliotecas agnósticas em relação ao backend.
"Mais spans sempre significam melhor observabilidade." Além de um certo ponto, a criação excessiva de spans e atributos de alta cardinalidade adicionam custo de armazenamento e consulta sem adicionar valor de depuração proporcional - a mesma disciplina de cardinalidade que se aplica a logs se aplica a spans.
"Traces tornam logs e métricas desnecessários." Cada sinal responde a uma pergunta diferente - traces mostram timing e estrutura causal, métricas mostram tendências e agregados, logs carregam detalhes arbitrários específicos - e o modelo do OTel é explicitamente projetado para correlacionar todos os três, não para substituir dois deles pelo terceiro.
Uma especificação e um conjunto de bibliotecas neutras em relação a fornecedores para produzir traces, métricas e logs em um modelo de dados comum, para que o código de instrumentação não fique preso a um backend de observabilidade específico.
Por que a API e o SDK são pacotes npm separados em vez de um só?
Para que os autores de bibliotecas possam instrumentar seu código (um driver de banco de dados, um cliente HTTP) sem forçar todas as aplicações que o importam a também puxar um exportador, amostrador ou configuração de backend específicos. A API é estável e em sua maioria no-op por si só; o SDK é o que uma aplicação registra uma vez para realmente transformar a instrumentação em telemetria gravada e exportada.
Como um "trace" é realmente estruturado internamente?
Como uma árvore de spans, não uma lista plana - cada span (exceto a raiz) tem exatamente um pai, formando uma hierarquia que espelha como o trabalho subjacente foi aninhado, incluindo trabalho que cruzou para serviços completamente diferentes.
Como o contexto realmente se propaga entre dois processos Node separados?
Os identificadores do span ativo são serializados em um cabeçalho de saída (traceparent, de acordo com o padrão W3C Trace Context) quando seu serviço faz uma chamada de saída, e o serviço receptor desserializa esse cabeçalho de volta em um contexto ativo antes de criar seu próprio span filho - isso é o que permite que um único trace abranja múltiplos processos independentes.
Por que o arquivo de registro de instrumentação tem que ser importado primeiro?
Porque a auto-instrumentação funciona aplicando patches em módulos (como express ou http) no momento em que são carregados - se o módulo que está sendo corrigido for importado antes que o pacote de instrumentação tenha sido executado, o patch não terá mais nada para interceptar, e nenhum span será criado para esse módulo.
Quando preciso de instrumentação manual em vez de depender da auto-instrumentação?
Sempre que a operação que você se importa for lógica de negócios em vez de uma chamada de biblioteca bem conhecida - a auto-instrumentação só conhece protocolos e drivers para os quais foi construída especificamente para aplicar patches, então ela não tem como saber que "aplicar um desconto" ou "reconciliar um pedido" é uma unidade de trabalho significativa que merece seu próprio span.
Qual é o trade-off entre amostragem baseada em cabeça e baseada em cauda?
A amostragem baseada em cabeça é barata e simples, mas decide antes de saber se um trace acabou sendo interessante, então pode perder exatamente os traces lentos ou com erro que você gostaria durante um incidente. A amostragem baseada em cauda espera até que um trace seja concluído e pode manter confiavelmente os interessantes, mas requer o buffer de spans em um coletor, o que adiciona infraestrutura.
Vale a pena executar OpenTelemetry sempre com a sobrecarga que ele adiciona?
Não automaticamente - o SDK adiciona tempo de inicialização e uma pegada de dependência não trivial, e a instrumentação irrestrita (muitos spans, atributos de alta cardinalidade) pode adicionar custo real sem valor de insight proporcional. Ele se paga mais claramente assim que um serviço tem chamadas downstream suficientes ou complexidade tal que "onde o tempo foi" não pode ser respondido apenas com logs.
O que significa um "recurso" no OpenTelemetry, e por que não é apenas outro atributo de span?
Um recurso descreve o que produziu a telemetria - nome do serviço, versão, ambiente - anexado uma vez por processo em vez de ser repetido em cada span. Mantê-lo separado dos atributos por span permite que os backends agrupem e filtrem por serviço de forma barata, sem que cada span carregue campos redundantes.
Qual o objetivo de executar um coletor em vez de exportar diretamente para um fornecedor?
Um coletor recebe telemetria via OTLP e pode agrupar, filtrar e reexportar para um ou mais backends com base em sua própria configuração, não no código do seu aplicativo. Isso desacopla seu serviço de qualquer fornecedor único - trocar ou adicionar um backend se torna uma alteração de configuração do coletor, não um redeploy.
Por que os nomes dos atributos de span seguem um vocabulário fixo como `http.status_code`?
As convenções semânticas do OpenTelemetry definem nomes de atributos padrão para conceitos comuns especificamente para que ferramentas e painéis construídos contra um serviço funcionem da mesma forma contra qualquer outro serviço instrumentado com OTel, em vez de cada equipe inventar seus próprios nomes de campo para os mesmos dados.
Adotar OpenTelemetry significa que não preciso mais de um logger estruturado como Pino?
Não - o sinal de Logs do OTel está padronizando como logs estruturados se correlacionam com traces e métricas através de identificadores compartilhados, mas você ainda emite logs através de uma biblioteca como Pino; os dois trabalham juntos em vez de um substituir o outro.