SimpleLogger buatan sendiri pada Chapter sebelumnya membuktikan bahwa trait Log mudah diimplementasikan, tetapi konfigurasi logger tersebut tertanang di dalam kode: mengubah ambang level berarti mengubah kode lalu mengompilasi ulang. Kebutuhan nyata di lapangan justru sebaliknya — program yang sama harus bisa berbicara ringkas saat berjalan normal dan berbicara detail saat kita sedang menyelidiki masalah. Crate env_logger adalah implementasi facade log yang paling banyak dipakai untuk kebutuhan tersebut: satu pemanggilan init() di awal main, lalu variabel lingkungan RUST_LOG mengendalikan seluruh level dan sumber pesan tanpa menyentuh kode sama sekali. Chapter ini memetakan seluruh perilaku env_logger lewat eksekusi nyata dengan berbagai nilai RUST_LOG.

Menyiapkan Proyek dengan env_logger #

Kita pakai aplikasi kasir dengan dua module supaya penyaringan per module terlihat. Struktur proyeknya sederhana:

text
myapp/
├── Cargo.toml
└── src/
    ├── main.rs
    └── pembayaran.rs

Berkas Cargo.toml memuat dua dependency:

toml
[package]
name = "myapp"
version = "0.1.0"
edition = "2024"

[dependencies]
log = "0.4"
env_logger = "0.11"

Kode utama memasang logger di baris pertama main, lalu mencatat empat kejadian dengan level berbeda:

rust
mod pembayaran;

fn main() {
    env_logger::init();

    log::error!("berkas config.toml tidak ditemukan, memakai nilai bawaan");
    log::info!("aplikasi kasir dimulai");
    log::debug!("memuat 3 harga barang dari memori");

    let total = pembayaran::proses(42, 150_000);
    log::info!("total belanja pesanan 42: Rp{}", total);
}

Module pembayaran menambahkan tiga catatan dari dalam fungsi:

rust
pub fn proses(pesanan_id: u64, nominal: u64) -> u64 {
    log::debug!("memvalidasi pesanan {}", pesanan_id);
    log::trace!("nominal mentah: {}", nominal);

    let pajak = nominal / 10;
    log::info!("pajak pesanan {} dihitung: Rp{}", pesanan_id, pajak);

    nominal + pajak
}

Mari kita bedah kode di atas sebelum mengeksekusinya:

  1. Baris 1 (mod pembayaran;): deklarasi module membuat berkas src/pembayaran.rs menjadi bagian dari crate; seluruh macro di dalamnya otomatis mendapat target myapp::pembayaran.
  2. Baris 4 (env_logger::init();): pemanggilan tunggal ini membaca RUST_LOG dari lingkungan, membangun filter dari nilai tersebut, lalu memasang env_logger sebagai implementasi Log global. Setelah baris ini, semua macro crate log — termasuk yang berasal dari library pihak ketiga — mengalir ke env_logger.
  3. Baris 6-8 (log::error!, log::info!, log::debug!): tiga kejadian di module utama dengan target myapp; ketiganya sengaja memakai level berbeda supaya ambang penyaringan terasa.
  4. Baris 10 (let total = pembayaran::proses(42, 150_000);): pemanggilan fungsi lintas module memicu catatan dengan target myapp::pembayaran.
  5. Baris 2-3 pada pembayaran.rs: catatan debug! dan trace! menandai langkah validasi dan data mentah — jenis detail yang hanya kita butuhkan saat menyelidiki.
  6. Baris 6 pada pembayaran.rs: catatan info! menandai hasil perhitungan pajak yang relevan secara operasional.

Menjalankan Tanpa RUST_LOG #

Eksekusi pertama kita lakukan tanpa mengatur RUST_LOG sama sekali:

bash
cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan

Hanya satu baris yang muncul. Ketika RUST_LOG tidak terisi, env_logger memakai level bawaan error, sehingga dari tujuh catatan program hanya pesan error! yang lolos. Perilaku ini masuk akal untuk production — pengguna biasa tidak perlu dibebani detail internal — tetapi perilaku yang sama sering membuat pemula mengira logging-nya rusak.

Menaikkan Verbosity dengan Level Global #

Isi RUST_LOG dengan nama level untuk menaikkan ambang secara global:

