Menu

slog e log em Golang: logs estruturados em Go

Como fazer logs em Go: o pacote clássico log, com as suas flags e o log.Fatal, e o log/slog (Go 1.21) para logs estruturados com níveis, atributos chave-valor, handlers de texto e JSON e loggers que carregam contexto com With.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

slog em um exemplo

O log/slog (Go 1.21) escreve registros estruturados: uma mensagem, um nível e atributos chave-valor.

Cada linha sai como time=... level=INFO msg="user logged in" user=ada attempts=1. Como cada valor é um campo separado, um coletor de logs (Loki, Elasticsearch, CloudWatch, Datadog) consegue filtrar por user=ada ou level=ERROR sem expressões regulares sobre texto livre.

Os exemplos desta página escrevem em os.Stdout para que a saída apareça em ordem. Em um serviço real, os logs normalmente vão para os.Stderr, que também é onde o logger padrão escreve.

Níveis

NívelValorUse para
slog.LevelDebug-4detalhes para desenvolvedores, desligado em produção
slog.LevelInfo0eventos normais: inicializou, requisição atendida, job concluído
slog.LevelWarn4algo inesperado que o programa tratou
slog.LevelError8uma operação falhou

O handler descarta registros abaixo do seu nível mínimo, e o mínimo padrão é Info. É por isso que slog.Debug(...) não imprime nada até você configurar um handler com Level: slog.LevelDebug. Os intervalos entre os valores deixam espaço para níveis personalizados como slog.Level(2).

Para mudar o nível em tempo de execução (a partir de uma flag, de um endpoint de administração ou de um sinal), coloque um slog.LevelVar nas opções e chame Set nele depois:

var level slog.LevelVar // zero value: Info
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: &level}))
level.Set(slog.LevelDebug) // from now on, debug records are written

Texto ou JSON

slog.NewTextHandler escreve pares key=value, fáceis de ler em um terminal. slog.NewJSONHandler escreve um objeto JSON por linha, o formato que a maioria dos pipelines de logs espera. As chamadas de log continuam as mesmas; só o handler muda.

Os valores mantêm os seus tipos: status é um número na saída JSON e retry um booleano, um time.Duration aparece como 42ms no texto e como nanossegundos no JSON, e um error imprime a sua mensagem. O ReplaceAttr é o gancho para reescrever ou remover atributos, usado aqui para descartar o horário e, na prática, para renomear chaves (msg para message) ou mascarar valores.

Atributos

A forma com tipagem solta alterna chaves e valores: "user", "ada", "attempts", 3. Ela é curta e tem um único modo de falha: um número ímpar de argumentos. O valor que sobra é registrado sob a chave !BADKEY. O go vet pega isso:

./main.go:14:2: call to slog.Info missing a final value

Para segurança de tipos e um pouco menos de alocação, use os construtores de atributos, e LogAttrs ao registrar logs em um caminho quente:

logger.Info("order placed",
	slog.Int("order_id", 1017),
	slog.String("currency", "EUR"),
	slog.Float64("total", 59.90),
	slog.Duration("took", elapsed),
)

logger.LogAttrs(ctx, slog.LevelInfo, "order placed", slog.Int("order_id", 1017))

Use uma única convenção de nomes para as chaves em toda a base de código (user_id em todo lugar, e não userID em um pacote e uid em outro). As consultas no seu sistema de logs dependem disso.

With: loggers que carregam contexto

logger.With(attrs...) devolve um novo logger que acrescenta esses atributos a todo registro. Crie um por requisição ou por job, e todas as linhas que ele escrever podem ser ligadas entre si:

Toda linha carrega service, version, request_id e user sem repeti-los em cada chamada. slog.Group aninha atributos, que o handler JSON escreve como um objeto aninhado ("payment":{"amount":25,"currency":"USD"}) e o handler de texto como chaves com ponto (payment.amount=25). logger.WithGroup("db") coloca todos os atributos seguintes daquele logger dentro de um grupo.

Passe o logger da requisição adiante como parâmetro ou campo de struct. Guardá-lo em um context.Context é possível, mas esconde a dependência; os métodos InfoContext(ctx, ...) do slog passam o context para o handler, que um handler personalizado pode usar para extrair trace IDs.

Escondendo segredos com LogValuer

Um tipo pode controlar como é registrado implementando slog.LogValuer. Isso mantém senhas e tokens fora dos logs, não importa quem registre o valor:

User registra só o ID e o email, e um Token registrado sozinho imprime REDACTED. O handler só chama LogValue quando o registro é de fato escrito, então isso também funciona para valores caros de calcular.

O pacote clássico log

O log é anterior ao slog e ainda serve bem para programas pequenos e scripts. Ele escreve linhas no erro padrão com um prefixo de data e hora:

FlagAcrescenta
log.LstdFlags (o padrão)data e hora 2009/11/10 23:00:00
log.Lmicrosecondsmicrossegundos no horário
log.LUTChorário em UTC
log.Lshortfile / log.Llongfilemain.go:14 / o caminho completo
log.Lmsgprefixcoloca o prefixo antes da mensagem em vez de no início da linha

Três funções saem do programa ou causam panic, e a diferença importa:

  • log.Fatal, log.Fatalf e log.Fatalln imprimem e depois chamam os.Exit(1). As chamadas adiadas não executam. Use-as no main para falhas na inicialização, nunca em código de biblioteca ou em handlers de requisição.
  • log.Panic e as suas variantes imprimem e depois causam panic, então as chamadas adiadas executam e o panic pode ser recuperado.
  • Todas as outras funções apenas escrevem uma linha.

Para registrar em um arquivo, abra-o e passe-o para log.New ou log.SetOutput; io.MultiWriter(os.Stderr, f) escreve nos dois.

log e slog juntos

slog.SetDefault(logger) torna logger o padrão das funções de nível superior slog.Info e também faz a saída do pacote log passar por ele. As chamadas log.Printf existentes no seu código ou nas dependências passam a sair como registros estruturados de nível Info:

slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
log.Printf("legacy message") // {"time":"...","level":"INFO","msg":"legacy message"}

Antes do SetDefault, o logger padrão do slog escreve por meio do pacote log, e é por isso que um slog.Info("hi") sozinho imprime 2026/09/23 14:30:00 INFO hi.

Regras práticas

  • Registre ou devolva um erro, não os dois. Uma função que registra um erro e o devolve faz a mesma falha ser registrada em todos os níveis da pilha de chamadas. Devolva os erros para cima com contexto e registre uma vez, onde eles são tratados.
  • Coloque os dados variáveis nos atributos, não na mensagem. logger.Info("user created", "user_id", id) agrupa bem em um sistema de logs; logger.Info(fmt.Sprintf("user %d created", id)) cria uma mensagem diferente para cada usuário.
  • Nunca registre segredos nem corpos completos de requisição. Use LogValuer ou ReplaceAttr para mascarar.
  • Use JSON em produção e texto em desenvolvimento. Escolha o handler na inicialização a partir de uma flag ou de uma variável de ambiente.
  • Escolha os níveis com cuidado. Se tudo é registrado como Error, os alertas de erro viram ruído.

Perguntas frequentes

O que é o slog em Go?

O log/slog é o pacote de logs estruturados acrescentado à biblioteca padrão no Go 1.21. Em vez de strings formatadas, cada registro tem uma mensagem, um nível (Debug, Info, Warn, Error) e atributos chave-valor, e um handler o escreve como texto key=value ou como JSON: slog.Info("login", "user", "ada", "attempts", 3).

Como ativar os logs de debug no slog?

O nível mínimo padrão é Info, então slog.Debug não imprime nada. Crie um handler com um nível mais baixo e torne-o o padrão: slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). Use um slog.LevelVar em vez de uma constante se quiser mudar o nível com o programa rodando.

Qual a diferença entre log e slog em Go?

O log escreve linhas livres com um prefixo opcional de data e hora e não tem níveis. O slog escreve registros com níveis e atributos chave-valor tipados que os coletores de logs conseguem interpretar e filtrar. Os dois estão na biblioteca padrão; slog.SetDefault também redireciona a saída do pacote log pelo handler do slog.

O log.Fatal executa as funções adiadas?

Não. log.Fatal e log.Fatalf imprimem a mensagem e chamam os.Exit(1), que pula todas as chamadas adiadas. Use-os só no main ou em código de inicialização em que não há nada para limpar. log.Panic causa panic no lugar, então as chamadas adiadas executam.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR