Estratégias de Error Logging em Zig

Introdução

Logging eficiente de erros é essencial para diagnosticar problemas em produção. Zig oferece std.log como mecanismo de logging integrado, com níveis configuráveis e output customizável. Esta receita cobre os padrões mais úteis.

Para tratamento de erros em geral, veja Padrões Try/Catch e Error Sets Customizados.

Pré-requisitos

std.log Básico

const std = @import("std");

pub fn main() void {
    std.log.info("Servidor iniciado na porta {}", .{8080});
    std.log.warn("Conexão lenta: {}ms", .{500});
    std.log.err("Falha na conexão com banco", .{});
    std.log.debug("Variável x = {}", .{42});
}

Níveis de Log

NívelFunçãoUso
errstd.log.errErros que impedem operação
warnstd.log.warnSituações anormais mas recuperáveis
infostd.log.infoInformações operacionais
debugstd.log.debugDetalhes para desenvolvimento

Logging com Contexto de Erro

fn processarRequisicao(req: Requisicao) !Resposta {
    const dados = parsearBody(req.body) catch |err| {
        std.log.err("Falha ao parsear body: {s} - erro: {}", .{ req.path, err });
        return err;
    };

    const resultado = executarQuery(dados) catch |err| {
        std.log.err("Query falhou para {s}: {}", .{ req.path, err });
        return err;
    };

    std.log.info("Requisição {s} processada com sucesso", .{req.path});
    return resultado;
}

Log Escopado (Scoped Logging)

const log = std.log.scoped(.servidor);

fn iniciar() void {
    log.info("Servidor iniciando...", .{});
    // Output: info(servidor): Servidor iniciando...
}

// Outro módulo
const db_log = std.log.scoped(.database);

fn conectar() void {
    db_log.info("Conectando ao banco...", .{});
    // Output: info(database): Conectando ao banco...
}

Customizar Log Handler

const std = @import("std");

pub const std_options = struct {
    pub const log_level = .info; // Nível mínimo

    pub const logFn = meuLogHandler;
};

fn meuLogHandler(
    comptime level: std.log.Level,
    comptime scope: @TypeOf(.enum_literal),
    comptime format: []const u8,
    args: anytype,
) void {
    const scope_prefix = if (scope != .default)
        "(" ++ @tagName(scope) ++ ") "
    else
        "";

    const prefix = comptime level.asText() ++ ": " ++ scope_prefix;

    const stderr = std.io.getStdErr().writer();
    nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
}

Logging para Arquivo

const std = @import("std");

const Logger = struct {
    arquivo: std.fs.File,
    mutex: std.Thread.Mutex,

    pub fn init(caminho: []const u8) !Logger {
        return .{
            .arquivo = try std.fs.cwd().createFile(caminho, .{ .truncate = false }),
            .mutex = .{},
        };
    }

    pub fn deinit(self: *Logger) void {
        self.arquivo.close();
    }

    pub fn logErr(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.mutex.lock();
        defer self.mutex.unlock();

        const writer = self.arquivo.writer();
        const timestamp = std.time.timestamp();
        nosuspend writer.print("[{d}] ERRO: " ++ fmt ++ "\n", .{timestamp} ++ args) catch {};
    }

    pub fn logInfo(self: *Logger, comptime fmt: []const u8, args: anytype) void {
        self.mutex.lock();
        defer self.mutex.unlock();

        const writer = self.arquivo.writer();
        const timestamp = std.time.timestamp();
        nosuspend writer.print("[{d}] INFO: " ++ fmt ++ "\n", .{timestamp} ++ args) catch {};
    }
};

Logging Condicional em Comptime

const builtin = @import("builtin");

fn logDebug(comptime fmt: []const u8, args: anytype) void {
    if (builtin.mode == .Debug) {
        std.debug.print("[DEBUG] " ++ fmt ++ "\n", args);
    }
    // Em release, esta função compila para noop (zero overhead)
}

fn logPerformance(comptime operacao: []const u8, inicio: i64) void {
    if (builtin.mode == .Debug or builtin.mode == .ReleaseSafe) {
        const duracao = std.time.milliTimestamp() - inicio;
        std.log.info("{s}: {}ms", .{ operacao, duracao });
    }
}

Log de Stack Trace em Erros

fn operacaoPerigosa() !void {
    return error.FalhaInterna;
}

fn executar() void {
    operacaoPerigosa() catch |err| {
        std.log.err("Erro: {}", .{err});

        // Em debug, imprimir stack trace
        if (@errorReturnTrace()) |trace| {
            std.debug.dumpStackTrace(trace.*);
        }
    };
}

Boas Práticas

1. Use o nível correto

// ERR: operação falhou, ação necessária
std.log.err("Banco de dados inacessível: {}", .{err});

// WARN: anomalia, mas recuperável
std.log.warn("Tentativa {}/3 falhou, retrying...", .{tentativa});

// INFO: operação normal que vale registrar
std.log.info("Usuário {} autenticado", .{user_id});

// DEBUG: detalhes para desenvolvimento
std.log.debug("Buffer alocado: {} bytes", .{tamanho});

2. Inclua contexto suficiente

// RUIM: sem contexto
std.log.err("Erro ao processar", .{});

// BOM: com contexto
std.log.err("Erro ao processar requisição {s} do user {}: {}", .{
    req.path, req.user_id, err,
});

3. Não logue dados sensíveis

// RUIM: expõe senha
std.log.info("Login: user={s} pass={s}", .{ user, senha });

// BOM: sem dados sensíveis
std.log.info("Login: user={s}", .{user});

Testes

test "logging não causa crash" {
    std.log.info("teste de log: {}", .{42});
    std.log.err("teste de erro: {s}", .{"mensagem"});
}

Veja Testes Unitários para mais sobre testes.

Conclusão

Zig oferece ferramentas de logging simples e eficientes via std.log. Use scoped logging para organizar output por módulo, customize o handler para produção, e aproveite comptime para eliminar logging de debug em builds de release sem overhead.

Para mais sobre erros, veja Padrões Try/Catch, Error Sets Customizados e Padrões Errdefer.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.