Menu

slog dan log di Golang: Structured Logging di Go

Cara melakukan logging di Go: package log klasik dengan flag-nya dan log.Fatal, serta log/slog (Go 1.21) untuk log terstruktur dengan level, atribut key-value, handler teks dan JSON, dan logger yang membawa konteks lewat With.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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

LevelNilaiDipakai untuk
slog.LevelDebug-4detail untuk developer, dimatikan di production
slog.LevelInfo0kejadian normal: server dimulai, request dilayani, job selesai
slog.LevelWarn4sesuatu yang tidak terduga tapi sudah ditangani program
slog.LevelError8sebuah 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:

FlagMenambahkan
log.LstdFlags (default)tanggal dan waktu 2009/11/10 23:00:00
log.Lmicrosecondsmikrodetik pada waktu
log.LUTCwaktu dalam UTC
log.Lshortfile / log.Llongfilemain.go:14 / path lengkap
log.Lmsgprefixmenaruh prefix sebelum pesan, bukan di awal baris

Tiga kelompok fungsi menghentikan program atau memicu panic, dan perbedaannya penting:

  • log.Fatal, log.Fatalf, log.Fatalln mencetak lalu memanggil os.Exit(1). Pemanggilan yang di-defer tidak dijalankan. Pakai di main untuk kegagalan saat startup, jangan pernah di kode library atau request handler.
  • log.Panic dan 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 LogValuer atau ReplaceAttr untuk 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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai