---
title: "Zig e OpenTelemetry: Traces, Métricas e Logs sem Framework Pesado"
url: "https://ziglang.com.br/artigos/zig-opentelemetry-traces-metricas-logs/"
markdown_url: "https://ziglang.com.br/artigos/zig-opentelemetry-traces-metricas-logs.MD"
description: "Como instrumentar aplicações Zig com a mentalidade do OpenTelemetry: spans, métricas, logs correlacionados, propagação de contexto, exporters e limites de overhead em produção."
date: "2026-05-25"
author: ""
---

# Zig e OpenTelemetry: Traces, Métricas e Logs sem Framework Pesado

Como instrumentar aplicações Zig com a mentalidade do OpenTelemetry: spans, métricas, logs correlacionados, propagação de contexto, exporters e limites de overhead em produção.


Observabilidade boa não começa quando o incidente já está aberto. Ela começa no desenho do binário: quais operações merecem um trace, quais métricas indicam degradação real, quais logs ajudam a explicar uma falha e quanto overhead você aceita pagar para enxergar o sistema. Em Zig, essa discussão fica ainda mais importante porque a linguagem não esconde custo em framework, runtime ou agente mágico. Se você quer sinal de produção, precisa escolher onde instrumentar.

OpenTelemetry virou o padrão de fato para traces, métricas e logs em aplicações modernas. Em linguagens como Go, Java, Python e Node, o ecossistema já oferece SDKs maduros, auto-instrumentação e exporters prontos para OTLP, Prometheus, Jaeger, Tempo, Honeycomb, Datadog e outros backends. Em Zig, o cenário ainda é mais manual. Isso não torna o padrão inútil. Pelo contrário: a mentalidade do OpenTelemetry ajuda a organizar a instrumentação mesmo quando parte do código ainda é própria.

Este guia mostra como pensar em **OpenTelemetry com Zig** sem prometer maturidade que o ecossistema ainda não tem. O foco é arquitetura prática: spans para operações importantes, métricas com cardinalidade controlada, logs correlacionados, propagação de contexto em HTTP, exporters simples e limites de overhead. Ele complementa os guias de [observabilidade em Zig](/artigos/zig-observabilidade/), [servidor HTTP em produção](/artigos/zig-http-server-producao/), [filas e workers em background](/artigos/zig-filas-workers-background/), [eBPF para observabilidade Linux](/artigos/zig-ebpf-observabilidade-rede-linux/) e [configuração segura em produção](/artigos/zig-configuracao-segura-segredos-env/).

## O que o OpenTelemetry resolve

OpenTelemetry não é apenas uma biblioteca. É um vocabulário comum para descrever o que uma aplicação fez. Esse vocabulário tem três pilares principais.

Traces mostram o caminho de uma operação entre componentes. Uma requisição HTTP pode começar no gateway, passar por um serviço Zig, chamar uma API externa, gravar em SQLite, publicar um job e responder ao usuário. Cada etapa vira um span com nome, duração, status e atributos.

Métricas mostram comportamento agregado ao longo do tempo: taxa de requisições, latência por rota, erros por tipo, tamanho de fila, jobs pendentes, bytes processados, conexões abertas, uso de memória e tempo de boot.

Logs explicam eventos discretos. Eles devem carregar contexto suficiente para responder perguntas como: qual `trace_id` falhou, qual rota estava em execução, qual job foi reprocessado, qual timeout ocorreu e qual dependência externa respondeu mal.

O valor do padrão aparece quando esses sinais conversam. Um alerta de latência aponta para uma métrica. A métrica leva para um trace lento. O trace mostra o span problemático. O span tem logs correlacionados. Você investiga a causa sem precisar adivinhar em qual máquina o erro apareceu.

## A realidade do ecossistema Zig

Hoje, Zig ainda não tem a mesma camada pronta de OpenTelemetry que linguagens mais estabelecidas têm. Isso significa que você deve evitar duas armadilhas.

A primeira é fingir que existe auto-instrumentação completa. Se o seu serviço usa `std.http.Server`, sockets diretos, SQLite via C, filas próprias e um loop de workers, você provavelmente vai criar wrappers explícitos em volta desses pontos. Isso combina com Zig, mas exige disciplina.

A segunda é copiar a arquitetura de uma linguagem com garbage collector sem adaptar os custos. Um SDK de tracing pode alocar por span, criar strings, montar JSON, fazer batching e exportar em background. Em Zig, você deve saber qual allocator está sendo usado, quando o buffer cresce, como o shutdown descarrega eventos e o que acontece se o collector estiver fora.

Um caminho realista é começar com uma camada pequena de instrumentação interna, compatível com os conceitos do OpenTelemetry, e exportar em um formato simples. Quando bibliotecas Zig maduras aparecerem ou quando você decidir usar uma biblioteca C, a fronteira já estará clara.

