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:

LevelMaknaPembaca yang relevanVolume tipikal
TRACEDetail alur paling dalam; hanya untuk investigasi mendalamDeveloper yang menelusuri satu permintaanSangat besar
DEBUGInformasi diagnostik; nilai variabel, keputusan cabangDeveloper saat developmentBesar
INFOPeristiwa bisnis normal; transaksi, permintaan masukOperator dan auditSedang
WARNKondisi mencurigakan yang ditangani; percobaan ulang, ambang terlampauiOperator perlu mengamatiKecil
ERRORKegagalan fungsi yang tidak tertangani; perlu tindakanTim insiden, diperiksa segeraSangat 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:

  1. Apakah pengguna atau proses bisnis terdampak? Tidak sama sekali → TRACE/DEBUG. Ya, dan berjalan normal → INFO.
  2. Apakah program berhasil menangani kondisinya? Berhasil dengan usaha ekstra (retry, fallback) → WARN. Tidak berhasil → ERROR.
  3. 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:

text
-- 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% terisi

Mari kita bedah simulasi tersebut:

  1. new LogMini("pembayaran", Level.DEBUG): menirai baris <logger name="com.toko.pembayaran" level="DEBUG"/> pada logback.xml — hanya package pembayaran yang ambangnya diturunkan.
  2. Baris pembayaran.debug(...) yang tercetak: transaksi verifikasi kini terlihat, padahal pada ambang normal baris tersebut tersaring.
  3. new LogMini("katalog", Level.INFO): logger lain tetap pada ambang akar (INFO), sehingga pemanggilan katalog.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; WARN dan ERROR dinotifikasi; DEBUG/TRACE nonaktif.
  • Saat insiden: turunkan sementara package terkait ke DEBUG (atau TRACE untuk kasus tersulit), sebab jejak lengkap hanya tersedia saat kejadian berlangsung — log yang tidak ditulis tidak bisa ditulis ulang setelahnya.
  • Development: DEBUG aktif untuk seluruh kode milik kita, dengan pengecualian pustaka yang berisik tetap di INFO agar 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.

java
// 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 berkas
java
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");
    }
}

LogMini.java

Unduh berkas
java
class 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); }
}