Logging Estruturado com slog: Performance, Contexto e Produção
Como fazer logs estruturados com o pacote slog da biblioteca padrão, com níveis, campos de contexto e saída pronta para produção.
Publicada em · 5 min de leitura
Logging Estruturado com slog: Performance, Contexto e Produção
Historicamente, o ecossistema Go sofreu com a fragmentação de bibliotecas de log. O pacote nativo log original oferecia apenas saída de texto simples sem níveis de severidade ou suporte a campos estruturados (log.Printf), forçando a comunidade a adotar soluções de terceiros como Logrus, Zap (Uber) ou Zerolog.
A partir do Go 1.21, a equipe oficial do Go introduziu o log/slog na Standard Library. O slog une a ergonomia de logs estruturados em JSON, alto desempenho com alocação mínima de memória e uma separação arquitetural estrita entre a API de chamada (Logger) e os mecanismos de formatação e transporte (Handler).
1. Arquitetura Interna do slog
O slog é desenhado sobre o princípio de desacoplamento: o código de negócio interage apenas com a struct slog.Logger, enquanto a formatação física (JSON, texto colorido ou envio direto para Datadog/Loki) é delegada à interface slog.Handler:
As Camadas do Pipeline:
- Chamada de Aplicação: O código invoca métodos como
logger.InfoContext(ctx, "mensagem", attrs...). - Construção do
slog.Record: Os dados são empacotados em uma struct leve alocada na pilha (stack) contendo timestamp, nível, mensagem e argumentos. - Interceptação no
slog.Handler: O Handler verifica se o nível está ativo (handler.Enabled(ctx, level)), aplica transformações de atributos e formata a saída. - Destino I/O: O payload formatado é gravado em um stream
io.Writer(geralmenteos.Stdout, coletado por agentes como FluentBit, Promtail ou Datadog Agent).
2. Handlers Nativos: Texto vs JSON
O slog disponibiliza dois handlers prontos para uso:
package main
import (
"log/slog"
"os"
)
func InitLogger(env string) *slog.Logger {
var handler slog.Handler
if env == "production" {
// Em produção: JSON puro para ingestão por ElasticSearch, Datadog ou Grafana Loki
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true, // Adiciona arquivo e linha (ex: "source":"main.go:42")
})
} else {
// Em desenvolvimento local: Texto legível para o terminal humano
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
}
logger := slog.New(handler)
// Define como logger global padrão (substitui o antigo log.Println)
slog.SetDefault(logger)
return logger
}
3. Performance e Atributos Fortemente Tipados
O slog permite passar pares soltos de chave-valor ou atributos fortemente tipados através da struct slog.Attr:
// 1. Sintaxe Flexível (Chave-Valor soltos):
// Confortável, porém força boxing de valores primitivos em interfaces any
slog.Info("pedido_criado", "order_id", "123", "total", 99.50)
// 2. Sintaxe Fortemente Tipada (Zero Allocations):
// Evita alocações no Heap ao empacotar primitivos diretamente
slog.Info("pedido_criado",
slog.String("order_id", "123"),
slog.Float64("total", 99.50),
slog.Int("items_count", 3),
)
4. Agrupamento de Atributos com slog.Group
Para evitar poluição na raiz do JSON e manter taxonomias limpas, agrupe atributos por domínio:
slog.Info("transacao_processada",
slog.Group("user",
slog.String("id", "usr_99"),
slog.String("email", "dev@empresa.com"),
),
slog.Group("payment",
slog.Float64("amount", 250.00),
slog.String("gateway", "stripe"),
),
)
JSON Resultante:
{
"time": "2026-10-20T10:00:00Z",
"level": "INFO",
"msg": "transacao_processada",
"user": {
"id": "usr_99",
"email": "dev@empresa.com"
},
"payment": {
"amount": 250.0,
"gateway": "stripe"
}
}
5. Mascaramento Automático de Dados Sensíveis com slog.LogValuer
Em conformidade com a LGPD e GDPR, senhas, tokens e dados pessoais nunca devem vazar para arquivos de log. O slog resolve isso de forma elegante através da interface slog.LogValuer:
type LogValuer interface {
LogValue() slog.Value
}
Implementação de Tipos Seguros
package domain
import "log/slog"
// Password protege contra vazamento acidental em logs
type Password string
func (p Password) LogValue() slog.Value {
return slog.StringValue("****** [REDACTED]")
}
// CreditCard mascara os dígitos centrais
type CreditCard string
func (c CreditCard) LogValue() slog.Value {
s := string(c)
if len(s) >= 16 {
return slog.StringValue(s[:4] + " **** **** " + s[12:])
}
return slog.StringValue("**** [MASKED]")
}
Ao registrar slog.Any("senha", user.Password) ou slog.Any("cartao", user.Card), o slog invoca automaticamente o método LogValue(), garantindo que o valor original em texto claro nunca chegue aos logs.
6. Mudança Dinâmica de Nível em Runtime com slog.LevelVar
Em produção, você quer que o nível padrão seja INFO para economizar I/O e custos de ingestão. Mas quando um incidente ocorre, você precisa ativar o DEBUG sem reiniciar os pods.
package main
import (
"log/slog"
"net/http"
"os"
)
var logLevel = &slog.LevelVar{} // Default inicial: LevelInfo
func SetupDynamicLogging() {
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: logLevel,
})
slog.SetDefault(slog.New(handler))
// Endpoint administrativo para alterar o nível em tempo real
http.HandleFunc("POST /admin/log-level", func(w http.ResponseWriter, r *http.Request) {
levelStr := r.URL.Query().Get("level")
switch levelStr {
case "debug":
logLevel.Set(slog.LevelDebug)
case "info":
logLevel.Set(slog.LevelInfo)
case "warn":
logLevel.Set(slog.LevelWarn)
case "error":
logLevel.Set(slog.LevelError)
default:
http.Error(w, "nível inválido (use debug, info, warn, error)", http.StatusBadRequest)
return
}
slog.Warn("Nível de log alterado dinamicamente", slog.String("novo_nivel", levelStr))
w.Write([]byte("Nível alterado com sucesso para " + levelStr))
})
}
7. Context-Aware Handler: Injetando Trace-ID Automaticamente
A maior fraqueza de logs tradicionais é a desconexão com o Tracing Distribuído e OpenTelemetry e com o Correlation ID criado em Middlewares em Go.
Para que qualquer chamada slog.InfoContext(ctx, "msg") automaticamente anexe o trace_id e o request_id sem que o desenvolvedor precise digitá-los manualmente em todos os logs, criamos um Handler Decorator:
Implementação do ContextHandler
package logging
import (
"context"
"log/slog"
)
type contextKey string
const (
TraceIDKey contextKey = "trace_id"
RequestIDKey contextKey = "request_id"
)
type ContextHandler struct {
slog.Handler
}
func NewContextHandler(h slog.Handler) *ContextHandler {
return &ContextHandler{Handler: h}
}
// Handle intercepta o registro antes de formatar e anexa dados do context
func (h *ContextHandler) Handle(ctx context.Context, r slog.Record) error {
if traceID, ok := ctx.Value(TraceIDKey).(string); ok && traceID != "" {
r.AddAttrs(slog.String("trace_id", traceID))
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok && reqID != "" {
r.AddAttrs(slog.String("request_id", reqID))
}
return h.Handler.Handle(ctx, r)
}
8. Boas Práticas e Checklist de Produção
- Sempre use
*Context: Prefiraslog.InfoContext(ctx, ...),slog.ErrorContext(ctx, ...)em vez deslog.Info(...)para que contextos e cancelamentos sejam propagados. - Nunca Logue o Mesmo Erro Múltiplas Vezes: Se uma camada inferior retornar um erro, retorne-o com
fmt.Errorf("...: %w", err)e registre o log apenas uma vez na borda da aplicação (no Handler HTTP ou no Worker de fila). - Proteja Dados Pessoais: Use a interface
slog.LogValuerpara qualquer struct que transporte senhas, tokens ou dados pessoais sensíveis. - Evite Interpolação de Strings: Nunca faça
slog.Info(fmt.Sprintf("User %s logou", u.Name)). Use sempre a mensagem constante e os atributos:slog.Info("user_logged_in", slog.String("name", u.Name)). - Use JSON em Produção: Facilita filtros por campos no Datadog (
@user.id:"usr_99"), Loki e ElasticSearch.
9. Resumo Conceitual
- O
log/slogé o padrão oficial do Go para logging estruturado a partir do Go 1.21. - Desacopla a interface
slog.Loggerdo formato e destinoslog.Handler. - Atributos fortemente tipados (
slog.String,slog.Int) evitam alocações extras de memória no Heap. - Implemente
slog.LogValuerpara mascaramento automático de dados sensíveis e conformidade com LGPD. - Utilize
slog.LevelVarpara alternar o nível de log entreINFOeDEBUGem runtime sem restart. - Crie um
ContextHandlercustomizado para correlacionar automaticamente todos os logs com otrace_idde Tracing Distribuído e OpenTelemetry e Middlewares em Go.