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:
{"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:
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:
{"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:
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:
logger.Info("http request",
slog.Group("request",
slog.String("method", "POST"),
slog.String("path", "/api/tasks"),
slog.Int("status", 201),
),
)
Output JSON-nya:
{"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:
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:
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:
{"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:
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:
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
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:
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:
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:
| Library | ns/op (approx) | Allocations |
|---|---|---|
| zerolog | ~26 | 0 |
| zap | ~51 | minimal |
| slog | ~101 | minimal |
| 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
| Library | Pilih jika... |
|---|---|
log/slog | Project baru, ingin zero dependency, atau butuh ekosistem handler yang bisa di-swap |
zerolog | Performa adalah prioritas utama, suka fluent API, throughput sangat tinggi |
zap | Butuh kontrol pipeline penuh lewat zapcore, sudah pakai ekosistem Uber |
logrus | Hanya 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.







