---
title: "Observabilidade em Zig: Logs Estruturados e Métricas Prometheus | Zig Brasil"
url: "https://ziglang.com.br/artigos/zig-observabilidade-logs-prometheus/"
markdown_url: "https://ziglang.com.br/artigos/zig-observabilidade-logs-prometheus.MD"
description: "Guia prático de observabilidade em Zig: logging estruturado JSON, níveis de log, métricas Prometheus com /metrics, exposição de counters e histograms e integração com Grafana. Tutorial em português."
date: "2026-06-21"
author: ""
---

# Observabilidade em Zig: Logs Estruturados e Métricas Prometheus | Zig Brasil

Guia prático de observabilidade em Zig: logging estruturado JSON, níveis de log, métricas Prometheus com /metrics, exposição de counters e histograms e integração com Grafana. Tutorial em português.


# Observabilidade em Zig: Logs Estruturados e Métricas Prometheus

Serviços em produção precisam de **observabilidade**: a capacidade de responder rapidamente "o que está acontecendo agora?" e "por que isso falhou?". As três pernas clássicas são **logs**, **métricas** e **traces**. Zig não traz um framework de observabilidade pronto na biblioteca padrão, mas a combinação de `std.log`, um writer JSON simples e um endpoint Prometheus entrega cobertura sólida para a maioria dos serviços.

Este guia mostra como implementar, do zero, logging estruturado JSON e métricas no formato Prometheus exposition, prontos para Grafana, Loki, Datadog, CloudWatch ou qualquer stack moderna.

## Os Três Pilares da Observabilidade

| Pilar | Responde a | Formato típico |
|---|---|---|
| **Logs** | O que aconteceu, em detalhe | Linhas de texto ou JSON |
| **Métricas** | Como está o sistema, agregado | Séries temporais (counter, gauge, histogram) |
| **Traces** | Por que uma requisição foi lenta | Spans encadeados por `trace_id` |

Para começar de forma pragmática, foque em **logs estruturados** e **métricas Prometheus** — os dois retornam valor imediato e cobrem 90% das necessidades de monitoramento.

## Logging Estruturado em JSON

Logs em texto livre são difíceis de filtrar em escala. JSON estruturado, em contraste, é parseado sem ambiguidade e permite consultas como `level=error AND route=/checkout` em Loki, Elasticsearch ou Datadog.

A `std.log` do Zig escreve para stderr com níveis (`Debug`, `Info`, `Warn`, `Error`) e suporta escopos nomeados via `std.log.scoped`. Para produzir JSON estruturado, vamos construir um helper simples:

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

pub const LogLevel = enum {
    debug, info, warn, err,

    pub fn toString(self: LogLevel) []const u8 {
        return switch (self) {
            .debug => "debug",
            .info => "info",
            .warn => "warn",
            .err => "error",
        };
    }
};

pub const Logger = struct {
    out: std.fs.File.Writer,
    level: LogLevel,

    pub fn log(self: *Logger, level: LogLevel, comptime fmt: []const u8, args: anytype, trace_id: []const u8) void {
        if (@intFromEnum(level) < @intFromEnum(self.level)) return;
        const msg = std.fmt.allocPrint(std.heap.page_allocator, fmt, args) catch return;
        defer std.heap.page_allocator.free(msg);
        const ts = std.time.timestamp();
        self.out.print(
            \\{{"ts":{d},"level":"{s}","trace_id":"{s}","msg":"{s}"}}
            ++ "\n",
            .{ ts, level.toString(), trace_id, msg },
        ) catch return;
    }
};
```

Uso típico em um handler HTTP:

```zig
var logger = Logger{
    .out = std.io.getStdErr().writer(),
    .level = .info,
};

