Log Slog #

Logging adalah kebutuhan yang hampir universal di setiap aplikasi produksi — tapi bukan sekadar mencetak teks ke konsol. Di sistem terdistribusi, log harus bisa dicari, difilter, dan dianalisis secara efisien: artinya log harus terstruktur, punya level yang konsisten, dan membawa konteks yang cukup untuk menelusuri masalah. Package log/slog hadir di Go 1.21 sebagai solusi resmi untuk kebutuhan ini. Sebelum slog, developer harus memilih antara package log bawaan yang sangat sederhana, atau library eksternal seperti zap atau zerolog. Sekarang, slog menyediakan structured logging yang performan langsung dari standard library — dengan API yang bersih, dukungan level, output JSON atau teks, dan kemampuan kustomisasi penuh melalui interface Handler.

Gambaran Besar Package log/slog #

flowchart TD
    App["Kode Aplikasi"] --> Logger["slog.Logger"]

    Logger --> Level["Level Filter\nDebug / Info / Warn / Error"]
    Level --> Handler["slog.Handler"]

    Handler --> TH["TextHandler\noutput teks manusia"]
    Handler --> JH["JSONHandler\noutput JSON mesin"]
    Handler --> CH["Custom Handler\nimplementasi sendiri"]

    TH --> Stdout["os.Stdout\nos.Stderr"]
    JH --> Stdout
    CH --> Any["File, Network,\nCloud Logging, dll"]

    Logger --> Attr["Atribut\nKey-Value pairs"]
    Logger --> Group["Group\nnamespace atribut"]
    Logger --> Ctx["Context\nrequest-scoped attrs"]

    style App fill:#4f86c6,color:#fff
    style Logger fill:#e8f5e9
    style Handler fill:#e3f2fd
    style TH fill:#fff3e0
    style JH fill:#fff3e0
    style CH fill:#f3e5f5

Mulai Cepat — Default Logger #

Package slog menyediakan fungsi-fungsi top-level yang menggunakan default logger — mudah digunakan tanpa konfigurasi apapun:

package main

import (
    "log/slog"
    "os"
)

func main() {
    // Fungsi top-level — menggunakan default logger
    slog.Info("aplikasi dimulai")
    slog.Debug("ini tidak muncul secara default — level minimum adalah Info")
    slog.Warn("penggunaan memori tinggi", "persen", 85)
    slog.Error("koneksi database gagal", "host", "localhost", "port", 5432)

    // Output (TextHandler, format teks):
    // time=2024-03-15T14:30:00.000Z level=INFO msg="aplikasi dimulai"
    // time=2024-03-15T14:30:00.001Z level=WARN msg="penggunaan memori tinggi" persen=85
    // time=2024-03-15T14:30:00.002Z level=ERROR msg="koneksi database gagal" host=localhost port=5432
}

Default logger menggunakan TextHandler dengan output ke os.Stderr dan level minimum Info. Untuk aplikasi produksi, selalu buat logger sendiri dengan konfigurasi yang eksplisit.


Handler — TextHandler dan JSONHandler #

Handler menentukan bagaimana log ditulis — ke mana dan dalam format apa. Go menyediakan dua handler bawaan:

flowchart LR
    subgraph Text["TextHandler"]
        T1["Format teks yang mudah dibaca manusia"]
        T2["time=... level=... msg=... key=val"]
        T3["Ideal untuk development\ndan CLI tools"]
    end

    subgraph JSON["JSONHandler"]
        J1["Format JSON yang mudah diproses mesin"]
        J2["{time:..., level:..., msg:..., key:val}"]
        J3["Ideal untuk production\ndan cloud logging"]
    end

    subgraph Config["HandlerOptions"]
        C1["Level — filter minimum level"]
        C2["AddSource — sertakan file:baris"]
        C3["ReplaceAttr — ubah atribut"]
    end

    Config --> Text
    Config --> JSON
import (
    "log/slog"
    "os"
)

// TextHandler — untuk development
func buatLoggerDev() *slog.Logger {
    handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
        Level:     slog.LevelDebug, // tampilkan semua level
        AddSource: true,            // sertakan nama file dan nomor baris
    })
    return slog.New(handler)
}

