O que é Log em Go?
O pacote log da biblioteca padrão de Go fornece funcionalidades básicas de registro (logging) para seus programas. Logging é uma prática essencial em desenvolvimento de software — permite rastrear o comportamento do programa, diagnosticar problemas, e auditar eventos em produção.
Go oferece dois pacotes principais para logging: o pacote log tradicional, disponível desde a primeira versão, e o pacote log/slog (structured logging), introduzido no Go 1.21. Juntos, eles cobrem desde necessidades simples de debug até requisitos sofisticados de observabilidade em sistemas distribuídos.
Uso básico do pacote log
package main
import "log"
func main() {
log.Println("Servidor iniciado")
log.Printf("Porta: %d", 8080)
nome := "Golang Brasil"
log.Printf("Bem-vindo ao %s", nome)
}
Saída:
2026/04/27 14:30:00 Servidor iniciado
2026/04/27 14:30:00 Porta: 8080
2026/04/27 14:30:00 Bem-vindo ao Golang Brasil
Por padrão, o pacote log adiciona data e hora antes de cada mensagem e escreve para os.Stderr. Essa saída padrão pode ser customizada.
Funções Principais do Pacote log
O pacote log oferece três famílias de funções, cada uma com comportamento distinto após imprimir a mensagem:
Print — apenas loga
log.Print("Mensagem simples") // sem formatação
log.Println("Mensagem com newline") // adiciona \n
log.Printf("Usuário %s logou", "João") // com formatação como fmt.Printf
Fatal — loga e encerra o programa
As funções Fatal chamam os.Exit(1) após logar. As funções defer não são executadas:
func conectarBanco() *sql.DB {
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatalf("Falha ao conectar ao banco: %v", err) // programa encerra aqui
}
return db
}
Use Fatal apenas durante a inicialização do programa, quando uma falha torna impossível continuar. Em handlers HTTP ou funções normais, retorne error em vez de chamar Fatal.
Panic — loga e entra em pânico
As funções Panic chamam panic após logar. Diferente de Fatal, as funções defer são executadas, e o panic pode ser capturado com recover:
func carregarConfiguracao(caminho string) Config {
dados, err := os.ReadFile(caminho)
if err != nil {
log.Panicf("Configuração obrigatória não encontrada: %v", err)
}
var cfg Config
if err := json.Unmarshal(dados, &cfg); err != nil {
log.Panicf("Configuração inválida: %v", err)
}
return cfg
}
Flags de Formatação
O pacote log permite customizar o prefixo das mensagens usando flags. As flags controlam quais informações aparecem antes de cada mensagem:
func main() {
// Padrão: data e hora
log.SetFlags(log.LstdFlags)
log.Println("Padrão") // 2026/04/27 14:30:00 Padrão
// Data, hora e microsegundos
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("Detalhado") // 2026/04/27 14:30:00.123456 Detalhado
// Arquivo e linha
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("Com arquivo") // 2026/04/27 14:30:00 main.go:15: Com arquivo
// Caminho completo do arquivo
log.SetFlags(log.LstdFlags | log.Llongfile)
log.Println("Caminho completo") // 2026/04/27 14:30:00 /app/main.go:18: Caminho completo
// UTC em vez de horário local
log.SetFlags(log.LstdFlags | log.LUTC)
log.Println("UTC") // 2026/04/27 17:30:00 UTC
// Prefixo após flags (Go 1.14+)
log.SetFlags(log.LstdFlags | log.Lmsgprefix)
log.SetPrefix("[APP] ")
log.Println("Com prefixo") // 2026/04/27 14:30:00 [APP] Com prefixo
}
Redirecionando a Saída
Por padrão, log escreve para os.Stderr. Você pode redirecionar para um arquivo, um io.Writer, ou múltiplos destinos:
func configurarLog() {
// Logar para arquivo
arquivo, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
log.SetOutput(arquivo)
log.Println("Esta mensagem vai para o arquivo")
// Logar para arquivo E stderr simultaneamente
multiWriter := io.MultiWriter(os.Stderr, arquivo)
log.SetOutput(multiWriter)
log.Println("Esta mensagem vai para ambos")
}
Custom Loggers
Para sistemas maiores, crie loggers personalizados com diferentes configurações para diferentes componentes:
var (
infoLog = log.New(os.Stdout, "[INFO] ", log.LstdFlags|log.Lshortfile)
warnLog = log.New(os.Stdout, "[WARN] ", log.LstdFlags|log.Lshortfile)
errorLog = log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lshortfile)
)
func processarPedido(id string) {
infoLog.Printf("Processando pedido %s", id)
total, err := calcularTotal(id)
if err != nil {
errorLog.Printf("Falha ao calcular total do pedido %s: %v", id, err)
return
}
if total > 10000 {
warnLog.Printf("Pedido %s com valor alto: R$ %.2f", id, total)
}
infoLog.Printf("Pedido %s processado com sucesso: R$ %.2f", id, total)
}
Cada logger criado com log.New() é independente e thread-safe — pode ser usado de múltiplas goroutines sem mutex adicional, pois o pacote log já usa sincronização interna.
Slog: Logging Estruturado (Go 1.21+)
O pacote log/slog, introduzido no Go 1.21, traz logging estruturado para a biblioteca padrão. Em vez de mensagens de texto livre, slog produz registros com campos chave-valor que são facilmente processados por ferramentas de análise:
package main
import "log/slog"
func main() {
// Logging estruturado básico
slog.Info("Servidor iniciado", "porta", 8080, "ambiente", "produção")
// 2026/04/27 14:30:00 INFO Servidor iniciado porta=8080 ambiente=produção
slog.Warn("Latência alta",
"endpoint", "/api/dados",
"duracao_ms", 1500,
)
slog.Error("Falha na conexão",
"host", "db.exemplo.com",
"erro", err,
"tentativa", 3,
)
}
Handlers: Text e JSON
Slog suporta diferentes formatos de saída através de handlers:
func configurarSlog() {
// Handler texto (padrão) — bom para desenvolvimento
textoHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
slog.SetDefault(slog.New(textoHandler))
slog.Debug("Modo texto") // time=... level=DEBUG msg="Modo texto"
// Handler JSON — ideal para produção
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
})
slog.SetDefault(slog.New(jsonHandler))
slog.Info("Modo JSON", "porta", 8080)
// {"time":"2026-04-27T14:30:00Z","level":"INFO","msg":"Modo JSON","porta":8080}
}
Grupos e Atributos
Para organizar campos relacionados, use grupos:
func logComGrupo() {
logger := slog.Default()
logger.Info("Requisição processada",
slog.Group("request",
slog.String("method", "POST"),
slog.String("path", "/api/usuarios"),
slog.Int("status", 201),
),
slog.Group("user",
slog.String("id", "usr_123"),
slog.String("role", "admin"),
),
slog.Duration("duracao", 45*time.Millisecond),
)
}
Logger com contexto
Crie loggers com campos persistentes para evitar repetição:
func processarRequisicao(w http.ResponseWriter, r *http.Request) {
// Logger com campos fixos para esta requisição
logger := slog.With(
"request_id", r.Header.Get("X-Request-ID"),
"method", r.Method,
"path", r.URL.Path,
)
logger.Info("Requisição recebida")
dados, err := buscarDados(r.Context())
if err != nil {
logger.Error("Falha ao buscar dados", "erro", err)
http.Error(w, "Erro interno", 500)
return
}
logger.Info("Requisição concluída", "registros", len(dados))
}
Custom slog.Handler
Para necessidades avançadas, implemente a interface slog.Handler:
type MeuHandler struct {
nivel slog.Level
saida io.Writer
attrs []slog.Attr
}
func (h *MeuHandler) Enabled(_ context.Context, level slog.Level) bool {
return level >= h.nivel
}
func (h *MeuHandler) Handle(_ context.Context, r slog.Record) error {
// Formatação customizada
msg := fmt.Sprintf("[%s] %s: %s",
r.Time.Format("15:04:05"),
r.Level.String(),
r.Message,
)
r.Attrs(func(a slog.Attr) bool {
msg += fmt.Sprintf(" %s=%v", a.Key, a.Value)
return true
})
_, err := fmt.Fprintln(h.saida, msg)
return err
}
func (h *MeuHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &MeuHandler{nivel: h.nivel, saida: h.saida, attrs: append(h.attrs, attrs...)}
}
func (h *MeuHandler) WithGroup(name string) slog.Handler {
return h // simplificado
}
Log Levels e Padrões de Produção
Em produção, controlar o nível de log é essencial para balancear visibilidade com performance:
func configurarProducao() *slog.Logger {
// Nível dinâmico — pode ser alterado em runtime
nivelVar := new(slog.LevelVar)
nivelVar.Set(slog.LevelInfo) // começa com Info
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: nivelVar,
AddSource: true, // adiciona arquivo:linha
})
logger := slog.New(handler)
// Endpoint para alterar nível em runtime
http.HandleFunc("/admin/log-level", func(w http.ResponseWriter, r *http.Request) {
nivel := r.URL.Query().Get("level")
switch nivel {
case "debug":
nivelVar.Set(slog.LevelDebug)
case "info":
nivelVar.Set(slog.LevelInfo)
case "warn":
nivelVar.Set(slog.LevelWarn)
case "error":
nivelVar.Set(slog.LevelError)
}
fmt.Fprintf(w, "Nível alterado para: %s", nivel)
})
return logger
}
Integração com Context
Use context para propagar informações de tracing e correlação:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.New().String()
// Adiciona logger ao context
logger := slog.With("request_id", requestID)
ctx := context.WithValue(r.Context(), "logger", logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func loggerFromCtx(ctx context.Context) *slog.Logger {
if logger, ok := ctx.Value("logger").(*slog.Logger); ok {
return logger
}
return slog.Default()
}
Próximos Passos
Explore mais sobre logging e observabilidade em Go:
- Fmt — formatação de saída para debug
- IO — interfaces usadas por log writers
- Context — propagação de informação entre funções
- Interface — implementando slog.Handler
- Logging estruturado com slog — guia detalhado
- Observabilidade com OpenTelemetry — tracing e métricas
- Go para Backend — logging em produção
Perguntas Frequentes
Qual a diferença entre log.Fatal e log.Panic?
log.Fatal chama os.Exit(1) após logar — o programa encerra imediatamente sem executar funções defer. log.Panic chama panic após logar — os defers são executados, e o panic pode ser capturado por recover. Use Fatal apenas na inicialização; use Panic quando quer permitir limpeza de recursos.
Quando usar log vs slog?
O pacote log tradicional é suficiente para scripts simples e ferramentas CLI. Para aplicações em produção, use log/slog (Go 1.21+) — logging estruturado facilita a busca e análise de logs com ferramentas como Elasticsearch, Loki, ou CloudWatch. Se você usa Go anterior a 1.21, bibliotecas como zerolog ou zap são alternativas populares.
O pacote log é thread-safe?
Sim. Tanto o logger padrão do pacote log quanto loggers criados com log.New() são seguros para uso concorrente de múltiplas goroutines sem mutex adicional. O pacote log/slog também é thread-safe. Essa garantia é essencial para sistemas Go que usam concorrência intensiva.
Como integrar slog com ferramentas de observabilidade?
O slog suporta handlers customizados que podem enviar logs para qualquer destino. Existem handlers da comunidade para Loki, Elasticsearch, Datadog, e outros. Para uma solução completa de observabilidade, combine slog com OpenTelemetry para correlacionar logs, métricas e traces usando trace IDs propagados via context.