Subscriber fmt() pada Chapter sebelumnya kita pakai sebagai kotak hitam: rantai with_max_level(Level::INFO) membuat output tampil, tetapi level tersebut terkunci di dalam kode. Chapter penutup section ini membuka kotak tersebut dan mengatur dua kenop utama subscriber sesuai kebutuhan nyata. Kenop pertama adalah filter: EnvFilter membaca variabel lingkungan RUST_LOG dengan sintaks yang sama seperti env_logger, sehingga verbosity bisa diubah per eksekusi tanpa kompilasi ulang. Kenop kedua adalah format: builder fmt() menawarkan beberapa preset tampilan, dari baris padat untuk production sampai tampilan verbose yang menampilkan lokasi kode setiap event.

Peran Subscriber: Filter dan Format #

Mekanismenya bekerja dalam dua tahap yang berurutan. Setiap event dan span yang dibangkitkan instrumentasi dikirim ke dispatcher global, lalu EnvFilter memeriksa level dan target kejadian terhadap directive dari RUST_LOG: kejadian yang ditolak dibuang di tahap ini, sebelum menyentuh pemformat. Kejadian yang lolos diteruskan ke layer format milik fmt(), yang merender timestamp, level, konteks span, target, pesan, dan field menjadi baris teks di stderr. Karena penyaringan terjadi sebelum pemformatan, kejadian yang dibuang tidak menimbulkan biaya pencetakan sama sekali.

Diagram di atas juga menjelaskan pembagian kerja yang kita pakai sepanjang dua Chapter terakhir: instrumentasi hanya mengirim data, EnvFilter memutuskan nasibnya, dan layer format menentukan wujud akhirnya. Satu hal praktis sebelum menulis kode: EnvFilter tidak aktif secara bawaan. Kita perlu menyalakan feature env-filter pada deklarasi dependency.

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

[dependencies]
tracing = { version = "0.1", features = ["attributes"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

Filter RUST_LOG dengan EnvFilter #

Program uji kita memakai dua module supaya penyaringan target terlihat, dengan pola pemasangan subscriber yang menjadi resep standar aplikasi tracing:

rust
use tracing::info;
use tracing_subscriber::EnvFilter;

mod gudang;

fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .init();

    info!(pesanan_id = 42, "pesanan diterima");
    tracing::debug!("membaca konfigurasi dari config.toml");

    let stok = gudang::cek_stok("buku");
    info!(stok, "pemeriksaan stok selesai");
}

Module gudang menyumbang dua event dari dalam fungsinya:

rust
pub fn cek_stok(barang: &str) -> u32 {
    let stok = if barang == "buku" { 12 } else { 0 };
    tracing::debug!(barang, "memeriksa stok");
    tracing::info!(barang, stok, "stok diperiksa");
    stok
}

Mari kita bedah pemasangan subscribernya terlebih dahulu:

  1. Baris 7-11 (tracing_subscriber::fmt() ... .init();): builder fmt() sekarang menerima filter lingkungan sebelum dipasang sebagai dispatcher global; urutan pemanggilan .with_env_filter bebas karena builder hanya mengumpulkan konfigurasi.
  2. Baris 9 (EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))): try_from_default_env membaca RUST_LOG dan mengembalikan Err ketika variabel tersebut tidak terisi atau berisi directive tidak valid; .unwrap_or_else mengganti kegagalan itu dengan EnvFilter::new("info") sehingga program tanpa RUST_LOG tetap menampilkan event info ke atas.
  3. Baris 13-17: empat event dengan level dan target berbeda menjadi bahan uji filter — dua di module utama, dua di myapp::gudang.
  4. Baris 2-4 pada gudang.rs: nilai stok dihitung lebih dulu, lalu event debug! dan info! mencatat parameter beserta hasilnya sebagai field.

Jalankan tanpa RUST_LOG untuk melihat fallback bekerja:

bash
cargo run

Output yang Dihasilkan:

text
2026-10-13T23:02:39.224528Z  INFO myapp: pesanan diterima pesanan_id=42
2026-10-13T23:02:39.224568Z  INFO myapp::gudang: stok diperiksa barang="buku" stok=12
2026-10-13T23:02:39.224574Z  INFO myapp: pemeriksaan stok selesai stok=12

Naikkan verbosity dengan sintaks level global yang sudah kita kenal dari env_logger:

bash
RUST_LOG=debug cargo run

Output yang Dihasilkan:

text
2026-10-13T23:02:39.250204Z  INFO myapp: pesanan diterima pesanan_id=42
2026-10-13T23:02:39.250240Z DEBUG myapp: membaca konfigurasi dari config.toml
2026-10-13T23:02:39.250245Z DEBUG myapp::gudang: memeriksa stok barang="buku"
2026-10-13T23:02:39.250249Z  INFO myapp::gudang: stok diperiksa barang="buku" stok=12
2026-10-13T23:02:39.250252Z  INFO myapp: pemeriksaan stok selesai stok=12

Directive target=level dan kombinasi dengan koma juga bekerja dengan grammar yang sama:

bash
RUST_LOG=myapp::gudang=debug cargo run

Output yang Dihasilkan:

text
2026-10-13T23:02:39.274977Z DEBUG myapp::gudang: memeriksa stok barang="buku"
2026-10-13T23:02:39.275000Z  INFO myapp::gudang: stok diperiksa barang="buku" stok=12
bash
RUST_LOG=warn cargo run