// JSONHandler — untuk produksi
func buatLoggerProd() *slog.Logger {
    handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo, // hanya Info ke atas
        ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
            // Ganti nama atribut waktu dari "time" ke "timestamp"
            if a.Key == slog.TimeKey && len(groups) == 0 {
                return slog.Attr{Key: "timestamp", Value: a.Value}
            }
            // Ganti nama level dari "level" ke "severity" (konvensi GCP)
            if a.Key == slog.LevelKey && len(groups) == 0 {
                return slog.Attr{Key: "severity", Value: a.Value}
            }
            return a
        },
    })
    return slog.New(handler)
}

func main() {
    // Set sebagai default logger global
    logger := buatLoggerProd()
    slog.SetDefault(logger)

    // Sekarang slog.Info(), dll menggunakan logger yang baru
    slog.Info("server dimulai", "port", 8080, "env", "production")

    // Output JSON:
    // {"timestamp":"2024-03-15T14:30:00Z","severity":"INFO","msg":"server dimulai","port":8080,"env":"production"}
}

Level Logging #

slog mendukung empat level bawaan, dan level kustom bisa ditambahkan:

flowchart LR
    Debug["Debug\n-4\nInformasi detail\nuntuk debugging"] --> Info["Info\n0\nEvent normal\n(default minimum)"]
    Info --> Warn["Warn\n4\nKondisi tidak normal\ntapi masih berjalan"]
    Warn --> Error["Error\n8\nKegagalan yang perlu\nperhatian segera"]

    style Debug fill:#e3f2fd
    style Info fill:#e8f5e9
    style Warn fill:#fff3e0
    style Error fill:#fce4ec
logger := slog.Default()

// Empat level standar
logger.Debug("memuat konfigurasi", "path", "/etc/app.yaml")
logger.Info("server berjalan", "addr", ":8080")
logger.Warn("disk hampir penuh", "persen_terpakai", 92)
logger.Error("gagal simpan data", "error", err)

// Cek apakah level aktif sebelum membuat pesan mahal
if logger.Enabled(context.Background(), slog.LevelDebug) {
    // Operasi mahal ini hanya dijalankan jika Debug aktif
    state := ambilStateDetailed() // mahal untuk dihitung
    logger.Debug("state detail", "state", state)
}

// Level kustom — int di antara level standar
const LevelTrace = slog.Level(-8) // lebih rendah dari Debug
const LevelNotice = slog.Level(2) // antara Info dan Warn
const LevelFatal = slog.Level(12) // lebih tinggi dari Error

logger.Log(context.Background(), LevelTrace, "trace sangat detail")
logger.Log(context.Background(), LevelFatal, "error fatal, aplikasi berhenti")

// Mengubah level secara dinamis (tanpa restart)
var levelVar slog.LevelVar // zero value = LevelInfo
levelVar.Set(slog.LevelDebug) // ubah ke Debug saat runtime

handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: &levelVar, // pointer ke LevelVar
})
dynamicLogger := slog.New(handler)
dynamicLogger.Debug("ini sekarang muncul")

levelVar.Set(slog.LevelWarn) // ubah lagi
dynamicLogger.Info("ini sekarang tidak muncul")

Atribut — Key-Value yang Terstruktur #

Kekuatan utama slog adalah kemampuannya menyertakan atribut terstruktur bersama setiap pesan log. Ada beberapa cara untuk menambahkan atribut:

logger := slog.Default()

// Cara 1: alternating key-value (paling ringkas)
logger.Info("pesanan dibuat",
    "id", 42,
    "total", 150000.50,
    "item_count", 3,
)

// Cara 2: slog.Attr (lebih eksplisit, lebih cepat)
logger.Info("pesanan dibuat",
    slog.Int("id", 42),
    slog.Float64("total", 150000.50),
    slog.Int("item_count", 3),
)

// Cara 3: slog.Any untuk tipe apapun
logger.Info("pengguna login",
    slog.Any("user", pengguna),
    slog.Any("ip", net.ParseIP("192.168.1.1")),
)

// Tipe atribut yang tersedia
slog.String("nama", "Budi")
slog.Int("umur", 30)
slog.Int64("id", int64(12345678901))
slog.Uint64("bytes", uint64(1024))
slog.Float64("skor", 98.5)
slog.Bool("aktif", true)
slog.Time("dibuat", time.Now())
slog.Duration("elapsed", 250*time.Millisecond)
slog.Any("error", err)
slog.Any("data", map[string]any{"key": "val"})

