IDE debugger sangat berguna di lingkungan pengembangan, tetapi tidak tersedia di produksi. Saat aplikasi berjalan di server dengan ribuan request per detik, kita perlu cara lain untuk memahami apa yang terjadi di dalamnya. Logging adalah mekanisme mencatat jejak eksekusi ke file atau konsol dengan tingkat detail yang dapat diatur. Pada chapter ini kita akan membahas konsep dasar logging: level, format, structured logging, dan log aggregation. Kita juga akan membangun logger sederhana yang mendemonstrasikan konsep-konsep tersebut.
Level Log Standar #
Lima level log standar industri memberi kita cara mengategorikan pesan berdasarkan tingkat kepentingan:
| Level | Kapan dipakai | Contoh |
|---|---|---|
| TRACE | Detail ekstrem, hanya saat debugging mendalam | “Memasuki method X dengan argumen Y” |
| DEBUG | Informasi dev, mati di produksi | “Cache hit untuk key=…” |
| INFO | Peristiwa signifikan normal | “Pesanan #123 berhasil diproses” |
| WARN | Masalah yang dapat dipulihkan | “Koneksi db retry #2 berhasil” |
| ERROR | Kegagalan yang memengaruhi user | “Gagal menyimpan pesanan #456” |
Logger Sederhana #
Berikut implementasi logger sederhana yang mendukung pengaturan level aktif. Logger ini meniru pola SLF4J/Logback tanpa dependensi eksternal, sehingga kita dapat menjalankannya langsung dengan kotlinc.
import java.time.LocalDate
import java.time.format.DateTimeFormatter
// Simulasi logger sederhana yang mendukung level
enum class Level { TRACE, DEBUG, INFO, WARN, ERROR }
object LoggerSederhana {
private var levelAktif = Level.INFO
fun setLevel(level: Level) {
levelAktif = level
println("[konfigurasi] Level aktif: $levelAktif")
}
private fun log(level: Level, pesan: String) {
if (level.ordinal >= levelAktif.ordinal) {
val waktu = LocalDate.of(2026, 1, 15).toString()
println("$waktu [$level] $pesan")
}
}
fun trace(p: String) = log(Level.TRACE, p)
fun debug(p: String) = log(Level.DEBUG, p)
fun info(p: String) = log(Level.INFO, p)
fun warn(p: String) = log(Level.WARN, p)
fun error(p: String) = log(Level.ERROR, p)
}
data class Pesanan(val id: Int, val total: Double, val pelanggan: String)
fun main() {
println("=== Demo Logging Fundamentals ===")
LoggerSederhana.setLevel(Level.TRACE)
val daftar = listOf(
Pesanan(1, 150_000.0, "Budi"),
Pesanan(2, 75_000.0, "Siti"),
Pesanan(3, 320_000.0, "Dewi")
)
LoggerSederhana.info("Memulai proses ${daftar.size} pesanan")
for (p in daftar) {
LoggerSederhana.debug("Memproses pesanan id=${p.id} pelanggan=${p.pelanggan}")
if (p.total > 300_000.0) {
LoggerSederhana.warn("Pesanan id=${p.id} nilainya besar: ${p.total}")
}
LoggerSederhana.trace("Selesai proses pesanan id=${p.id}")
}
LoggerSederhana.info("Semua pesanan selesai diproses")
// Structured log: tampilkan dalam format key=value
println()
println("=== Format Structured Log ===")
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
for (p in daftar) {
val waktu = LocalDate.of(2026, 1, 15).format(formatter)
println("$waktu level=INFO event=pesanan_diproses id=${p.id} total=${p.total} pelanggan=${p.pelanggan}")
}
}Output yang Dihasilkan:
=== Demo Logging Fundamentals ===
[konfigurasi] Level aktif: TRACE
2026-01-15 [INFO] Memulai proses 3 pesanan
2026-01-15 [DEBUG] Memproses pesanan id=1 pelanggan=Budi
2026-01-15 [TRACE] Selesai proses pesanan id=1
2026-01-15 [DEBUG] Memproses pesanan id=2 pelanggan=Siti
2026-01-15 [TRACE] Selesai proses pesanan id=2
2026-01-15 [DEBUG] Memproses pesanan id=3 pelanggan=Dewi
2026-01-15 [WARN] Pesanan id=3 nilainya besar: 320000.0
2026-01-15 [TRACE] Selesai proses pesanan id=3
2026-01-15 [INFO] Semua pesanan selesai diproses
=== Format Structured Log ===
2026-01-15 level=INFO event=pesanan_diproses id=1 total=150000.0 pelanggan=Budi
2026-01-15 level=INFO event=pesanan_diproses id=2 total=75000.0 pelanggan=Siti
2026-01-15 level=INFO event=pesanan_diproses id=3 total=320000.0 pelanggan=DewiFilter Berdasarkan Level #
Pada output demo, kita lihat bahwa saat level aktif TRACE, semua pesan dari TRACE hingga ERROR muncul. Saat level aktif INFO, hanya INFO/WARN/ERROR yang muncul. Filter ini menghemat ruang penyimpanan dan bandwidth di produksi: pesan DEBUG dan TRACE tidak perlu dicatat, tetapi tetap tersedia saat kita butuh dengan mengubah level di konfigurasi.
Structured Logging #
Pada bagian akhir output demo, kita melihat format level=INFO event=pesanan_diproses id=1 total=150000.0 pelanggan=Budi. Format key=value seperti ini disebut structured logging. Keuntungan utamanya adalah mesin dapat mem-pesan dengan mudah: tools seperti ELK Stack (Elasticsearch + Logstash + Kibana) atau Grafana Loki dapat mencari berdasarkan field tertentu, misalnya pelanggan=Budi AND total>100000.
Log Aggregation #
Pada sistem produksi dengan banyak server, log dari setiap server harus dikumpulkan ke pusat agar dapat dicari. Beberapa stack populer:
- ELK Stack: Logstash mengirim log ke Elasticsearch; Kibana untuk visualisasi.
- Grafana Loki + Promtail: lebih ringan dari ELK, cocok untuk Kubernetes.
- Datadog / New Relic: solusi komersial dengan dashboard siap pakai.
Referensi Lanjutan #
- SLF4J — dokumentasi resmi SLF4J sebagai facade logging di JVM
- Logback — dokumentasi resmi Logback sebagai backend logging populer
Penutup #
Logging adalah fondasi observabilitas aplikasi. Pada chapter berikutnya kita akan menggunakan SLF4J sebagai façade dan Logback sebagai implementasi konkret — pola standar industri di ekosistem JVM — termasuk konfigurasi melalui logback.xml dan penggunaan appender console serta file.