bash
RUST_LOG=info cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan
[2026-10-13T23:03:38Z INFO  myapp] aplikasi kasir dimulai
[2026-10-13T23:03:38Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000
[2026-10-13T23:03:38Z INFO  myapp] total belanja pesanan 42: Rp165000

Naikkan satu tingkat lagi untuk melihat detail pengembang:

bash
RUST_LOG=debug cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan
[2026-10-13T23:03:38Z INFO  myapp] aplikasi kasir dimulai
[2026-10-13T23:03:38Z DEBUG myapp] memuat 3 harga barang dari memori
[2026-10-13T23:03:38Z DEBUG myapp::pembayaran] memvalidasi pesanan 42
[2026-10-13T23:03:38Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000
[2026-10-13T23:03:38Z INFO  myapp] total belanja pesanan 42: Rp165000

Dan level paling verbose untuk melihat semua denyut:

bash
RUST_LOG=trace cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan
[2026-10-13T23:03:38Z INFO  myapp] aplikasi kasir dimulai
[2026-10-13T23:03:38Z DEBUG myapp] memuat 3 harga barang dari memori
[2026-10-13T23:03:38Z DEBUG myapp::pembayaran] memvalidasi pesanan 42
[2026-10-13T23:03:38Z TRACE myapp::pembayaran] nominal mentah: 150000
[2026-10-13T23:03:38Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000
[2026-10-13T23:03:38Z INFO  myapp] total belanja pesanan 42: Rp165000

Tiga eksekusi di atas memperlihatkan semantik ambang dari Chapter Crate log: RUST_LOG=debug menampilkan debug beserta semua level yang lebih penting, dan setiap kenaikan satu tingkat menambah baris tanpa pernah mengurangi yang lama. Sekarang saatnya membaca format barisnya. Pola cetak env_logger adalah [timestamp LEVEL target] pesan — timestamp dalam format UTC dengan ketelitian detik, level ditata dalam lebar lima karakter sehingga kolom target tetap rapi (perhatikan INFO diikuti dua spasi sedangkan DEBUG hanya satu), dan target adalah module path asal macro. Nilai timestamp pada output di atas diambil dari satu eksekusi nyata di mesin penulis; di mesin Anda angka tersebut pasti berbeda pada setiap eksekusi, karena env_logger selalu mencatat waktu kejadian sebenarnya, bukan waktu kompilasi.

Menyaring per Module #

Level global masih boros ketika kita hanya curiga pada satu bagian program. RUST_LOG menerima directive target=level untuk membatasi pesan pada module tertentu:

bash
RUST_LOG=myapp::pembayaran=debug cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z DEBUG myapp::pembayaran] memvalidasi pesanan 42
[2026-10-13T23:03:38Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000

Output menyusut menjadi dua baris — keduanya berasal dari myapp::pembayaran — sementara keempat pesan dari module utama hilang. Directive myapp::pembayaran=debug berarti: izinkan level debug ke atas hanya untuk target myapp::pembayaran, dan matikan target lainnya. Kita juga bisa menggabungkan ambang global dan pengecualian module dengan koma:

bash
RUST_LOG=warn,myapp=debug cargo run

Output yang Dihasilkan:

text
[2026-10-13T23:03:38Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan
[2026-10-13T23:03:38Z INFO  myapp] aplikasi kasir dimulai
[2026-10-13T23:03:38Z DEBUG myapp] memuat 3 harga barang dari memori
[2026-10-13T23:03:38Z DEBUG myapp::pembayaran] memvalidasi pesanan 42
[2026-10-13T23:03:38Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000
[2026-10-13T23:03:38Z INFO  myapp] total belanja pesanan 42: Rp165000

Directive pertama (warn) menetapkan ambang global warn, lalu directive kedua (myapp=debug) menimpa ambang menjadi debug untuk target myapp beserta seluruh turunannya — itulah sebabnya pesan dari myapp::pembayaran ikut lolos. Pencocokan target bekerja seperti prefiks path: directive myapp=debug berlaku untuk myapp, myapp::pembayaran, dan module-module lain di bawahnya. Aturan prefiks ini juga berlaku untuk crate pihak ketiga: nama crate menjadi awalan target, sehingga directive hyper=warn membungkam seluruh pesan bawaan library HTTP populer tersebut.

Sintaks lengkap RUST_LOG sebenarnya adalah daftar directive yang dipisahkan koma, dan program produksi lazim memakai tiga lapis sekaligus. Sebuah web server dengan database bisa berjalan dengan RUST_LOG=info,hyper=warn,sqlx=debug,myapp=debug: ambang global info untuk semua crate, hyper diturunkan ke warn karena riuh, sqlx dan myapp dinaikkan ke debug karena sedang diselidiki. Directive paling spesifik selalu menang atas ambang global, dan pengecualian bertingkat semacam ini tidak menuntut perubahan kode sedikit pun — cukup satu variabel lingkungan yang diubah per eksekusi.

Log Mengalir ke stderr #

env_logger menulis semua pesannya ke stderr, bukan stdout. Kita bisa membuktikannya dengan membuang masing-masing stream:

bash
RUST_LOG=info cargo run 2>/dev/null

Output yang Dihasilkan:

text
bash
RUST_LOG=info cargo run 2>&1 1>/dev/null

Output yang Dihasilkan:

text
[2026-10-13T23:03:42Z ERROR myapp] berkas config.toml tidak ditemukan, memakai nilai bawaan
[2026-10-13T23:03:42Z INFO  myapp] aplikasi kasir dimulai
[2026-10-13T23:03:42Z INFO  myapp::pembayaran] pajak pesanan 42 dihitung: Rp15000
[2026-10-13T23:03:42Z INFO  myapp] total belanja pesanan 42: Rp165000

Perintah pertama membuang stderr (2>/dev/null) dan hasilnya kosong — tidak ada satu baris log pun di stdout. Perintah kedua membalik arah: 2>&1 mengarahkan stderr ke layar sementara 1>/dev/null membuang stdout, dan keempat baris log muncul utuh. Pemisahan ini melanjutkan keputusan SimpleLogger pada Chapter sebelumnya: hasil program tetap bersih di stdout untuk dikonsumsi skrip, sedangkan catatan berjalan di stderr untuk dibaca manusia.

Ringkasan #

env_logger mengubah satu baris env_logger::init() menjadi sistem logging yang sepenuhnya dikendalikan RUST_LOG: tanpa variabel tersebut filter diam di level error, nama level seperti info dan trace menaikkan ambang global, directive target=level membatasi pesanan pada satu module beserta turunannya, dan beberapa directive dipisahkan koma untuk kombinasi ambang global plus pengecualian. Semua pesan mengalir ke stderr dengan format [timestamp LEVEL target] pesan. Kendali berbasis teks tersebut menyelesaikan aplikasi sederhana, tetapi pola pikir “satu baris = satu kejadian lepas” masih kehilangan konteks alur — masalah yang diselesaikan span dan event dari crate tracing pada Chapter berikutnya.