slog dalam satu contoh
log/slog (Go 1.21) menulis record terstruktur: sebuah pesan, sebuah level, dan atribut key-value.
Setiap baris keluar sebagai time=... level=INFO msg="user logged in" user=ada attempts=1. Karena setiap nilai adalah field tersendiri, log collector (Loki, Elasticsearch, CloudWatch, Datadog) bisa memfilter berdasarkan user=ada atau level=ERROR tanpa regular expression pada teks bebas.
Contoh di halaman ini menulis ke os.Stdout supaya output-nya tampil berurutan. Di service sungguhan, log biasanya dikirim ke os.Stderr, yang juga merupakan tujuan tulis logger default.
Level
| Level | Nilai | Dipakai untuk |
|---|---|---|
slog.LevelDebug | -4 | detail untuk developer, dimatikan di production |
slog.LevelInfo | 0 | kejadian normal: server dimulai, request dilayani, job selesai |
slog.LevelWarn | 4 | sesuatu yang tidak terduga tapi sudah ditangani program |
slog.LevelError | 8 | sebuah operasi gagal |
Handler membuang record di bawah level minimumnya, dan minimum default-nya adalah Info. Itulah sebabnya slog.Debug(...) tidak mencetak apa pun sampai kamu mengonfigurasi handler dengan Level: slog.LevelDebug. Jarak antarnilai menyisakan ruang untuk level kustom seperti slog.Level(2).
Untuk mengubah level saat program berjalan (dari flag, endpoint admin, atau signal), taruh slog.LevelVar di opsi lalu panggil Set padanya nanti:
var level slog.LevelVar // zero value: Info
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: &level}))
level.Set(slog.LevelDebug) // from now on, debug records are written
Teks atau JSON
slog.NewTextHandler menulis pasangan key=value, mudah dibaca di terminal. slog.NewJSONHandler menulis satu object JSON per baris, format yang diharapkan sebagian besar pipeline log. Pemanggilan logging-nya tetap sama; hanya handler-nya yang berubah.
Nilai mempertahankan tipenya: status berupa angka di output JSON dan retry berupa boolean, time.Duration tercetak sebagai 42ms di teks dan sebagai nanodetik di JSON, dan error mencetak pesannya. ReplaceAttr adalah hook untuk menulis ulang atau menghapus atribut, di sini dipakai untuk membuang timestamp, dan dalam praktiknya untuk mengganti nama key (msg menjadi message) atau menyamarkan nilai.
Atribut
Bentuk yang longgar tipenya menyelang-nyeling key dan nilai: "user", "ada", "attempts", 3. Bentuk ini singkat, dan punya satu kemungkinan gagal: jumlah argumen yang ganjil. Nilai yang tersisa dicatat dengan key !BADKEY. go vet bisa menangkapnya:
./main.go:14:2: call to slog.Info missing a final value
Untuk keamanan tipe dan alokasi yang sedikit lebih hemat, pakai constructor atribut, dan LogAttrs saat logging di jalur yang sering dieksekusi (hot path):
logger.Info("order placed",
slog.Int("order_id", 1017),
slog.String("currency", "EUR"),
slog.Float64("total", 59.90),
slog.Duration("took", elapsed),
)
logger.LogAttrs(ctx, slog.LevelInfo, "order placed", slog.Int("order_id", 1017))
Pakai satu konvensi penamaan key di seluruh codebase (user_id di mana-mana, bukan userID di satu package dan uid di package lain). Query di sistem log-mu bergantung pada hal itu.
With: logger yang membawa konteks
logger.With(attrs...) mengembalikan logger baru yang menambahkan atribut-atribut itu ke setiap record. Buat satu per request atau per job, dan setiap baris yang ditulisnya bisa dihubungkan satu sama lain:
Setiap baris membawa service, version, request_id, dan user tanpa perlu diulang di setiap pemanggilan. slog.Group menyusun atribut secara bersarang, yang ditulis handler JSON sebagai object bersarang ("payment":{"amount":25,"currency":"USD"}) dan oleh handler teks sebagai key bertitik (payment.amount=25). logger.WithGroup("db") menaruh setiap atribut berikutnya dari logger itu di bawah sebuah group.
Teruskan logger berlingkup request ke bawah sebagai parameter atau field struct. Menyimpannya di context.Context memang bisa, tapi menyembunyikan dependensinya; method InfoContext(ctx, ...) milik slog meneruskan context ke handler, yang bisa dipakai handler kustom untuk mengambil trace ID.
Menyembunyikan rahasia dengan LogValuer
Sebuah tipe bisa mengatur cara ia dicatat di log dengan mengimplementasikan slog.LogValuer. Cara ini menjauhkan password dan token dari log, siapa pun yang mencatat nilainya:
User hanya mencatat ID dan email-nya, dan Token yang dicatat sendirian mencetak REDACTED. Handler memanggil LogValue hanya saat record benar-benar ditulis, jadi cara ini juga cocok untuk nilai yang mahal dihitung.
Package log klasik
log sudah ada sebelum slog dan masih cocok untuk program kecil dan script. Package ini menulis baris ke standard error dengan prefix tanggal dan waktu:
| Flag | Menambahkan |
|---|---|
log.LstdFlags (default) | tanggal dan waktu 2009/11/10 23:00:00 |
log.Lmicroseconds | mikrodetik pada waktu |
log.LUTC | waktu dalam UTC |
log.Lshortfile / log.Llongfile | main.go:14 / path lengkap |
log.Lmsgprefix | menaruh prefix sebelum pesan, bukan di awal baris |
Tiga kelompok fungsi menghentikan program atau memicu panic, dan perbedaannya penting:
log.Fatal,log.Fatalf,log.Fatallnmencetak lalu memanggilos.Exit(1). Pemanggilan yang di-defer tidak dijalankan. Pakai dimainuntuk kegagalan saat startup, jangan pernah di kode library atau request handler.log.Panicdan teman-temannya mencetak lalu memicu panic, jadi pemanggilan defer tetap berjalan dan panic-nya bisa di-recover.- Semua fungsi lainnya hanya menulis satu baris.
Untuk menulis log ke file, buka file-nya lalu berikan ke log.New atau log.SetOutput; io.MultiWriter(os.Stderr, f) menulis ke keduanya.
log dan slog bersamaan
slog.SetDefault(logger) menjadikan logger sebagai default untuk fungsi tingkat atas slog.Info, sekaligus mengalirkan output package log lewat logger itu. Pemanggilan log.Printf yang sudah ada di kodemu atau di dependensi lalu keluar sebagai record terstruktur di level Info:
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
log.Printf("legacy message") // {"time":"...","level":"INFO","msg":"legacy message"}
Sebelum SetDefault, logger slog default menulis lewat package log, itulah sebabnya slog.Info("hi") saja mencetak 2026/09/23 14:30:00 INFO hi.
Aturan praktis
- Catat error di log atau kembalikan, jangan keduanya. Fungsi yang mencatat error lalu juga mengembalikannya membuat kegagalan yang sama tercatat di setiap level call stack. Kembalikan error ke atas beserta konteksnya, dan catat sekali di tempat error itu ditangani.
- Taruh data yang berubah-ubah di atribut, bukan di pesan.
logger.Info("user created", "user_id", id)mudah dikelompokkan di sistem log;logger.Info(fmt.Sprintf("user %d created", id))menghasilkan pesan berbeda untuk setiap user. - Jangan pernah mencatat rahasia atau body request lengkap. Pakai
LogValueratauReplaceAttruntuk menyamarkannya. - Pakai JSON di production, teks saat development. Pilih handler saat startup dari flag atau environment variable.
- Pilih level dengan sengaja. Kalau semuanya dicatat di level Error, peringatan untuk error jadi tidak berarti.
Pertanyaan yang sering diajukan
Apa itu slog di Go?
log/slog adalah package structured logging yang ditambahkan ke standard library di Go 1.21. Alih-alih string yang diformat, setiap record punya pesan, level (Debug, Info, Warn, Error), dan atribut key-value, lalu sebuah handler menulisnya sebagai teks key=value atau sebagai JSON: slog.Info("login", "user", "ada", "attempts", 3).
Bagaimana cara menyalakan log debug di slog?
Level minimum default adalah Info, jadi slog.Debug tidak mencetak apa pun. Buat handler dengan level yang lebih rendah lalu jadikan default: slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). Pakai slog.LevelVar sebagai ganti konstanta kalau kamu ingin mengubah level saat program berjalan.
Apa perbedaan log dan slog di Go?
log menulis baris bebas dengan prefix timestamp opsional dan tidak punya level. slog menulis record dengan level dan atribut key-value bertipe yang bisa di-parse dan difilter oleh log collector. Keduanya ada di standard library; slog.SetDefault juga mengalihkan output package log lewat handler slog.
Apakah log.Fatal menjalankan fungsi yang di-defer?
Tidak. log.Fatal dan log.Fatalf mencetak pesan lalu memanggil os.Exit(1), yang melewati semua pemanggilan defer. Pakai hanya di main atau kode setup yang tidak perlu membersihkan apa pun. log.Panic justru memicu panic, jadi pemanggilan defer tetap dijalankan.