Lima level — TRACE, DEBUG, INFO, WARN, ERROR — tampak seperti kategori sederhana, tetapi dalam tim besar level adalah bahasa kesepakatan: pembaca log mengasumsikan makna tertentu dari setiap level, dan asumsi itu harus sama di seluruh kode. ERROR pada satu tim berarti “halaman status layanan berubah”, di tim lain hanya berarti “kondisi yang tidak saya duga”. Ketika makna level tidak konsisten, notifikasi insiden berbunyi untuk hal sepele sampai orang berhenti mempercayainya. Pada Chapter ini kita susun tabel keputusan pemilihan level, praktik mengatur level per package, kebiasaan level saat insiden versus development, serta satu aturan mutlak: rahasia tidak boleh masuk log.
Lima Level dan Maknanya #
Tabel berikut merangkum makna baku yang dipakai luas di ekosistem Java:
| Level | Makna | Pembaca yang relevan | Volume tipikal |
|---|---|---|---|
TRACE | Detail alur paling dalam; hanya untuk investigasi mendalam | Developer yang menelusuri satu permintaan | Sangat besar |
DEBUG | Informasi diagnostik; nilai variabel, keputusan cabang | Developer saat development | Besar |
INFO | Peristiwa bisnis normal; transaksi, permintaan masuk | Operator dan audit | Sedang |
WARN | Kondisi mencurigakan yang ditangani; percobaan ulang, ambang terlampaui | Operator perlu mengamati | Kecil |
ERROR | Kegagalan fungsi yang tidak tertangani; perlu tindakan | Tim insiden, diperiksa segera | Sangat kecil |
Dua batas pada tabel itu paling sering dilanggar. Batas INFO/DEBUG: INFO bukan tempat menulis isi variabel — itu ranah DEBUG. Batas WARN/ERROR: jika kode berhasil memulihkan diri (retry berhasil, fallback dipakai), peristiwanya WARN; ERROR disimpan untuk kegagalan yang membuat permintaan atau proses gagal total.
Tabel Keputusan Pemilihan Level #
Sebelum menulis log.sesuatu(...), tiga pertanyaan berikut menentukan level secara hampir mekanis:
- Apakah pengguna atau proses bisnis terdampak? Tidak sama sekali →
TRACE/DEBUG. Ya, dan berjalan normal →INFO. - Apakah program berhasil menangani kondisinya? Berhasil dengan usaha ekstra (retry, fallback) →
WARN. Tidak berhasil →ERROR. - Apakah baris ini berguna jika muncul satu juta kali? Tidak → turunkan satu level atau hapus.
Sebagai contoh penerapan: koneksi database terputus lalu percobaan ulang ketiga berhasil → WARN (“koneksi terputus, pulih setelah 3 percobaan”). Percobaan ulang ketiga gagal dan permintaan dibatalkan → ERROR dengan nomor permintaan sebagai konteks. Keduanya menjelaskan kejadian yang sama, tetapi dampaknya berbeda sehingga levelnya berbeda.
Level per Package #
Saat insiden terjadi di modul pembayaran, kita ingin detail penuh dari modul itu saja — tanpa membanjiri log dengan DEBUG dari modul lain. Konfigurasi Logback mengizinkannya dengan elemen logger per package, dan program berikut mensimulasikan efeknya dengan dua logger mini yang ambangnya berbeda:
public class LevelPerPaket {
public static void main(String[] args) {
// Simulasi konfigurasi logback.xml:
// paket pembayaran DEBUG, akar aplikasi INFO
LogMini pembayaran = new LogMini("pembayaran", LogMini.Level.DEBUG);
LogMini katalog = new LogMini("katalog", LogMini.Level.INFO);
System.out.println("-- logger pembayaran (ambang DEBUG) --");
pembayaran.debug("Transaksi 1001 ditandai untuk verifikasi");
pembayaran.info("Transaksi 1001 selesai dalam 42 ms");
pembayaran.warn("OTP untuk akun budi dicoba 3 kali");
System.out.println();
System.out.println("-- logger katalog (ambang INFO) --");
katalog.debug("Query harga dieksekusi dengan filter aktif");
katalog.info("Katalog dimuat: 1.240 item");
katalog.warn("Cache katalog 85% terisi");
}
}Output yang Dihasilkan:
-- logger pembayaran (ambang DEBUG) --
[DEBUG] pembayaran - Transaksi 1001 ditandai untuk verifikasi
[INFO] pembayaran - Transaksi 1001 selesai dalam 42 ms
[WARN] pembayaran - OTP untuk akun budi dicoba 3 kali
-- logger katalog (ambang INFO) --
[INFO] katalog - Katalog dimuat: 1.240 item
[WARN] katalog - Cache katalog 85% terisiMari kita bedah simulasi tersebut:
new LogMini("pembayaran", Level.DEBUG): menirai baris<logger name="com.toko.pembayaran" level="DEBUG"/>padalogback.xml— hanya package pembayaran yang ambangnya diturunkan.- Baris
pembayaran.debug(...)yang tercetak: transaksi verifikasi kini terlihat, padahal pada ambang normal baris tersebut tersaring. new LogMini("katalog", Level.INFO): logger lain tetap pada ambang akar (INFO), sehingga pemanggilankatalog.debug(...)di bagian bawah tidak menghasilkan keluaran — volume log tetap terkendali.
Praktik ini membuat penyetelan level menjadi alat investigasi: turunkan ambang package bermasalah, amati, kembalikan setelah selesai.
Level saat Insiden dan saat Development #
Kebutuhan level berbeda antara lingkungan, dan konfigurasi seharusnya mengikuti:
- Produksi normal: akar
INFO;WARNdanERRORdinotifikasi;DEBUG/TRACEnonaktif. - Saat insiden: turunkan sementara package terkait ke
DEBUG(atauTRACEuntuk kasus tersulit), sebab jejak lengkap hanya tersedia saat kejadian berlangsung — log yang tidak ditulis tidak bisa ditulis ulang setelahnya. - Development:
DEBUGaktif untuk seluruh kode milik kita, dengan pengecualian pustaka yang berisik tetap diINFOagar konsol tetap terbaca.
Rahasia Tidak Boleh Masuk Log #
Satu aturan yang tidak mengenal pengecualian: kata sandi, token, nomor kartu, dan data pribadi lainnya tidak boleh ditulis ke log, pada level apa pun. Log hidup jauh lebih lama daripada memori program — berkas lama tersalin ke cadangan, dikumpulkan ke pusat log, dan dibaca oleh lebih banyak orang daripada yang kita bayangkan. Sebelum menulis log.debug("Login {}", ...) periksa apakah objek tersebut toString-nya memuat kredensial.
// Bentuk berisiko: seluruh objek permintaan bisa memuat kata sandi
log.debug("Permintaan login: {}", permintaan);
// Bentuk aman: hanya field non-rahasia yang dicatat
log.debug("Permintaan login untuk akun {} dari alamat {}", permintaan.email(), permintaan.alamatIp());Jika data sensitif memang harus hadir (misalnya nomor kartu untuk rekonsiliasi), catat bentuk tersamar seperti 5221****1234 melalui fungsi penyamaran yang diuji. Menjadikan “rahasia tidak masuk log” sebagai item wajib code review adalah cara termurah mencegah kebocoran data.
Kita kini memiliki bahasa level yang konsisten beserta cara menyetelnya per kebutuhan. Namun semua praktik sejauh ini masih reaktif — menunggu masalah muncul lalu menyelidikinya. Keluarga alat berikutnya bekerja di arah berlawanan: memeriksa kode sebelum program berjalan. Chapter berikutnya membuka topik static analysis.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
LevelPerPaket.java
Unduh berkaspublic class LevelPerPaket {
public static void main(String[] args) {
// Simulasi konfigurasi logback.xml:
// paket pembayaran DEBUG, akar aplikasi INFO
LogMini pembayaran = new LogMini("pembayaran", LogMini.Level.DEBUG);
LogMini katalog = new LogMini("katalog", LogMini.Level.INFO);
System.out.println("-- logger pembayaran (ambang DEBUG) --");
pembayaran.debug("Transaksi 1001 ditandai untuk verifikasi");
pembayaran.info("Transaksi 1001 selesai dalam 42 ms");
pembayaran.warn("OTP untuk akun budi dicoba 3 kali");
System.out.println();
System.out.println("-- logger katalog (ambang INFO) --");
katalog.debug("Query harga dieksekusi dengan filter aktif");
katalog.info("Katalog dimuat: 1.240 item");
katalog.warn("Cache katalog 85% terisi");
}
}
LogMini.java
Unduh berkasclass LogMini {
enum Level { TRACE, DEBUG, INFO, WARN, ERROR }
private final String nama;
private Level ambang;
LogMini(String nama, Level ambang) {
this.nama = nama;
this.ambang = ambang;
}
private boolean aktif(Level level) {
return level.ordinal() >= ambang.ordinal();
}
private void catat(Level level, String pesan) {
if (aktif(level)) {
System.out.println("[" + level + "] " + nama + " - " + pesan);
}
}
void debug(String pesan) { catat(Level.DEBUG, pesan); }
void info(String pesan) { catat(Level.INFO, pesan); }
void warn(String pesan) { catat(Level.WARN, pesan); }
}