logger.log(.info, "request method={s} path={s} status={d} duration_ms={d}", .{ method, path, status, duration }, trace_id);
```

Saída:

```json
{"ts":1718956800,"level":"info","trace_id":"a3f2","msg":"request method=GET path=/checkout status=200 duration_ms=12"}
```

Para escapar aspas dentro de strings reais, troque o `msg` por um objeto JSON montado com `std.json.stringify`, que cuida de escaping corretamente para valores dinâmicos.

### Boas práticas para logs em produção

- **Sempre inclua `trace_id`**: gerado no início de cada request e propagado para downstream.
- **Logue em stderr**: deixa stdout livre para output de comando e facilita coleta por Docker/journald.
- **Use níveis com disciplina**: `info` para eventos de negócio, `warn` para degradações recuperáveis, `error` para falhas que precisam de ação.
- **Evite dados sensíveis**: nunca logue tokens, senhas, PII em texto puro.
- **Log estruturado desde o início**: migrar de texto livre para JSON depois é caro.

## Métricas no Formato Prometheus

Prometheus coleta métricas fazendo **scrape** periódico de um endpoint HTTP `/metrics`. Esse endpoint retorna texto simples no formato **exposition**, que qualquer linguagem consegue gerar. Não é necessário SDK proprietário.

Exemplo de saída válida para `/metrics`:

```
# HELP http_requests_total Total de requisições HTTP por rota.
# TYPE http_requests_total counter
http_requests_total{method="GET",route="/home",status="200"} 4523
http_requests_total{method="POST",route="/checkout",status="500"} 7

# HELP http_request_duration_seconds Latência das requisições.
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{route="/home",le="0.005"} 4300
http_request_duration_seconds_bucket{route="/home",le="0.01"} 4450
http_request_duration_seconds_bucket{route="/home",le="0.05"} 4510
http_request_duration_seconds_bucket{route="/home",le="0.1"} 4520
http_request_duration_seconds_bucket{route="/home",le="+Inf"} 4523
http_request_duration_seconds_sum 12.34
http_request_duration_seconds_count 4523

# HELP go_goroutines Conexões ativas.
# TYPE process_open_connections gauge
process_open_connections 42
```

### Implementando um Counter thread-safe

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

pub const Counter = struct {
    value: std.atomic.Value(u64) = .{ .raw = 0 },
    help: []const u8,
    name: []const u8,

    pub fn init(name: []const u8, help: []const u8) Counter {
        return .{ .name = name, .help = help };
    }

    pub fn inc(self: *Counter) void {
        _ = self.value.fetchAdd(1, .monotonic);
    }

    pub fn add(self: *Counter, n: u64) void {
        _ = self.value.fetchAdd(n, .monotonic);
    }

    pub fn write(self: *Counter, writer: anytype) !void {
        try writer.print("# HELP {s} {s}\n", .{ self.name, self.help });
        try writer.print("# TYPE {s} counter\n", .{ self.name });
        try writer.print("{s} {d}\n", .{ self.name, self.value.load(.monotonic) });
    }
};
```

### Gauge para valores que sobem e descem

```zig
pub const Gauge = struct {
    value: std.atomic.Value(u64) = .{ .raw = 0 },
    name: []const u8,
    help: []const u8,

    pub fn set(self: *Gauge, v: u64) void {
        self.value.store(v, .monotonic);
    }

    pub fn write(self: *Gauge, writer: anytype) !void {
        try writer.print("# HELP {s} {s}\n", .{ self.name, self.help });
        try writer.print("# TYPE {s} gauge\n", .{ self.name });
        try writer.print("{s} {d}\n", .{ self.name, self.value.load(.monotonic) });
    }
};
```

### Expondo o endpoint `/metrics`

Agregue todos os counters/gauges em um struct global e sirva via HTTP. Exemplo conceitual:

```zig
var requests_total = Counter.init("http_requests_total", "Total de requisições HTTP.");
var open_connections = Gauge.init("process_open_connections", "Conexões abertas.");

fn metricsHandler(writer: anytype) !void {
    try requests_total.write(writer);
    try open_connections.write(writer);
}
```

Aponte o Prometheus para `http://seu-servico:8080/metrics` com `scrape_interval: 15s`. Em segundos, os dashboards no Grafana começam a aparecer.

## Histograma de Latência