## Desenhe spans como operações de negócio

O erro comum em tracing é criar span demais. Se cada função vira um span, o trace fica barulhento, caro e difícil de ler. Em Zig, prefira instrumentar fronteiras relevantes:

- entrada de requisição HTTP;
- chamada para API externa;
- consulta ou transação de banco;
- execução de job em background;
- leitura ou escrita de arquivo grande;
- parsing de payload pesado;
- operação crítica de criptografia, compressão ou compilação;
- chamada FFI para biblioteca C importante.

Um span mínimo precisa de nome, início, fim, status e atributos. Em Zig, isso pode começar como uma struct simples:

```zig
const std = @import("std");

const SpanStatus = enum { ok, err };

const Span = struct {
    trace_id: [16]u8,
    span_id: [8]u8,
    parent_span_id: ?[8]u8,
    name: []const u8,
    start_ns: i128,
    end_ns: i128 = 0,
    status: SpanStatus = .ok,

    pub fn finish(self: *Span) void {
        self.end_ns = std.time.nanoTimestamp();
    }

    pub fn durationNs(self: Span) i128 {
        return self.end_ns - self.start_ns;
    }
};
```

Esse exemplo não é um SDK completo. Ele mostra a fronteira: o código de negócio recebe um contexto, abre um span, executa a operação e fecha o span. Depois outro componente decide se serializa, amostra, descarta ou exporta.

## Propagação de contexto em HTTP

Tracing distribuído depende de propagação. Se uma requisição entra com cabeçalhos `traceparent` e `tracestate`, o serviço Zig deve preservar esse contexto ao criar spans filhos e repassá-lo para chamadas downstream.

O formato W3C Trace Context usa um cabeçalho como:

```text
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
```

Ele carrega versão, `trace_id`, `parent_id` e flags. Para um serviço Zig, a primeira implementação não precisa cobrir todos os detalhes do mundo. Ela precisa fazer o básico com segurança:

1. validar tamanho e separadores;
2. rejeitar IDs inválidos ou zerados;
3. preservar o `trace_id` quando válido;
4. criar novo `span_id` para a operação atual;
5. incluir `traceparent` nas chamadas HTTP de saída.

Se o cabeçalho não existir, gere um novo trace. Se existir mas for inválido, comece outro trace e registre um contador de propagação inválida. Não deixe uma string externa malformada quebrar a requisição.

## Métricas: controle cardinalidade antes de exportar

Métricas são mais perigosas do que parecem. Um contador simples como `http_requests_total` é barato. O problema aparece quando você adiciona labels livres demais: `user_id`, `email`, `path` completo, `job_id`, `tenant`, `error_message`. Cardinalidade alta explode o backend de métricas e transforma observabilidade em incidente.

Para Zig, a regra deve ser explícita: label só entra se tiver conjunto pequeno e previsível. Bons labels:

- rota normalizada, como `/api/jobs/:id`, não o caminho real;
- método HTTP;
- status class, como `2xx`, `4xx`, `5xx`;
- tipo de job;
- nome da dependência externa;
- resultado, como `ok`, `timeout`, `erro_validacao`.

Evite IDs, tokens, endereços de e-mail, mensagens brutas e payloads. Essa disciplina também protege privacidade e reduz risco de vazar segredo, como discutido no guia de [configuração segura](/artigos/zig-configuracao-segura-segredos-env/).

Um conjunto inicial útil para serviço HTTP em Zig:

- `http_server_requests_total` por método, rota e status;
- `http_server_request_duration_seconds` por método e rota;
- `process_start_time_seconds`;
- `zig_allocator_bytes_in_use`, se você tiver wrapper de allocator;
- `worker_jobs_total` por tipo e resultado;
- `worker_queue_depth` por fila;
- `external_client_requests_total` por dependência e resultado.

Comece pequeno. Métrica boa é aquela que alguém usa em alerta, dashboard ou investigação.

## Logs correlacionados não são despejo de texto

Logs continuam úteis, mas precisam carregar contexto. Em vez de imprimir apenas `erro ao processar job`, registre campos previsíveis:

```json
{"level":"error","msg":"job falhou","trace_id":"...","span_id":"...","job_type":"webhook","attempt":3,"error":"timeout"}
```

Em Zig, isso costuma significar escrever um logger estruturado próprio ou um wrapper em torno de `std.log`. O ponto principal é não depender de concatenação solta de strings. Defina campos comuns: `service.name`, `service.version`, `environment`, `trace_id`, `span_id`, `route`, `job_type` e `dependency`.

Também defina uma política de redaction. Logs de produção não devem carregar token, senha, cookie, header `Authorization`, DSN com credencial, payload completo de webhook ou dados pessoais desnecessários. Como Zig incentiva tipos explícitos, use wrappers como `Secret` e funções de serialização que se recusam a imprimir valores sensíveis.