// Error — konvensi: gunakan key "error" atau "err"
if err != nil {
    logger.Error("operasi gagal",
        "error", err, // akan memanggil err.Error() otomatis
        "operasi", "simpanProduk",
        "id", produkID,
    )
}

Group — Mengelompokkan Atribut #

Group membuat namespace untuk atribut yang terkait, menghasilkan struktur yang lebih bersih di output JSON:

// Tanpa group — atribut datar
logger.Info("request masuk",
    "method", "POST",
    "path", "/api/produk",
    "ip", "192.168.1.1",
    "user_agent", "Mozilla/5.0",
    "user_id", 42,
    "user_nama", "Budi",
)
// Output JSON: {"msg":"request masuk","method":"POST","path":"/api/produk",...}

// Dengan group — atribut terstruktur
logger.Info("request masuk",
    slog.Group("http",
        slog.String("method", "POST"),
        slog.String("path", "/api/produk"),
        slog.String("ip", "192.168.1.1"),
        slog.String("user_agent", "Mozilla/5.0"),
    ),
    slog.Group("user",
        slog.Int("id", 42),
        slog.String("nama", "Budi"),
    ),
)
// Output JSON:
// {
//   "msg": "request masuk",
//   "http": {"method":"POST","path":"/api/produk","ip":"...","user_agent":"..."},
//   "user": {"id":42,"nama":"Budi"}
// }

Logger dengan Atribut Tetap — With #

logger.With() membuat logger baru yang selalu menyertakan atribut tertentu di setiap log — berguna untuk menambahkan konteks yang konsisten seperti nama service, versi, atau request ID:

// Logger dasar
base := slog.New(slog.NewJSONHandler(os.Stdout, nil))

// Logger dengan konteks service — atribut ini muncul di SETIAP log
serviceLogger := base.With(
    slog.String("service", "order-service"),
    slog.String("version", "1.2.3"),
    slog.String("env", "production"),
)

serviceLogger.Info("server dimulai", "port", 8080)
// {"service":"order-service","version":"1.2.3","env":"production","msg":"server dimulai","port":8080}

serviceLogger.Error("koneksi DB gagal", "error", err)
// {"service":"order-service","version":"1.2.3","env":"production","msg":"koneksi DB gagal","error":"..."}

// Logger per request — tambahkan request ID
func handlerAPI(w http.ResponseWriter, r *http.Request) {
    requestID := r.Header.Get("X-Request-ID")
    if requestID == "" {
        requestID = generateRequestID()
    }

    // Logger khusus request ini — mewarisi atribut dari serviceLogger
    log := serviceLogger.With(
        slog.String("request_id", requestID),
        slog.String("method", r.Method),
        slog.String("path", r.URL.Path),
    )

    log.Info("request diterima")

    hasil, err := prosesRequest(r)
    if err != nil {
        log.Error("request gagal", "error", err)
        http.Error(w, "internal error", 500)
        return
    }

    log.Info("request berhasil", "status", 200)
    json.NewEncoder(w).Encode(hasil)
}

WithGroup — Namespace Tetap #

// Semua atribut berikutnya dimasukkan ke dalam group "db"
dbLogger := base.WithGroup("db")
dbLogger.Info("query dijalankan",
    slog.String("query", "SELECT * FROM users"),
    slog.Duration("duration", 45*time.Millisecond),
    slog.Int("rows", 10),
)
// {"msg":"query dijalankan","db":{"query":"SELECT...","duration":"45ms","rows":10}}

Logging dengan Context #

slog mendukung logging yang terintegrasi dengan context.Context — memungkinkan middleware menyimpan atribut di context dan semua log berikutnya otomatis menyertakannya:

sequenceDiagram
    participant MW as Middleware
    participant Handler as HTTP Handler
    participant Service as Service
    participant Repo as Repository

    MW->>MW: Buat logger dengan request_id, user_id
    MW->>MW: Simpan logger di context
    MW->>Handler: ctx dengan logger
    Handler->>Handler: log := LoggerFromCtx(ctx)
    Handler->>Handler: log.Info("proses request")
    Handler->>Service: Service(ctx, ...)
    Service->>Service: log := LoggerFromCtx(ctx)
    Service->>Service: log.Info("validasi input")
    Service->>Repo: Repo(ctx, ...)
    Repo->>Repo: log := LoggerFromCtx(ctx)
    Repo->>Repo: log.Info("eksekusi query")

    Note over Handler,Repo: Semua log otomatis punya\nrequest_id dan user_id yang sama
