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
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
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ível | Função | Uso |
|---|---|---|
err | std.log.err | Erros que impedem operação |
warn | std.log.warn | Situações anormais mas recuperáveis |
info | std.log.info | Informações operacionais |
debug | std.log.debug | Detalhes 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.