## Exporters: simples primeiro, OTLP depois

OpenTelemetry normalmente exporta dados via OTLP, por HTTP ou gRPC. Para Zig, você tem algumas opções.

A opção mais simples é expor métricas em formato Prometheus e enviar logs JSON para stdout. Em containers, isso já integra bem com Prometheus, Grafana Agent, Vector, Fluent Bit ou outro coletor. Para muitos serviços pequenos, esse primeiro passo entrega 80% do valor.

A segunda opção é enviar spans e métricas para um OpenTelemetry Collector local. Seu processo Zig fala com o collector por HTTP em formato OTLP/JSON ou por um formato intermediário que você controla. O collector faz batching, retry, autenticação e exportação para o backend final. Essa separação é saudável: o binário Zig continua pequeno e previsível, enquanto o collector absorve complexidade operacional.

A terceira opção é usar bibliotecas C ou bindings quando o projeto exigir compatibilidade mais profunda. Zig consegue linkar com C, mas isso muda o perfil de distribuição, build e segurança. Vale a pena quando você precisa de OTLP completo, compressão, TLS avançado, batching robusto e semântica compatível com uma plataforma corporativa.

Para aplicações Zig pequenas, a recomendação prática é: métricas Prometheus, logs JSON correlacionados e spans exportados para collector apenas nos fluxos críticos.

## Amostragem e overhead

Nem todo trace precisa ser exportado. Em tráfego alto, grave todos os erros, todos os fluxos raros e uma amostra das requisições bem-sucedidas. A decisão pode ser probabilística, por rota ou por header de debug interno.

O cuidado é manter a decisão no início do trace. Se uma requisição não foi amostrada, evite alocar listas de eventos e atributos que serão descartados no final. Em Zig, essa diferença importa. Você pode representar isso no contexto:

```zig
const TraceContext = struct {
    trace_id: [16]u8,
    current_span_id: [8]u8,
    sampled: bool,
};
```

Quando `sampled` for falso, spans podem virar no-ops baratos, enquanto métricas e logs de erro continuam ativos. Assim você mantém visibilidade sem transformar observabilidade em gargalo.

## Checklist para produção

Antes de chamar a instrumentação de pronta, passe por esta lista:

1. cada requisição HTTP recebe ou cria `trace_id`;
2. chamadas externas propagam `traceparent`;
3. erros incluem `trace_id` nos logs;
4. métricas não usam labels de alta cardinalidade;
5. segredos são redigidos antes de logar;
6. exporter não bloqueia o caminho crítico indefinidamente;
7. shutdown tenta descarregar spans pendentes com timeout;
8. dashboards mostram latência, erro e saturação;
9. alertas apontam para sintomas de usuário, não ruído interno;
10. overhead foi medido com carga realista.

Essa última linha é essencial. Não basta afirmar que Zig é rápido. Instrumentação muda alocação, CPU, lock contention, tamanho de resposta e tempo de shutdown. Meça antes e depois, usando os mesmos princípios do guia de [benchmarking em Zig](/artigos/zig-benchmarking-medir-performance/).

## Quando usar Go, Rust ou outro componente junto

Zig pode ser excelente para o serviço ou agente que precisa ser pequeno, previsível e próximo do sistema. Mas o ecossistema de OpenTelemetry em outras linguagens é mais maduro. Em uma plataforma grande, faz sentido deixar o collector, gateways de telemetria e integrações complexas em ferramentas já consolidadas, enquanto o binário Zig emite sinal simples e correto.

No cluster de programação, <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go continua sendo uma referência natural para serviços de observabilidade e integrações OpenTelemetry</a>, principalmente por causa de SDKs, exporters e projetos como Prometheus, Grafana Tempo e muitos agentes cloud-native. A decisão saudável não é “Zig ou Go para tudo”. É usar Zig onde controle de memória, distribuição e custo operacional importam, e integrar com ferramentas maduras onde o padrão já está resolvido.

## Conclusão

OpenTelemetry em Zig ainda exige trabalho manual, mas a direção é clara: traces para operações importantes, métricas com cardinalidade controlada, logs estruturados com correlação e exportação que não derruba o caminho crítico. Zig combina bem com essa disciplina porque força escolhas explícitas sobre alocação, I/O, erros e limites.

O melhor começo não é criar um SDK perfeito. É instrumentar um serviço real com poucos sinais bons: latência por rota, taxa de erro, profundidade de fila, chamadas externas, logs com `trace_id` e um trace amostrado para fluxos críticos. Depois disso, evolua para collector, OTLP e bibliotecas mais completas conforme a necessidade aparecer.

Observabilidade não deve transformar o binário Zig em uma aplicação pesada. Ela deve fazer o contrário: dar visibilidade suficiente para manter serviços pequenos, rápidos e confiáveis em produção.
