O pacote log/slog foi introduzido no Go 1.21 como a solução oficial para logging estruturado na biblioteca padrão. Antes dele, o pacote log era limitado a mensagens de texto simples, forçando a maioria dos projetos a depender de bibliotecas externas como zerolog ou zap. Com slog, você tem logging estruturado nativo, de alta performance, pronto para produção.
Por que Logging Estruturado?
Logs tradicionais em texto puro funcionam para debugging local, mas em ambientes de produção com centenas de serviços, você precisa de logs que sejam pesquisáveis, filtráveis e agregáveis. Logging estruturado emite cada entrada como um conjunto de pares chave-valor, facilitando a ingestão por ferramentas como Elasticsearch, Grafana Loki ou Datadog.
// Log tradicional — difícil de parsear automaticamente
log.Printf("usuário %s fez login em %s", userID, time.Now())
// Log estruturado com slog — facilmente parseável
slog.Info("usuário fez login",
slog.String("user_id", userID),
slog.Time("timestamp", time.Now()),
)
A saída estruturada permite que você filtre logs por user_id, agrupe por nível de severidade e correlacione eventos entre microsserviços — algo essencial em arquiteturas distribuídas como as que discutimos no guia de APIs REST em Go.
Primeiros Passos com slog
O pacote slog vem com dois handlers prontos: TextHandler para saída legível e JSONHandler para saída estruturada em JSON.
package main
import (
"log/slog"
"os"
)
func main() {
// Handler de texto (bom para desenvolvimento)
textHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
logger := slog.New(textHandler)
logger.Info("servidor iniciando", slog.Int("porta", 8080))
// Saída: time=2026-03-25T10:00:00.000-03:00 level=INFO msg="servidor iniciando" porta=8080
// Handler JSON (ideal para produção)
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
})
prodLogger := slog.New(jsonHandler)
prodLogger.Info("servidor iniciando", slog.Int("porta", 8080))
// Saída: {"time":"2026-03-25T10:00:00.000-03:00","level":"INFO","msg":"servidor iniciando","porta":8080}
}
Para definir um logger como padrão global, use slog.SetDefault(logger). Depois disso, chamadas diretas como slog.Info() usarão o handler que você configurou.
Níveis de Log
O slog define quatro níveis padrão, cada um com um valor numérico:
| Nível | Valor | Uso |
|---|---|---|
| Debug | -4 | Informações detalhadas para debugging |
| Info | 0 | Eventos normais de operação |
| Warn | 4 | Situações inesperadas mas recuperáveis |
| Error | 8 | Erros que precisam de atenção |
slog.Debug("query executada", slog.String("sql", query), slog.Duration("duracao", elapsed))
slog.Info("requisição processada", slog.Int("status", 200))
slog.Warn("cache miss", slog.String("chave", key))
slog.Error("falha ao conectar", slog.String("erro", err.Error()))
Você pode criar níveis customizados entre os padrões. Por exemplo, slog.Level(2) ficaria entre Info e Warn — útil para diferenciar severidades em sistemas de tratamento de erros mais complexos.
Atributos e Grupos
Atributos tipados garantem que seus logs sejam consistentes. O slog oferece construtores como slog.String, slog.Int, slog.Bool, slog.Duration, slog.Any e outros:
slog.Info("pagamento processado",
slog.String("transacao_id", txID),
slog.Float64("valor", 199.90),
slog.Bool("recorrente", true),
slog.Duration("latencia", 45*time.Millisecond),
slog.Any("metadados", metadata),
)
Para organizar atributos relacionados, use grupos:
slog.Info("requisição recebida",
slog.Group("http",
slog.String("metodo", "POST"),
slog.String("path", "/api/usuarios"),
slog.Int("status", 201),
),
slog.Group("cliente",
slog.String("ip", "192.168.1.100"),
slog.String("user_agent", "Mozilla/5.0"),
),
)
// JSON: {"http":{"metodo":"POST","path":"/api/usuarios","status":201},"cliente":{"ip":"192.168.1.100",...}}
Loggers com Contexto
Uma das funcionalidades mais poderosas do slog é a integração com context.Context. Isso permite propagar informações como request ID e trace ID automaticamente por toda a cadeia de chamadas — algo fundamental quando você trabalha com context em Go.
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.New().String()
// Cria logger com atributos fixos
logger := slog.Default().With(
slog.String("request_id", requestID),
slog.String("metodo", r.Method),
slog.String("path", r.URL.Path),
)
// Injeta no context
ctx := context.WithValue(r.Context(), loggerKey, logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func handler(w http.ResponseWriter, r *http.Request) {
logger := r.Context().Value(loggerKey).(*slog.Logger)
logger.Info("processando requisição")
// Todos os logs incluem request_id, metodo e path automaticamente
}
O método With() cria um novo logger que inclui os atributos em todas as mensagens subsequentes, sem necessidade de repeti-los.
A Interface LogValuer
Quando você precisa logar structs complexas, implemente a interface LogValuer para controlar como o objeto aparece nos logs:
type Usuario struct {
ID string
Nome string
Email string
Senha string // nunca deve aparecer nos logs
}
func (u Usuario) LogValue() slog.Value {
return slog.GroupValue(
slog.String("id", u.ID),
slog.String("nome", u.Nome),
// Email e Senha omitidos intencionalmente
)
}
// Uso
slog.Info("usuário criado", slog.Any("usuario", usuario))
// Saída segura: sem senha ou dados sensíveis
Criando um Handler Customizado
Para cenários avançados, você pode criar seu próprio handler implementando a interface slog.Handler:
type ColorHandler struct {
handler slog.Handler
writer io.Writer
}
func (h *ColorHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.handler.Enabled(ctx, level)
}
func (h *ColorHandler) Handle(ctx context.Context, r slog.Record) error {
var cor string
switch {
case r.Level >= slog.LevelError:
cor = "\033[31m" // vermelho
case r.Level >= slog.LevelWarn:
cor = "\033[33m" // amarelo
default:
cor = "\033[0m" // padrão
}
fmt.Fprintf(h.writer, "%s[%s]\033[0m %s\n", cor, r.Level, r.Message)
return nil
}
func (h *ColorHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &ColorHandler{handler: h.handler.WithAttrs(attrs), writer: h.writer}
}
func (h *ColorHandler) WithGroup(name string) slog.Handler {
return &ColorHandler{handler: h.handler.WithGroup(name), writer: h.writer}
}
slog em HTTP Middleware
Na prática, slog brilha quando integrado ao middleware HTTP para logar cada requisição automaticamente:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
// Wrapper para capturar o status code
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
slog.Info("requisição completada",
slog.String("metodo", r.Method),
slog.String("path", r.URL.Path),
slog.Int("status", rw.statusCode),
slog.Duration("duracao", time.Since(inicio)),
slog.String("ip", r.RemoteAddr),
)
})
}
Esse padrão se aplica diretamente quando você constrói APIs REST — veja nosso guia sobre APIs REST em Go para um exemplo completo.
Performance: slog vs zerolog vs zap
O slog foi projetado para ser rápido, mas como se compara com as alternativas consolidadas?
| Biblioteca | Alocações por log | Latência |
|---|---|---|
| slog (JSON) | 0-1 | ~200ns |
| zerolog | 0 | ~150ns |
| zap | 0-1 | ~180ns |
| log (padrão) | 1-2 | ~300ns |
O slog é competitivo com zap e ligeiramente mais lento que zerolog, mas a vantagem de ser parte da biblioteca padrão compensa na maioria dos cenários. Não há dependências externas para gerenciar e a API é estável.
Para otimizar a performance, use atributos tipados (slog.String, slog.Int) em vez de slog.Any, e configure o nível mínimo de log corretamente para evitar processamento desnecessário em produção.
Migrando de log para slog
Se você já usa o pacote log padrão, a migração é gradual:
// 1. Configure slog como backend do log padrão
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
// 2. Chamadas ao log padrão agora passam pelo slog
log.Println("isso agora é processado pelo slog")
// 3. Gradualmente substitua por chamadas diretas ao slog
slog.Info("versão melhorada", slog.String("contexto", "migração"))
Quando Usar slog
Use slog quando:
- Você quer logging estruturado sem dependências externas
- Precisa de integração nativa com context para rastreamento de requisições
- Está começando um projeto novo em Go 1.21+
- Quer uma API estável mantida pelo time do Go
Considere zerolog ou zap quando:
- Precisa de zero alocações absolutas (hot path extremo)
- Já tem uma base de código grande usando essas bibliotecas
- Precisa de funcionalidades específicas como sampling ou sink rotation
Próximos Passos
O slog transforma a forma como fazemos observabilidade em Go. Combine com context para rastreamento distribuído, aplique em middleware HTTP para monitorar suas APIs, e use tratamento de erros estruturado para logs de erro mais informativos.
Se você também trabalha com Python, confira o guia de logging estruturado em Python para comparar abordagens. No ecossistema Rust, o crate tracing oferece funcionalidades semelhantes — veja mais em Rust Brasil.
Se você está explorando outros recursos modernos do Go, veja também como iteradores com range over func trazem novos padrões para processar dados — inclusive em pipelines de log customizados.
Para aprofundar nos fundamentos de concorrência em Go, que impacta diretamente como loggers devem ser thread-safe, confira nosso guia dedicado.