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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR