Capítulo 25 19 min de leitura

Logging

stdlib::log é um logger estruturado com níveis. Seis níveis (trace < debug < info < warn < error < fatal) fluem por um Logger global para um ou mais sinks (stdout/stderr, arquivos, arquivos rotativos, syslog ou um callback), renderizados como texto simples, logfmt key=value, ou um objeto JSON por linha. Cada nível tem uma macro no estilo println! (info!, warn!, …) além de uma função livre (info, warn, …) roteada pelo logger global do processo.

Import

import stdlib::log::*;

Este único glob traz tanto as funções/tipos de runtime quanto as proc-macros (info!, warn!, @trace, @logged). O módulo de macros (stdlib::log_macros) é re-importado de forma transparente — você nunca o importa diretamente.

Superfície pública em resumo

Grupo Itens
Constantes de nível LOG_TRACE LOG_DEBUG LOG_INFO LOG_WARN LOG_ERROR LOG_FATAL LOG_OFF
Constantes de formato LOG_FMT_TEXT LOG_FMT_KV LOG_FMT_JSON
Constantes de timestamp LOG_TS_NONE LOG_TS_ISO LOG_TS_UNIX LOG_TS_UNIX_MS LOG_TS_RELATIVE
Constantes de sink SINK_STDOUT SINK_STDERR SINK_FILE SINK_ROTATING SINK_SYSLOG SINK_CALLBACK
Structs Logger LoggerBuilder LogSink LogLine LogField LogEntry
Funções livres (global) trace debug info warn error fatal at log_set_level log_set_format
Helpers de temporização trace_now_ns trace_since_str
Estáticos de Logger default global set_global
Métodos de Logger with logv logv_at trace debug info warn error fatal
LoggerBuilder new level format timestamps add_sink async_writer build
Construtores de LogSink stdout stderr file rotating_file syslog callback
Métodos de LogSink min_level
LogLine trace debug info warn error fatal kv emit emit_to
Macros trace! debug! info! warn! error! fatal!
Atributos @trace @trace(level) @logged @logged(level)

Níveis, formatos, timestamps e tipos de sink

Todos os valores de configuração são constantes i32 simples. Números de nível menores são mais verbosos; um logger emite uma entrada apenas quando o número do nível é >= logger.level.

Constantes de nível

Constante Valor Significado
LOG_TRACE 0 Mais verboso; marcadores de fluxo.
LOG_DEBUG 1 Diagnósticos de debug.
LOG_INFO 2 Mensagens operacionais normais.
LOG_WARN 3 Problemas recuperáveis.
LOG_ERROR 4 Erros.
LOG_FATAL 5 Condições fatais.
LOG_OFF 99 Definir como nível do logger para silenciar tudo.
pub const LOG_TRACE: i32 = 0;
pub const LOG_DEBUG: i32 = 1;
pub const LOG_INFO:  i32 = 2;
pub const LOG_WARN:  i32 = 3;
pub const LOG_ERROR: i32 = 4;
pub const LOG_FATAL: i32 = 5;
pub const LOG_OFF:   i32 = 99;

Constantes de formato de saída

Constante Valor Saída
LOG_FMT_TEXT 0 Legível por humanos, com suporte a cores (INFO server started k=v).
LOG_FMT_KV 1 logfmt: level=INFO msg="…" k=v.
LOG_FMT_JSON 2 Um objeto JSON por linha.

A mesma entrada INFO LogLine::info("server started").kv("port", "8080").emit() é renderizada de forma diferente por formato (timestamps omitidos por clareza):

output
# LOG_FMT_TEXT
INFO server started port=8080
# LOG_FMT_KV
level=INFO msg="server started" port=8080
# LOG_FMT_JSON
{"level":"INFO","msg":"server started","port":"8080"}

Em KV e JSON, valores que contêm espaços, =, aspas, tabulações ou quebras de linha são colocados entre aspas/escapados (msg="slow query", \n\\n). O formato texto emite a mensagem e os campos sem escapamento. Todo valor de campo é uma `string` — não existe tipo de campo numérico ou booleano; formate seus números antes de passá-los.

Constantes de timestamp

Constante Valor Renderizado como
LOG_TS_NONE 0 (sem timestamp)
LOG_TS_ISO 1 2026-05-23T15:30:45Z
LOG_TS_UNIX 2 1716480000 (segundos)
LOG_TS_UNIX_MS 3 1716480000123 (milissegundos)
LOG_TS_RELATIVE 4 0.001s decorrido desde o início do logger (renderizado como <ms>ms)

Constantes de tipo de sink

Essas constantes marcam um LogSink.kind; normalmente você cria sinks com os construtores LogSink::* em vez de definir kind diretamente.

Constante Valor Sink
SINK_STDOUT 0 Saída padrão.
SINK_STDERR 1 Saída de erro padrão.
SINK_FILE 2 Adiciona ao final de um arquivo.
SINK_ROTATING 3 Arquivo rotativo com tamanho máximo.
SINK_SYSLOG 4 Log do sistema (no-op no Windows).
SINK_CALLBACK 5 fn(string) fornecida pelo usuário.

Início rápido: o logger global

A forma mais rápida de registrar logs. As funções livres e as macros ! roteiam todas pelo Logger::global() — um singleton inicializado preguiçosamente que usa INFO+ no stdout no formato texto por padrão.

Macros ! (estilo println!)

info!(fmt, args…) formata exatamente como format!/println! e registra o resultado no nível correspondente. A macro expande para stdlib::log::info(format!(fmt, args…)), portanto usa o caminho completo do módulo e resolve independentemente de como você importou. Sem string de formato, um {} é sintetizado por argumento.

Macro Nível
trace!(fmt, …) TRACE
debug!(fmt, …) DEBUG
info!(fmt, …) INFO
warn!(fmt, …) WARN
error!(fmt, …) ERROR
fatal!(fmt, …) FATAL
import stdlib::log::*;

fn main() -> i32 {
    log_set_level(LOG_DEBUG);
    log_set_format(LOG_FMT_TEXT);

    info!("server started");
    warn!("queue at {}% capacity", 90);
    error!("request failed: {}", "timeout");
    debug!("cache size {}", 1024);
    trace!("entering loop");
    return 0;
}

Funções livres

Funções simples que roteiam pelo logger global sem formatação. Com um import qualificado, aparecem como log::info(...).

Função Assinatura Registra em
trace pub fn trace(msg: string) TRACE
debug pub fn debug(msg: string) DEBUG
info pub fn info(msg: string) INFO
warn pub fn warn(msg: string) WARN
error pub fn error(msg: string) ERROR
fatal pub fn fatal(msg: string) FATAL
at pub fn at(lvl: i32, msg: string) lvl
import stdlib::log::*;

fn main() -> i32 {
    info("bare fn info");
    error("bare fn error");
    at(LOG_INFO, "ready");        // registra em um nível explícito
    return 0;
}

Configuração global

Função Assinatura Efeito
log_set_level pub fn log_set_level(lvl: i32) Define o nível mínimo do logger global.
log_set_format pub fn log_set_format(fmt: i32) Define o formato de saída do logger global.

Essas são conveniências sobre Logger::global().level = lvl / .format = fmt.

Logger

O logger armazena um nível, formato, estilo de timestamp, um tempo de início (para timestamps relativos), seus sinks e quaisquer campos vinculados herdados por loggers filhos.

pub struct Logger {
    pub level: i32,
    pub format: i32,
    pub ts_format: i32,
    pub time_start_ns: i64,
    pub sinks: *Vector<*LogSink>,
    pub bound_fields: *Vector<LogField>,
}

Construtores e o singleton global

Função Assinatura Descrição
default pub fn default() -> *Logger INFO+ para stdout, com cor quando stdout é um TTY, timestamps ISO.
global pub fn global() -> *Logger O singleton de todo o processo (criado preguiçosamente no primeiro uso).
set_global pub fn set_global(l: *Logger) Substitui o logger global.

Logger::global() é criado no primeiro uso chamando Logger::default(). As funções livres (info, warn, …), as macros !, at, log_set_level e log_set_format roteiam todas por ele. Para tornar um logger personalizado o padrão para esses pontos de entrada, construa-o e passe-o para Logger::set_global.

import stdlib::log::*;

fn main() -> i32 {
    let l: *Logger = Logger::default();   // INFO+, stdout, ts ISO, cor no TTY
    l.info("from default logger");

    Logger::set_global(l);                // instala como logger do processo
    log_set_level(LOG_DEBUG);             // ajusta o logger agora global
    log_set_format(LOG_FMT_KV);

    Logger::global().debug("now visible at DEBUG");
    info("global free fn");               // roteia pelo mesmo logger
    return 0;
}

Métodos de chamada direta

Cada método emite uma mensagem no seu nível fixo. Eles não capturam file:line.

pub fn trace(self: *Logger, msg: string)
pub fn debug(self: *Logger, msg: string)
pub fn info (self: *Logger, msg: string)
pub fn warn (self: *Logger, msg: string)
pub fn error(self: *Logger, msg: string)
pub fn fatal(self: *Logger, msg: string)

Loggers filhos — with

with retorna um logger filho que herda sinks/nível/formato/timestamps e adiciona um campo vinculado. Toda entrada do filho carrega esse campo. Encadeie with para vincular vários.

pub fn with(self: *Logger, key: string, value: string) -> *Logger

Emissão de baixo nível — logv / logv_at

extras é uma lista plana de chave, valor, chave, valor, …. logv_at é igual, mas com localização no código-fonte fornecida pelo chamador, que a saída renderizada inclui como (file:line).

pub fn logv(self: *Logger, lvl: i32, msg: string, extras: *Vector<string>)
pub fn logv_at(self: *Logger, lvl: i32, msg: string, file: string, line: i32,
               extras: *Vector<string>)
import stdlib::log::*;

fn main() -> i32 {
    let l: *Logger = LoggerBuilder::new()
        .level(LOG_DEBUG)
        .add_sink(LogSink::stdout(true))
        .add_sink(LogSink::file("/var/log/app.log"))
        .format(LOG_FMT_JSON)
        .timestamps(LOG_TS_ISO)
        .build();

    l.info("started");

    // Logger filho: req_id + user anexados a cada linha.
    let req_log: *Logger = l.with("req_id", "abc123").with("user", "alice");
    req_log.info("handled");

    // Baixo nível: extras chave/valor planos + localização explícita no código.
    let extras: *Vector<string> = Vector::new();
    extras.push("port");
    extras.push("8080");
    l.logv(LOG_INFO, "with extras", extras);
    l.logv_at(LOG_INFO, "with loc", "main.glide", 42, extras);

    Logger::set_global(l);              // torna-o o global
    Logger::global().info("via global");
    return 0;
}

No formato KV, as mesmas chamadas logv / logv_at são renderizadas com os extras como pares chave=valor à direita e a localização como file=… line=…:

import stdlib::log::*;

fn main() -> i32 {
    let l: *Logger = LoggerBuilder::new().format(LOG_FMT_KV).build();

    let extras: *Vector<string> = Vector::new();
    extras.push("port");  extras.push("8080");
    extras.push("tls");   extras.push("on");
    l.logv(LOG_INFO, "listening", extras);
    // level=INFO msg=listening port=8080 tls=on

    l.logv_at(LOG_ERROR, "boom", "server.glide", 128, extras);
    // level=ERROR msg=boom file=server.glide line=128 port=8080 tls=on
    return 0;
}

LoggerBuilder

Builder fluente para um Logger personalizado. Cada setter retorna self para encadeamento; build() produz o *Logger. Se nenhum sink for adicionado, build() usa por padrão um sink stdout colorido.

Método Assinatura Descrição
new pub fn new() -> *LoggerBuilder Inicia com os padrões (INFO, texto, timestamps ISO, sem sinks).
level pub fn level(self: *LoggerBuilder, lvl: i32) -> *LoggerBuilder Nível mínimo.
format pub fn format(self: *LoggerBuilder, fmt: i32) -> *LoggerBuilder Formato de saída.
timestamps pub fn timestamps(self: *LoggerBuilder, ts: i32) -> *LoggerBuilder Estilo de timestamp.
add_sink pub fn add_sink(self: *LoggerBuilder, s: *LogSink) -> *LoggerBuilder Adiciona um sink (chame repetidamente).
async_writer pub fn async_writer(self: *LoggerBuilder, on: bool) -> *LoggerBuilder Solicita escritas não-bloqueantes.
build pub fn build(self: *LoggerBuilder) -> *Logger Finaliza.

Escolhendo formato e timestamps

Cada builder produz um logger independente — útil para rotear diferentes subsistemas para diferentes formatos. O estilo de timestamp é aplicado uniformemente ao formato selecionado.

import stdlib::log::*;

fn main() -> i32 {
    let kv: *Logger = LoggerBuilder::new()
        .format(LOG_FMT_KV)
        .timestamps(LOG_TS_UNIX)
        .add_sink(LogSink::stdout(false))
        .build();
    LogLine::warn("slow query").kv("ms", "320").kv("table", "users").emit_to(kv);

    let js: *Logger = LoggerBuilder::new()
        .format(LOG_FMT_JSON)
        .timestamps(LOG_TS_ISO)
        .build();
    js.with("svc", "api").info("ready");

    let rel: *Logger = LoggerBuilder::new()
        .timestamps(LOG_TS_RELATIVE)        // <ms>ms desde o início do logger
        .format(LOG_FMT_TEXT)
        .build();
    rel.info("relative timestamp");

    let none: *Logger = LoggerBuilder::new().timestamps(LOG_TS_NONE).build();
    none.info("no timestamp");
    return 0;
}

LogSink

Um sink é um destino de saída com seu próprio nível mínimo. Construa sinks com os construtores estáticos, opcionalmente restrinja-os com min_level e anexe-os via LoggerBuilder::add_sink.

pub struct LogSink {
    pub kind: i32,
    pub level: i32,           // filtro por sink (LOG_TRACE por padrão)
    pub color: bool,          // ANSI para formato texto
    pub fd: i32,              // fd bruto para stdout/stderr/arquivo
    pub path: string,
    pub max_size: i32,        // rotativo: bytes por arquivo
    pub max_files: i32,       // rotativo: mantém N arquivos rotacionados
    pub cur_size: i32,        // rotativo: tamanho atual no arquivo ativo
    pub cb: fn(string),       // SINK_CALLBACK
}

Construtores

Construtor Assinatura Descrição
stdout pub fn stdout(color: bool) -> *LogSink stdout; cor aplicada somente quando o fd 1 é um TTY.
stderr pub fn stderr(color: bool) -> *LogSink stderr; cor aplicada somente quando o fd 2 é um TTY.
file pub fn file(path: string) -> *LogSink Adiciona ao final de path (cria o arquivo).
rotating_file pub fn rotating_file(path: string, max_size: i32, max_files: i32) -> *LogSink Rotaciona para .1.N quando max_size bytes é atingido, mantendo max_files arquivos rotacionados.
syslog pub fn syslog(ident: string) -> *LogSink syslog POSIX com o ident fornecido (no-op no Windows).
callback pub fn callback(f: fn(string)) -> *LogSink Invoca f com cada linha completamente renderizada.

Métodos

Método Assinatura Descrição
min_level pub fn min_level(self: *LogSink, lvl: i32) -> *LogSink Rejeita entradas abaixo de lvl neste sink. Encadeável.
import stdlib::log::*;

fn audit(line: string) {
    // encaminha cada linha renderizada para algum lugar (ex.: uma trilha de auditoria)
    return;
}

fn main() -> i32 {
    let l: *Logger = LoggerBuilder::new()
        .add_sink(LogSink::stdout(false).min_level(LOG_WARN))   // warnings+ no stdout
        .add_sink(LogSink::file("/var/log/app.log"))            // tudo para o arquivo
        .add_sink(LogSink::rotating_file("/var/log/app.log", 10485760, 5))
        .add_sink(LogSink::syslog("myapp"))
        .add_sink(LogSink::callback(audit))
        .build();
    l.info("multi-sink");
    return 0;
}

Uma entrada precisa passar por dois portões: o level do logger (decide se a entrada é construída) e depois o level próprio de cada sink (decide se aquele sink a escreve). Um sink usa LOG_TRACE por padrão, portanto aceita tudo que o logger emite.

import stdlib::log::*;

fn main() -> i32 {
    let l: *Logger = LoggerBuilder::new()
        .level(LOG_DEBUG)
        .add_sink(LogSink::stdout(false).min_level(LOG_WARN))
        .add_sink(LogSink::file("/tmp/app.log"))   // herda o piso LOG_TRACE
        .build();

    l.debug("only in file");      // abaixo do piso do console -> somente no arquivo
    l.warn("file + console");     // passa pelos pisos de ambos os sinks
    return 0;
}

Sinks de callback e LogField

Um sink SINK_CALLBACK entrega à sua fn(string) cada linha completamente renderizada (já formatada e com newline ao final). Use-o para integrar a uma trilha de auditoria, um ring buffer, um contador de métricas ou um harness de testes. A struct LogField é o portador público {key, value} que os renderizadores iteram; tanto key quanto value são string.

import stdlib::log::*;

fn render(line: string) {
    // Encaminha a linha renderizada para algum lugar: uma trilha de auditoria,
    // um ring buffer, um contador de métricas, etc. Aqui apenas inspecionamos seu tamanho.
    let _n: i32 = line.len();
    return;
}

fn main() -> i32 {
    let f: LogField = LogField { key: "user", value: "alice" };
    println!("field {}={}", f.key, f.value);

    let l: *Logger = LoggerBuilder::new()
        .format(LOG_FMT_KV)
        .timestamps(LOG_TS_NONE)
        .add_sink(LogSink::callback(render))
        .build();
    LogLine::info("login").kv("user", f.value).emit_to(l);
    return 0;
}

LogLine — campos estruturados fluentes

Quando você quer campos chave=valor em uma única linha sem vinculá-los a um logger filho, construa um LogLine, encadeie kv e então emit (global) ou emit_to (um logger específico).

Método Assinatura Descrição
trace pub fn trace(msg: string) -> *LogLine Inicia uma linha TRACE.
debug pub fn debug(msg: string) -> *LogLine Inicia uma linha DEBUG.
info pub fn info(msg: string) -> *LogLine Inicia uma linha INFO.
warn pub fn warn(msg: string) -> *LogLine Inicia uma linha WARN.
error pub fn error(msg: string) -> *LogLine Inicia uma linha ERROR.
fatal pub fn fatal(msg: string) -> *LogLine Inicia uma linha FATAL.
kv pub fn kv(self: *LogLine, k: string, v: string) -> *LogLine Anexa um campo. Encadeável.
emit pub fn emit(self: *LogLine) Envia pelo logger global.
emit_to pub fn emit_to(self: *LogLine, lg: *Logger) Envia pelo logger lg.
import stdlib::log::*;

fn main() -> i32 {
    LogLine::info("server started")
        .kv("port", "8080")
        .kv("workers", "4")
        .emit();

    let l: *Logger = LoggerBuilder::new().build();
    LogLine::warn("slow query").kv("ms", "320").emit_to(l);
    return 0;
}

LogField e LogEntry

Os dados carregados por um único registro de log. Você raramente os constrói diretamente — logv/LogLine/with os criam para você — mas os campos são públicos para renderizadores e callbacks.