Output yang Dihasilkan:

text

Empat eksekusi tersebut memperlihatkan kisah lengkapnya: fallback info menampilkan tiga baris saat RUST_LOG kosong, level global debug membuka seluruh detail, directive myapp::gudang=debug membatasi output pada module gudang saja, dan ambang warn memutuskan seluruh event karena tidak ada satu pun yang mencapai level tersebut. Nilai timestamp di setiap blok diambil dari eksekusi nyata di mesin penulis dan akan berbeda di mesin Anda — subscriber mencatat waktu kejadian dengan ketelitian mikrodetik pada saat setiap event dibangkitkan.

Anatomi Satu Baris Output #

Ambil satu baris dari eksekusi debug di atas dan bedah bagiannya:

text
2026-10-13T23:02:39.250245Z DEBUG myapp::gudang: memeriksa stok barang="buku"

Tabel berikut memetakan setiap bagian:

BagianNilai pada contohKeterangan
Timestamp2026-10-13T23:02:39.250245ZWaktu UTC berketelitian mikrodetik
LevelDEBUGDitata rata kanan dalam lima karakter
Konteks span—Muncul di antara level dan target bila event berada dalam span aktif
Targetmyapp::gudangModule path asal event
Pesanmemeriksa stokTeks utama dari argumen terakhir macro
Fieldbarang="buku"Pasangan kunci-nilai terstruktur dari event

Perbandingan dengan env_logger pada Chapter sebelumnya menajamkan perbedaan keduanya. Format env_logger adalah [timestamp LEVEL target] pesan — tanpa field terstruktur dan dengan ketelitian detik — sedangkan format bawaan fmt() menempatkan field data setelah pesan dan mencatat mikrodetik. Ketika event dicatat di dalam span aktif, bagian konteks span seperti proses_pesanan{pesanan_id=42}: tampil di antara level dan target, persis seperti output Chapter Tracing Dasar.

Menyesuaikan Format Output #

Builder fmt() menerima penyesuaian berantai. Opsi pertama menyembunyikan target untuk baris yang lebih pendek — ganti rantai builder di awal main menjadi berikut:

rust
tracing_subscriber::fmt()
    .with_target(false)
    .with_env_filter(
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
    )
    .init();

Output yang Dihasilkan (dengan RUST_LOG=debug cargo run):

text
2026-10-13T23:03:04.393557Z  INFO pesanan diterima pesanan_id=42
2026-10-13T23:03:04.393584Z DEBUG membaca konfigurasi dari config.toml
2026-10-13T23:03:04.393588Z DEBUG memeriksa stok barang="buku"
2026-10-13T23:03:04.393592Z  INFO stok diperiksa barang="buku" stok=12
2026-10-13T23:03:04.393595Z  INFO pemeriksaan stok selesai stok=12

Kolom myapp: dan myapp::gudang: lenyap dari setiap baris, sehingga baris lebih ringkas — kompromi yang pantas ketika satu aplikasi hanya punya sedikit module. Opsi kedua mengubah wujud total menjadi preset pretty():

rust
tracing_subscriber::fmt()
    .pretty()
    .with_env_filter(
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
    )
    .init();

Output yang Dihasilkan (dengan RUST_LOG=debug cargo run):

text
  2026-10-13T23:03:04.735006Z  INFO myapp: pesanan diterima, pesanan_id: 42
    at src/main.rs:14

  2026-10-13T23:03:04.735039Z DEBUG myapp: membaca konfigurasi dari config.toml
    at src/main.rs:15

  2026-10-13T23:03:04.735044Z DEBUG myapp::gudang: memeriksa stok, barang: "buku"
    at src/gudang.rs:3

  2026-10-13T23:03:04.735049Z  INFO myapp::gudang: stok diperiksa, barang: "buku", stok: 12
    at src/gudang.rs:4

  2026-10-13T23:03:04.735052Z  INFO myapp: pemeriksaan stok selesai, stok: 12
    at src/main.rs:18

Format pretty() mengorbankan kerapatan demi keterbacaan: setiap event menempati blok tersendiri dengan indentasi, field dipisahkan koma, dan — bagian paling berharga saat menyelidiki masalah — baris at src/main.rs:14 yang menunjukkan lokasi kode persis tempat event ditulis. Nomor baris pada output di atas merujuk pada posisi event di berkas sumber setelah rantai builder diganti, dan nomor tersebut otomatis mengikuti kode kita selama kompilasi ulang.

Ringkasan #

tracing-subscriber melengkapi instrumentasi tracing dengan dua kenop yang siap produksi. EnvFilter membaca RUST_LOG dengan grammar level global, target=level, dan kombinasi koma — sintaks yang sama dengan env_logger — sementara pola try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")) menjamin fallback yang ramah ketika variabel lingkungan kosong. Layer format menentukan wujud baris: format bawaan menempatkan field terstruktur setelah pesan, with_target(false) memangkas kolom target, dan pretty() menukar kepadatan dengan blok verbose berisi lokasi kode. Dengan ini, perjalanan kita dari println! debug sampai sistem logging terkontrol lengkap: level yang terstandar, filter per module lewat variabel lingkungan, data terstruktur, dan konteks span — fondasi observability yang siap menopang aplikasi Rust skala berapa pun.