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:

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:


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.