pub struct LogField {
    pub key: string,
    pub value: string,
}

pub struct LogEntry {
    pub level: i32,
    pub msg: string,
    pub time_ns: i64,
    pub file: string,
    pub line: i32,
    pub fields: *Vector<LogField>,
}

Atributos de rastreamento de fluxo — @trace e @logged

Esses proc-atributos envolvem uma função para que ela registre logs na entrada e na saída. O primeiro argumento opcional escolhe o nível (trace, debug, info, warn, error, fatal); o padrão é debug.

Atributo Registra
@trace > name na entrada, < name na saída (marcadores de fluxo simples).
@trace(info) O mesmo, mas no nível indicado.
@logged Entrada com os valores dos argumentos (> name(4, 6)), saída com o valor de retorno e o tempo decorrido (< name -> 10 (340ns)).
@logged(info) O mesmo, no nível indicado.

Uma função sem retorno omite a parte -> {} na saída (< name (340ns)). O identificador de nível é a palavra simples info, não LOG_INFO. Ambos os atributos roteiam suas linhas de entrada/saída pelo logger global via a função livre correspondente, portanto as linhas obedecem ao level global.

import stdlib::log::*;

@trace
fn step() { return; }                          // registra `> step` / `< step`

@trace(info)
fn ping() -> i32 { return 1; }                 // entrada/saída em INFO

@logged
fn add(a: i32, b: i32) -> i32 { return a + b; } // registra args, retorno, duração

fn main() -> i32 {
    log_set_level(LOG_TRACE);
    step();
    let p: i32 = ping();
    let s: i32 = add(4, 6);

    let t0: i64 = trace_now_ns();
    let dur: string = trace_since_str(t0);
    info!("p={} s={} dur={}", p, s, dur);
    return 0;
}

Helpers de temporização

@logged é respaldado por dois helpers públicos que você também pode chamar diretamente para medir um trecho de código.

Função Assinatura Descrição
trace_now_ns pub fn trace_now_ns() -> i64 Marca monotônica em nanossegundos.
trace_since_str pub fn trace_since_str(start_ns: i64) -> string Tempo decorrido desde uma marca, com escala automática (340ns / 12.3us / 4.5ms / 1.2s).
import stdlib::log::*;

fn main() -> i32 {
    let t0: i64 = trace_now_ns();
    // ... trabalho ...
    let elapsed: string = trace_since_str(t0);
    info!("done in {}", elapsed);
    return 0;
}

Ressalvas

  • Sinks que tocam recursos reais (LogSink::file, rotating_file, syslog) abrem arquivos / conectam ao syslog no momento da construção; os exemplos acima os constroem e passam em check, mas não são executados contra destinos reais. syslog é um no-op no Windows.
  • async_writer(true) é atualmente um no-op compatível com versões futuras (as escritas permanecem síncronas).
  • Todos os valores de campo são stringLogLine::kv, with e os extras de logv aceitam apenas string. Formate números/bools (format!("{}", n)) antes de passá-los.
  • Para silenciar um logger completamente, defina seu nível como LOG_OFF (99); nenhum número de nível chega até ele, portanto nada é emitido independentemente dos filtros por sink.
  • A cor é decidida uma única vez, a partir do flag color do primeiro sink, e se aplica somente a LOG_FMT_TEXT. LogSink::stdout(true) / stderr(true) condicionam adicionalmente a cor ao fd ser realmente um TTY, então a saída redirecionada permanece limpa.
  • rotating_file rotaciona quando o arquivo ativo atinge max_size bytes, deslocando base → .1 → … → .N e mantendo max_files arquivos rotacionados; ele rastreia o tamanho a partir do comprimento existente do arquivo na construção.

Veja também

  • stdlib::timenow_ns, time_now_ns, time_since_ns e o tipo Time sustentam os timestamps e as durações de @logged.
  • Os capítulos HTTP do livro usam este logger para registro de requisições; o HTTP em si está documentado lá, não aqui.