type contextKey string

const keyLogger contextKey = "logger"

// Simpan logger di context
func ContextDenganLogger(ctx context.Context, logger *slog.Logger) context.Context {
    return context.WithValue(ctx, keyLogger, logger)
}

// Ambil logger dari context — fallback ke default jika tidak ada
func LoggerDariCtx(ctx context.Context) *slog.Logger {
    if logger, ok := ctx.Value(keyLogger).(*slog.Logger); ok {
        return logger
    }
    return slog.Default()
}

// Middleware: tambahkan request-scoped logger ke context
func middlewareSlog(base *slog.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            requestID := r.Header.Get("X-Request-ID")
            if requestID == "" {
                requestID = generateRequestID()
            }

            // Logger dengan atribut request
            log := base.With(
                slog.String("request_id", requestID),
                slog.String("method", r.Method),
                slog.String("path", r.URL.Path),
                slog.String("remote_addr", r.RemoteAddr),
            )

            // Simpan di context
            ctx := ContextDenganLogger(r.Context(), log)

            mulai := time.Now()
            rw := &responseWriter{ResponseWriter: w, statusCode: 200}
            next.ServeHTTP(rw, r.WithContext(ctx))

            // Log setelah request selesai
            log.Info("request selesai",
                slog.Int("status", rw.statusCode),
                slog.Duration("duration", time.Since(mulai)),
            )
        })
    }
}

// Penggunaan di service — tidak perlu oper logger sebagai parameter
func serviceCariBuku(ctx context.Context, id int) (*Buku, error) {
    log := LoggerDariCtx(ctx)
    log.Debug("mencari buku", slog.Int("id", id))

    buku, err := repoCariBuku(ctx, id)
    if err != nil {
        log.Error("gagal cari buku", slog.Int("id", id), slog.Any("error", err))
        return nil, err
    }

    log.Debug("buku ditemukan", slog.String("judul", buku.Judul))
    return buku, nil
}

Custom Handler #

Untuk kebutuhan logging yang tidak bisa dipenuhi oleh TextHandler atau JSONHandler, implementasikan interface slog.Handler:

type Handler interface {
    Enabled(context.Context, Level) bool
    Handle(context.Context, Record) error
    WithAttrs(attrs []Attr) Handler
    WithGroup(name string) Handler
}

Contoh: Multi-Handler #

// Handler yang mengirim log ke beberapa tujuan sekaligus
type MultiHandler struct {
    handlers []slog.Handler
}

func NewMultiHandler(handlers ...slog.Handler) *MultiHandler {
    return &MultiHandler{handlers: handlers}
}

func (h *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
    for _, handler := range h.handlers {
        if handler.Enabled(ctx, level) {
            return true
        }
    }
    return false
}

func (h *MultiHandler) Handle(ctx context.Context, r slog.Record) error {
    var errs []error
    for _, handler := range h.handlers {
        if handler.Enabled(ctx, r.Level) {
            if err := handler.Handle(ctx, r.Clone()); err != nil {
                errs = append(errs, err)
            }
        }
    }
    return errors.Join(errs...)
}

func (h *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    handlers := make([]slog.Handler, len(h.handlers))
    for i, handler := range h.handlers {
        handlers[i] = handler.WithAttrs(attrs)
    }
    return &MultiHandler{handlers: handlers}
}

func (h *MultiHandler) WithGroup(name string) slog.Handler {
    handlers := make([]slog.Handler, len(h.handlers))
    for i, handler := range h.handlers {
        handlers[i] = handler.WithGroup(name)
    }
    return &MultiHandler{handlers: handlers}
}

// Penggunaan: log ke stdout (teks) DAN file (JSON)
func buatMultiLogger() *slog.Logger {
    logFile, _ := os.OpenFile("app.log",
        os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

    textHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelDebug,
    })
    jsonHandler := slog.NewJSONHandler(logFile, &slog.HandlerOptions{
        Level: slog.LevelInfo,
    })

    return slog.New(NewMultiHandler(textHandler, jsonHandler))
}

