karawaci.kode

← Semua snippet

Go Menengah Utility

Structured logging dengan slog (Go 1.21+)

Standard library `log/slog` untuk structured logging. JSON output, level, context-aware. Replace logrus/zerolog untuk kebanyakan use case.

Dipublikasikan 15 Mei 2026

Go 1.21 keluarkan log/slog — structured logging di stdlib. Sebelumnya saya pakai zerolog. Sekarang slog cukup untuk most use case, tanpa dependency tambahan.

Setup

package main

import (
    "log/slog"
    "os"
)

func main() {
    // JSON output ke stderr, level Debug
    logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
        Level:     slog.LevelDebug,
        AddSource: true, // include file:line
    }))
    
    slog.SetDefault(logger)
    
    slog.Info("server starting", 
        slog.String("addr", ":8080"),
        slog.Int("workers", 4),
    )
    
    slog.Error("connection failed", 
        slog.String("host", "db.local"),
        slog.Int("port", 5432),
        slog.Any("error", errors.New("timeout")),
    )
}

Output (formatted untuk readability):

{
  "time": "2026-06-04T10:30:00+07:00",
  "level": "INFO",
  "source": {"function": "main.main", "file": "/app/main.go", "line": 15},
  "msg": "server starting",
  "addr": ":8080",
  "workers": 4
}

Pattern: contextual logger per request

import (
    "context"
    "log/slog"
)

// Middleware: tambah request ID dan path ke logger
func WithLoggerMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        reqID := uuid.New().String()
        
        logger := slog.With(
            slog.String("request_id", reqID),
            slog.String("method", r.Method),
            slog.String("path", r.URL.Path),
        )
        
        ctx := context.WithValue(r.Context(), "logger", logger)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Handler: pakai contextual logger
func handler(w http.ResponseWriter, r *http.Request) {
    logger := r.Context().Value("logger").(*slog.Logger)
    
    logger.Info("processing request")
    
    if err := doWork(); err != nil {
        logger.Error("work failed", slog.Any("error", err))
        http.Error(w, "Internal error", 500)
        return
    }
    
    logger.Info("request done")
}

Setiap log dari handler include request_id, method, path — tanpa Anda pass explicit.

Levels

slog.Debug("only useful in dev", slog.Int("count", 42))
slog.Info("normal flow")
slog.Warn("something unusual", slog.String("reason", "rate limit near"))
slog.Error("recoverable error", slog.Any("err", err))

Set level dari env:

levelStr := os.Getenv("LOG_LEVEL")
var level slog.Level
switch levelStr {
case "debug": level = slog.LevelDebug
case "info":  level = slog.LevelInfo
case "warn":  level = slog.LevelWarn
case "error": level = slog.LevelError
default:      level = slog.LevelInfo
}

handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: level})
slog.SetDefault(slog.New(handler))

Performance

Slog dengan JSON handler benchmark di laptop saya (M2):

  • Simple log (5 attrs): ~1.2 µs/op, 1 alloc
  • Complex log (15 attrs nested): ~5.3 µs/op, 4 allocs

Comparable dengan zerolog (yang dikenal cepat). Untuk high-throughput (10k req/sec), overhead total: ~1.2ms per 1000 log = negligible.

TextHandler untuk dev

JSON nggak nyaman dibaca di terminal. Untuk dev, pakai TextHandler:

var handler slog.Handler
if os.Getenv("ENV") == "production" {
    handler = slog.NewJSONHandler(os.Stderr, opts)
} else {
    handler = slog.NewTextHandler(os.Stderr, opts)
}

slog.SetDefault(slog.New(handler))

TextHandler output:

time=2026-06-04T10:30:00+07:00 level=INFO msg="server starting" addr=:8080 workers=4

Catatan

  • slog.With() di paket-level untuk tambah attribute permanent (e.g., service name, env)
  • Nested values via slog.Group() untuk hierarchy:
    slog.Info("user signup", slog.Group("user",
      slog.String("id", userID),
      slog.String("email", email),
    ))
  • Don’t log secrets: implement LogValue() method di struct yang punya sensitive field
  • Async logging (write to disk in background): pakai library tambahan (slogger, lumberjack) — slog stdlib synchronous

Migration dari logrus/zerolog

Mostly 1-to-1:

logrusslog
log.WithFields(log.Fields{"k": v}).Info(msg)slog.Info(msg, slog.Any("k", v))
log.WithError(err).Error("...")slog.Error("...", slog.Any("err", err))
log.SetLevel(log.DebugLevel)Set via handler options

Untuk most project: migrate dalam 1-2 jam. Worth karena drop 1 dependency.

slog menjadi standard di Go ekosistem. Library baru (chi v6, fiber v3) sudah support slog adapter natively.

# tags

gologgingslogstructured

Ditulis oleh Asti Larasati · 15 Mei 2026