Para responder "qual o p99 da latência do checkout?", você precisa de um **histograma**. A ideia é dividir observações em buckets e expor contagem por bucket + soma + total. O Prometheus computa p50, p90, p99 via `histogram_quantile()`.

```zig
pub const Histogram = struct {
    buckets: []const f64, // limites, ex: {0.005, 0.01, 0.05, 0.1, 0.5, 1, +Inf}
    counts: []std.atomic.Value(u64),
    sum: std.atomic.Value(u64) = .{ .raw = 0 }, // soma em microssegundos p/ inteiro
    total: std.atomic.Value(u64) = .{ .raw = 0 },
    name: []const u8,
    help: []const u8,

    pub fn observe(self: *Histogram, value_seconds: f64) void {
        const us: u64 = @intFromFloat(value_seconds * 1_000_000);
        _ = self.sum.fetchAdd(us, .monotonic);
        _ = self.total.fetchAdd(1, .monotonic);
        for (self.buckets, 0..) |bound, i| {
            if (value_seconds <= bound) {
                _ = self.counts[i].fetchAdd(1, .monotonic);
            }
        }
    }
};
```

No Grafana, a query típica para p99 é:

```promql
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route))
```

## Composição: Logs + Métricas + Trace ID

O padrão produtivo em serviços Zig reais:

1. **Middleware HTTP** gera `trace_id` (UUID ou timestamp+random) no início de cada request.
2. **Counter `http_requests_total`** é incrementado a cada resposta, com labels de `route` e `status`.
3. **Histograma `http_request_duration_seconds`** observa a duração de cada request.
4. **Logger estruturado** emite uma linha JSON por request com `trace_id`, `method`, `path`, `status`, `duration_ms`.
5. **Gauge `process_open_connections`** reflete conexões ativas.

Com isso, quando um alerta no Grafana dispara, você segue o `trace_id` do pico de latência até o log correspondente, e do log para o span (se tracing estiver habilitado).

## OpenTelemetry em Zig

Ainda não há SDK oficial OTel estável para Zig em 2026. As opções práticas:

- **Métricas via Prometheus**: cobre a maior parte das necessidades.
- **Traces via OTLP HTTP manual**: gere spans em JSON OTLP e poste para o collector.
- **Bindings C**: linkar a biblioteca C do OTel SDK via `@cImport` para traces completos.
- **W3C Trace Context**: propague o header `traceparent` manualmente entre serviços.

Para a maioria dos serviços pequenos e médios, Prometheus + Loki (para logs JSON) já entrega observabilidade robusta sem precisar de OTel completo.

## Erros Comuns a Evitar

- **Métricas de alta cardinalidade**: nunca use `user_id` ou `email` como label — explode a memória do Prometheus.
- **Logs verbosos em hot path**: logar dentro de loops apertados degrada performance.
- **Esquecer `# TYPE`**: sem isso, o Prometheus pode inferir o tipo errado e quebrar queries.
- **Buckets mal calibrados**: buckets default não servem para todo serviço; ajuste ao seu SLO.
- **stderr não coletado**: configure Docker/journald/k8s para capturar stderr do processo Zig.

## Conclusão

Observabilidade em Zig é direto de implementar porque os formatos (JSON para logs, texto simples para Prometheus) são agnósticos de linguagem. Com um logger JSON, alguns wrappers atômicos sobre `std.atomic.Value` e um endpoint `/metrics`, você entrega dashboards e alertas profissionais sem depender de SDKs pesados. Comece por logs estruturados com `trace_id` e um counter de requests; adicione histograma de latência e gauges de recursos conforme o serviço amadurece.

Para aprofundar a stack de produção, leia também nosso guia de [Servidor HTTP em Zig para Produção](/artigos/zig-http-server-producao/), o tutorial de [Zig com Docker](/artigos/zig-docker-containers/) (para empacotar o serviço com healthcheck) e o artigo sobre [Cron Jobs em Zig para Produção](/artigos/zig-cron-jobs-producao/), que mostra como orquestrar tarefas recorrentes observáveis.