Contoh: Handler dengan Sampling #

// Handler yang hanya log sebagian pesan Debug untuk mengurangi volume
type SamplingHandler struct {
    handler  slog.Handler
    rate     int // log 1 dari N pesan Debug
    counter  atomic.Int64
}

func (h *SamplingHandler) Enabled(ctx context.Context, level slog.Level) bool {
    if level > slog.LevelDebug {
        return h.handler.Enabled(ctx, level)
    }
    // Untuk Debug: hanya enabled 1 dari N kali
    return h.counter.Add(1)%int64(h.rate) == 0
}

func (h *SamplingHandler) Handle(ctx context.Context, r slog.Record) error {
    return h.handler.Handle(ctx, r)
}

func (h *SamplingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    return &SamplingHandler{
        handler: h.handler.WithAttrs(attrs),
        rate:    h.rate,
    }
}

func (h *SamplingHandler) WithGroup(name string) slog.Handler {
    return &SamplingHandler{
        handler: h.handler.WithGroup(name),
        rate:    h.rate,
    }
}

Migrasi dari log ke log/slog #

Jika kamu punya kode yang menggunakan package log lama, migrasi ke slog mudah dilakukan secara bertahap:

import (
    "log"
    "log/slog"
    "os"
)

// Package log lama — tidak terstruktur
log.Printf("server dimulai di port %d", 8080)
log.Printf("error: %v", err)

// slog — terstruktur
slog.Info("server dimulai", "port", 8080)
slog.Error("operasi gagal", "error", err)

// Redirect output log lama ke slog (untuk migrasi bertahap)
// Semua log.Printf akan lewat slog sebagai level Info
slogHandler := slog.NewJSONHandler(os.Stdout, nil)
slogLogger := slog.New(slogHandler)
slog.SetDefault(slogLogger)

// log.Default() sekarang menulis ke slog
log.SetOutput(slog.NewLogLogger(slogLogger.Handler(), slog.LevelInfo).Writer())

Pola Penggunaan di Produksi #

Setup Logger Produksi yang Lengkap #

package main

import (
    "log/slog"
    "os"
)

func setupLogger(env, versi string) *slog.Logger {
    var level slog.Level
    switch env {
    case "production":
        level = slog.LevelInfo
    case "staging":
        level = slog.LevelDebug
    default: // development
        level = slog.LevelDebug
    }

    opts := &slog.HandlerOptions{
        Level:     level,
        AddSource: env != "production", // source hanya di non-prod
        ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
            // Format waktu sebagai Unix timestamp untuk efisiensi parsing
            if a.Key == slog.TimeKey && len(groups) == 0 {
                return slog.Int64("ts", a.Value.Time().Unix())
            }
            return a
        },
    }

    var handler slog.Handler
    if env == "production" || env == "staging" {
        handler = slog.NewJSONHandler(os.Stdout, opts)
    } else {
        handler = slog.NewTextHandler(os.Stdout, opts)
    }

    return slog.New(handler).With(
        slog.String("service", "myapp"),
        slog.String("version", versi),
        slog.String("env", env),
    )
}

func main() {
    env := os.Getenv("APP_ENV")
    if env == "" {
        env = "development"
    }

    logger := setupLogger(env, "1.2.3")
    slog.SetDefault(logger)

    logger.Info("aplikasi dimulai",
        slog.String("go_version", runtime.Version()),
        slog.Int("pid", os.Getpid()),
    )
}

Logging Performa Operasi #

// Decorator untuk mengukur dan log durasi operasi
func denganLog(ctx context.Context, operasi string, fn func() error) error {
    log := LoggerDariCtx(ctx)
    log.Debug("mulai " + operasi)

    mulai := time.Now()
    err := fn()
    durasi := time.Since(mulai)

    if err != nil {
        log.Error("gagal "+operasi,
            slog.Duration("duration", durasi),
            slog.Any("error", err),
        )
        return err
    }

    log.Info("selesai "+operasi,
        slog.Duration("duration", durasi),
    )
    return nil
}

