Memilih Go Logging Library yang Tepat
TutorialGo#tutorial#backend#golang#go

Memilih Go Logging Library yang Tepat

A
Abd. Asis
Bagikan:

Ketika aplikasi Go mulai naik ke production, satu pertanyaan yang hampir selalu muncul adalah: library logging mana yang harus dipakai? Standar library bawaan (log) terlalu sederhana untuk kebutuhan serius — tidak ada level, tidak ada format JSON, tidak ada structured fields. Di sisi lain, ekosistem Go punya banyak pilihan yang masing-masing punya filosofi berbeda.

Artikel ini membandingkan empat library logging Go yang paling banyak digunakan: log/slog (standar library sejak Go 1.21), zerolog, zap, dan logrus — dari sisi performa, API design, dan kasus penggunaan yang paling cocok untuk masing-masing.

Mengapa Logging Terstruktur Itu Penting

Logging dengan format teks biasa (fmt.Println("user login failed")) memang mudah dibaca manusia, tapi menjadi mimpi buruk saat harus diproses oleh sistem monitoring. Bayangkan harus mencari semua event login gagal dari jutaan baris log di Elasticsearch atau Loki.

Structured logging menyelesaikan ini dengan menghasilkan log dalam format yang bisa di-query — biasanya JSON. Setiap entry punya field yang konsisten:

{}JSON
{"time":"2026-04-29T10:00:00Z","level":"ERROR","msg":"user login failed","user_id":42,"ip":"192.168.1.1"}

Dengan format ini, mencari semua login gagal dari IP tertentu cukup dengan satu query filter, bukan regex yang rapuh.

slog — Standar Library yang Solid

Sejak Go 1.21, bahasa ini punya solusi structured logging bawaan lewat paket log/slog. Keunggulan utamanya: tidak perlu dependency eksternal, dan siapapun yang membaca kode Go langsung familiar.

Cara paling cepat memulai dengan slog:

GoGO
package main

import (
    "log/slog"
    "os"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    slog.SetDefault(logger)

    slog.Info("server started", "port", 8080, "env", "production")
    slog.Warn("config missing", "key", "DATABASE_URL", "fallback", "localhost")
}

Output yang dihasilkan:

{}JSON
{"time":"2026-04-29T10:00:00.000000000Z","level":"INFO","msg":"server started","port":8080,"env":"production"}
{"time":"2026-04-29T10:00:00.000000001Z","level":"WARN","msg":"config missing","key":"DATABASE_URL","fallback":"localhost"}

Mengatur Level dan Opsi

slog mendukung konfigurasi level minimum, penambahan source location, dan modifikasi atribut lewat HandlerOptions:

GoGO
opts := &slog.HandlerOptions{
    Level:     slog.LevelDebug,
    AddSource: true,
}

logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
logger.Debug("query executed", "sql", "SELECT * FROM projects", "duration_ms", 3)

Mengelompokkan Field dengan Group

Saat ingin mengelompokkan atribut terkait, gunakan slog.Group:

GoGO
logger.Info("http request",
    slog.Group("request",
        slog.String("method", "POST"),
        slog.String("path", "/api/tasks"),
        slog.Int("status", 201),
    ),
)

Output JSON-nya:

{}JSON
{"level":"INFO","msg":"http request","request":{"method":"POST","path":"/api/tasks","status":201}}

Menyembunyikan Data Sensitif

Salah satu fitur slog yang berguna adalah interface LogValuer — memungkinkan kita mengontrol bagaimana sebuah tipe muncul di log:

GoGO
type APIToken string

func (t APIToken) LogValue() slog.Value {
    return slog.StringValue("[REDACTED]")
}

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    token := APIToken("sk-prod-abc123xyz")
    logger.Info("request authenticated", "token", token)
    // Output: "token":"[REDACTED]"
}

slog mendukung custom handler melalui interface slog.Handler. Ini berarti bisa swap backend ke zerolog atau zap tanpa mengubah kode aplikasi — cukup ganti handler-nya.

zerolog — Performa Maksimal, API Fluent

zerolog didesain dari awal untuk zero allocation. Library ini menggunakan pendekatan chained API — setiap method mengembalikan event yang sama, memungkinkan penulisan log dalam satu ekspresi fluid:

GoGO
package main

import (
    "os"
    "time"

    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
    log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()

    log.Info().
        Str("service", "task-manager").
        Int("worker_count", 4).
        Msg("service initialized")
}

Hasilnya:

{}JSON
{"level":"info","service":"task-manager","worker_count":4,"time":1714387200000,"message":"service initialized"}

Sampling untuk Log High-Throughput

Di aplikasi yang menghasilkan ribuan log per detik, zerolog punya mekanisme sampling bawaan untuk mencegah banjir log:

GoGO
sampled := log.Sample(&zerolog.BasicSampler{N: 10})

for i := 0; i < 1000; i++ {
    sampled.Info().
        Int("request_id", i).
        Msg("health check ping")
    // Hanya 1 dari 10 entry yang ditulis
}

Satu gotcha zerolog: jika lupa memanggil .Msg() atau .Send() di akhir chain, log entry tidak akan ditulis — dan memory-nya bocor. Selalu pastikan setiap chain diakhiri dengan salah satu dari keduanya.

Logger dengan Context

zerolog mendukung logger yang membawa field tetap lewat konteks:

GoGO
taskLogger := log.With().
    Str("module", "task-processor").
    Str("version", "2.1.0").
    Logger()

taskLogger.Info().Int("task_id", 101).Msg("task started")
taskLogger.Error().Err(err).Int("task_id", 101).Msg("task failed")

zap — Dua API untuk Dua Kebutuhan

zap dari Uber menawarkan dua API: Logger yang cepat dan bebas alokasi, serta SugaredLogger yang lebih ergonomis tapi sedikit lebih lambat.

Logger untuk Production

GoGO
package main

import (
    "go.uber.org/zap"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()

    logger.Info("deployment triggered",
        zap.String("environment", "staging"),
        zap.Int("replica_count", 3),
        zap.Bool("rolling_update", true),
    )
}

zap.NewProduction() langsung menghasilkan JSON logger yang siap pakai — dengan timestamp, caller info, dan sampling untuk level Warn ke atas.

SugaredLogger untuk Kode Non-Critical-Path

Untuk bagian kode yang tidak sensitif performa, SugaredLogger terasa lebih ringkas:

GoGO
sugar := logger.Sugar()

sugar.Infow("project created",
    "project_id", 42,
    "owner", "abdasis",
    "visibility", "private",
)

sugar.Infof("welcome back, %s!", username)

zapcore untuk Pipeline Kustom

Kekuatan sebenarnya zap ada di zapcore — interface yang memungkinkan pipeline logging yang bisa dikustomisasi penuh:

GoGO
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = "timestamp"
encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder

core := zapcore.NewCore(
    zapcore.NewJSONEncoder(encoderCfg),
    zapcore.AddSync(os.Stdout),
    zapcore.InfoLevel,
)

logger := zap.New(core, zap.AddCaller())

Untuk keperluan testing, zaptest/observer memungkinkan kita memverifikasi log secara programatik tanpa harus parse output teks.

logrus — Warisan yang Sudah Pensiun

logrus adalah library logging Go yang paling banyak di-star di GitHub selama bertahun-tahun, dan kemungkinan besar masih ada di banyak codebase lama. Tapi perlu diketahui dengan jelas: logrus kini berada dalam maintenance mode — tidak ada fitur baru yang akan ditambahkan.

Perbandingan performa menunjukkan betapa jauh logrus tertinggal:

Libraryns/op (approx)Allocations
zerolog~260
zap~51minimal
slog~101minimal
logrus~1500+banyak

Logrus ~15x lebih lambat dari slog dan ~50x lebih lambat dari zerolog. Untuk project baru, tidak ada alasan menggunakannya.

Jika masih punya kode logrus lama, migrasi bertahap ke slog adalah pilihan paling aman — API-nya tidak terlalu berbeda jauh untuk penggunaan dasar.

Perbandingan: Kapan Pakai yang Mana

LibraryPilih jika...
log/slogProject baru, ingin zero dependency, atau butuh ekosistem handler yang bisa di-swap
zerologPerforma adalah prioritas utama, suka fluent API, throughput sangat tinggi
zapButuh kontrol pipeline penuh lewat zapcore, sudah pakai ekosistem Uber
logrusHanya untuk maintain codebase lama — jangan untuk project baru

Strategi yang Direkomendasikan

Untuk project baru, mulai dengan slog.NewJSONHandler(). Ini cukup untuk 99% kebutuhan, tidak menambah dependency, dan kode aplikasi tetap bisa swap ke backend lain kapanpun dibutuhkan.

Jika profiling menunjukkan logging menjadi bottleneck — baru pertimbangkan zerolog atau zap sebagai handler backend untuk slog. Dengan cara ini, kode pemanggil tidak perlu diubah sama sekali.

Kesimpulan

Tidak ada jawaban universal untuk pilihan logging library di Go. Tapi ada panduan yang cukup jelas: mulai dengan slog karena standar dan nol dependency, lompat ke zerolog jika butuh performa ekstrem, gunakan zap jika butuh customization pipeline tingkat rendah, dan tinggalkan logrus untuk project baru. Yang paling penting adalah konsistensi — pilih satu library dan gunakan secara konsisten di seluruh codebase.

Referensi

  1. 1 log/slog — Go Standard Library Documentation
  2. 2 uber-go/zap — Blazing fast, structured, leveled logging in Go
  3. 3 rs/zerolog — Zero Allocation JSON Logger
  4. 4 Go Logging Benchmarks — betterstack-community
Abd. Asis
Ditulis oleh
Abd. Asis

Software Developer dari Madura. Menulis tentang PHP, Laravel, dan pengembangan web modern dalam Bahasa Indonesia.