Logging em aplicações Go: melhores práticas para iniciantes
Logging não é apenas registrar mensagens no console. Em produção, os logs ajudam a rastrear erros, analisar desempenho e entender o comportamento do sistema. Neste artigo, abordaremos as melhores práticas de logging em Go (Golang) para iniciantes: desde a biblioteca padrão até abordagens estruturadas modernas.
Por que o logging é importante em Go?
Go é uma linguagem para criar aplicações server-side de alta carga. Sem um logging de qualidade, você não conseguirá:
- Localizar rapidamente bugs em ambiente de produção
- Entender a sequência de eventos durante uma falha
- Realizar auditoria de segurança
- Otimizar o desempenho
Um bom logging deve ser: estruturado, por nível (debug, info, warn, error), contextual (conter ID da requisição, nome da função) e gerenciável (rotação, saída para arquivo).
1. Começando com a biblioteca padrão log
Go fornece o pacote embutido log — simples e suficiente para projetos pequenos. Veja como ele funciona:
package main
import ( "log" "os")
func main() { // Configuração básica: saída para stderr log.SetOutput(os.Stderr) log.SetPrefix("INFO: ") log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("Aplicação iniciada") log.Printf("Usuário %s autenticado", "admin") log.Fatal("Erro crítico") // encerra o programa com código 1}Principais recursos do log:
log.Println()— saída de stringlog.Printf()— saída formatadalog.Fatal()— saída + encerramento do programalog.Panic()— saída + pânico
Mas o logger padrão tem desvantagens: não possui níveis (debug, info, error), não tem saída estruturada (JSON), nem rotação de arquivos.
2. Adicionando níveis de logging
Para aplicações em produção, é crucial separar os logs por níveis. Aqui está uma implementação mínima usando o pacote padrão:
package main
import ( "log" "os")
type Logger struct { debug *log.Logger info *log.Logger warn *log.Logger error *log.Logger}
func NewLogger() *Logger { flags := log.Ldate | log.Ltime | log.Lshortfile return &Logger{ debug: log.New(os.Stdout, "DEBUG: ", flags), info: log.New(os.Stdout, "INFO: ", flags), warn: log.New(os.Stderr, "WARN: ", flags), error: log.New(os.Stderr, "ERROR: ", flags), }}
func (l *Logger) Debug(msg string, args ...interface{}) { l.debug.Printf(msg, args...)}
func (l *Logger) Info(msg string, args ...interface{}) { l.info.Printf(msg, args...)}
func (l *Logger) Error(msg string, args ...interface{}) { l.error.Printf(msg, args...)}
func main() { log := NewLogger() log.Info("Servidor iniciado na porta %d", 8080) log.Debug("Requisição de %s", "192.168.1.1") log.Error("Falha ao conectar ao BD: %v", "timeout")}Dica: Em produção, use os.Stderr para erros e os.Stdout para logs normais — isso simplifica a coleta de logs via systemd ou Docker.
3. Logging estruturado (JSON)
Em sistemas modernos, os logs são frequentemente coletados em armazenamentos centralizados (Elasticsearch, Loki). Para isso, é necessária uma saída estruturada, por exemplo em JSON. Bibliotecas populares:
- logrus — flexível, com suporte a formatadores
- zap — ultrarrápido, da Uber
- zerolog — leve e rápido
Exemplo com logrus:
package main
import ( "os" "gi