// Penggunaan
func serviceProsesPembayaran(ctx context.Context, p Pembayaran) error {
    return denganLog(ctx, "proses pembayaran", func() error {
        if err := validasiPembayaran(ctx, p); err != nil {
            return err
        }
        return simpanPembayaran(ctx, p)
    })
}

Structured Error Logging #

// Helper untuk log error dengan konteks yang kaya
func logError(ctx context.Context, msg string, err error, attrs ...slog.Attr) {
    log := LoggerDariCtx(ctx)

    // Kumpulkan atribut error
    allAttrs := []slog.Attr{slog.Any("error", err)}

    // Tambahkan stack trace jika error mendukungnya
    type stackTracer interface {
        StackTrace() []string
    }
    if st, ok := err.(stackTracer); ok {
        allAttrs = append(allAttrs,
            slog.Any("stack_trace", st.StackTrace()))
    }

    allAttrs = append(allAttrs, attrs...)

    args := make([]any, len(allAttrs))
    for i, a := range allAttrs {
        args[i] = a
    }

    log.Error(msg, args...)
}

// Penggunaan
func handlerBuatPesanan(w http.ResponseWriter, r *http.Request) {
    pesanan, err := serviceBuatPesanan(r.Context(), input)
    if err != nil {
        logError(r.Context(), "gagal buat pesanan", err,
            slog.Int("user_id", userID),
            slog.String("produk", input.ProdukID),
        )
        http.Error(w, "gagal", 500)
        return
    }

    LoggerDariCtx(r.Context()).Info("pesanan berhasil dibuat",
        slog.Int("pesanan_id", pesanan.ID),
        slog.Float64("total", pesanan.Total),
    )
}

Kapan Beralih ke Alternatif #

Tetap gunakan log/slog jika:
  ✓ Structured logging dengan level untuk semua aplikasi Go 1.21+
  ✓ Output JSON untuk cloud logging (GCP, AWS CloudWatch, ELK)
  ✓ Kustomisasi via custom Handler
  ✓ Integrasi dengan context untuk request-scoped logging
  ✓ Ingin zero external dependency

Pertimbangkan package log lama jika:
  ✗ Menggunakan Go < 1.21 dan tidak bisa upgrade
  ✗ Logging sangat sederhana tanpa kebutuhan struktur

Pertimbangkan library eksternal jika:
  ✗ Performa sangat kritikal dengan volume log sangat tinggi
     → zap (uber-go/zap) — zero-allocation, sangat cepat
     → zerolog (rs/zerolog) — zero-allocation, API berantai
  ✗ Fitur yang belum ada di slog:
     → Log rotation → lumberjack
     → Sampling built-in → zap
     → Hooks untuk kirim ke Sentry/Datadog → logrus (tapi lebih lambat)
  ✗ Tim sudah familiar dan ekosistem sudah terikat ke zap/zerolog

Ringkasan #

  • slog adalah pilihan logging default untuk semua project Go 1.21+ — menggantikan log, logrus, dan zap untuk kasus umum tanpa dependensi eksternal.
  • JSONHandler untuk produksi, TextHandler untuk development — JSON mudah diproses oleh sistem log aggregation, teks mudah dibaca manusia di terminal.
  • logger.With() untuk konteks tetap — buat logger dengan atribut yang selalu muncul (service name, version, request ID) alih-alih menambahkannya secara manual di setiap log.
  • Simpan logger di context untuk request-scoped logging — middleware menambahkan request ID dan user info ke logger, dan semua layer bawah menggunakannya tanpa oper logger sebagai parameter.
  • slog.Attr lebih cepat dari alternating key-valueslog.String("key", val) menghindari alokasi refleksi dibanding "key", val. Gunakan Attr di hot path.
  • logger.Enabled(ctx, level) sebelum operasi logging yang mahal — hindari membuat pesan debug yang kompleks jika level Debug tidak aktif.
  • LevelVar untuk mengubah level tanpa restart — ekspos endpoint HTTP untuk mengubah log level di runtime saat debugging di produksi.
  • ReplaceAttr untuk normalisasi — standardisasi nama field (misal timetimestamp) agar kompatibel dengan format yang diharapkan sistem log aggregation.
  • slog.SetDefault(logger) agar slog.Info() dll menggunakan logger yang sudah dikonfigurasi — jangan andalkan default logger di aplikasi produksi.

← Sebelumnya: Sync   Berikutnya: Sort →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact