Structured logging artinya setiap log ditulis sebagai object dengan field yang tetap, biasanya satu baris JSON, bukan kalimat bebas. Bayangin buku tamu kantor. Kalau tiap tamu menulis kalimat bebas ("Saya Budi, datang jam 10 mau ketemu HRD"), mencari "semua tamu yang ke HRD hari ini" berarti membaca semuanya satu per satu. Kalau bukunya berbentuk formulir dengan kolom nama, jam, dan tujuan, kamu tinggal menyaring kolom tujuan. Log juga begitu, dan kolomnya disebut field.
🧪 Tebakan cepat: ada tiga log:
info"server start",warn"high memory", danerror"db connection failed". Kalau disaring dengan level minimumwarn, berapa log yang lolos? Jawabannya ada di bagian "Level log dan cara menyaringnya".
Dari kalimat ke JSON
// Kalimat bebas: susah dicari, susah dihitung
console.log("Login gagal untuk user 42, percobaan ke-3");
// Structured: satu baris JSON dengan field tetap
// {"level":"warn","timestamp":"2024-01-01T10:30:00.000Z","context":"auth","message":"Login gagal","userId":42,"attempt":3}
level: seberapa penting kejadiannya (dibahas di bawah).timestamp: kapan, dalam format ISO 8601 (new Date().toISOString()).context: bagian aplikasi mana yang menulis, misalnyaauthataupayment.message: kalimat pendek yang selalu sama untuk kejadian yang sama. Detail yang berubah-ubah (user mana, percobaan ke berapa) masuk ke field sendiri.
Di production, log dari banyak server dikumpulkan ke log management seperti ELK Stack (Elasticsearch, Logstash, Kibana), Grafana Loki, atau Datadog. Karena tiap log adalah JSON, kamu bisa bertanya level:error AND context:payment, atau menghitung berapa kali "Login gagal" untuk userId 42 minggu ini. Dengan kalimat bebas, yang bisa dilakukan cuma mencari potongan teks.
Membuat logger
Logger adalah object yang menyusun log dengan format yang sama setiap kali. Ini versi latihan. Timestamp-nya dibuat tetap ("2024-01-01") supaya output-nya selalu sama dan bisa dicek. Di aplikasi sungguhan, pakai new Date().toISOString().
function createLogger(context) {
function write(level, message, meta) {
const entry = {
level,
timestamp: "2024-01-01",
context,
message,
...meta, // field tambahan dari pemanggil
};
console.log(JSON.stringify(entry)); // satu baris JSON
}
return {
info: (message, meta) => write("info", message, meta),
warn: (message, meta) => write("warn", message, meta),
error: (message, meta) => write("error", message, meta),
};
}
const logger = createLogger("auth");
logger.info("User login", { userId: 42 });
// {"level":"info","timestamp":"2024-01-01","context":"auth","message":"User login","userId":42}
createLogger("auth")mengembalikan object dengan tiga method.contextdiingat oleh function di dalamnya, jadi kamu gak perlu menulisnya di setiap log....metamenyalin semua field tambahan ke dalam log.{ userId: 42 }jadi"userId":42, dan angka tetap angka, tanpa tanda kutip. Kalaumetagak diisi,...undefinedgak menambahkan apa-apa.JSON.stringify(entry)mengubah object jadi teks JSON satu baris. Tanpa itu, yang tercetak bukan JSON.
Level log dan cara menyaringnya
| Level | Kapan dipakai | Contoh |
|---|---|---|
trace |
jejak paling rinci, hampir gak pernah dinyalakan | masuk dan keluar tiap function |
debug |
detail untuk mencari bug, biasanya cuma di development | "cek password" |
info |
kejadian normal yang penting | "User login", "server start" |
warn |
gak normal, tapi aplikasi masih jalan | "high memory", "Retry attempt" |
error |
sesuatu gagal dan perlu ditangani | "db connection failed" |
Urutannya dari yang paling kritis ke paling cerewet (verbose): error, warn, info, debug, trace. Sebagian library menambahkan fatal di atas error untuk kegagalan yang menghentikan aplikasi.
Menyaring dengan level minimum artinya meloloskan level itu dan semua yang lebih kritis. Caranya, beri tiap level angka:
const LOG_LEVELS = { info: 1, warn: 2, error: 3 };
function filterLogs(logs, level) {
const minimum = LOG_LEVELS[level];
return logs.filter((log) => LOG_LEVELS[log.level] >= minimum);
}
| Log | LOG_LEVELS[log.level] |
>= 2 (warn)? |
|---|---|---|
info "server start" |
1 | gak lolos |
warn "high memory" |
2 | lolos |
error "db connection failed" |
3 | lolos |
Yang lolos 2 log, dan yang pertama warn. Itu jawaban tebakannya. Di production, minimum-nya biasanya info atau warn. Saat development, debug.
Correlation ID: satu request, banyak service
Satu klik "Bayar" bisa melewati beberapa service: API gateway, auth, lalu payment. Masing-masing menulis log-nya sendiri. Supaya bisa diikuti dari awal sampai akhir, service pertama membuat ID unik untuk request itu (correlation ID, sering bernama requestId atau trace ID), meneruskannya ke service berikutnya lewat header HTTP, dan setiap log menyertakannya:
{"level":"info","context":"gateway","message":"request masuk","requestId":"req-103","path":"/pay"}
{"level":"warn","context":"payment","message":"Retry attempt","requestId":"req-103","attempt":2}
{"level":"error","context":"payment","message":"Payment failed","requestId":"req-103","reason":"gateway_timeout"}
Cari requestId:"req-103", dan seluruh perjalanan request itu muncul berurutan, walaupun log-nya datang dari tiga service.
💡 Pakai library di aplikasi sungguhan
Di Node.js, library seperti pino dan winston sudah menulis JSON, mengatur level minimum, dan menambahkan timestamp. pino misalnya menulis {"level":30,"time":...,"msg":"User login"}: level-nya angka (30 untuk info) dan pesannya di field msg. Konsepnya sama dengan logger buatanmu.
⚠️ Jebakan yang sering ketemu
- Menggabung object dengan teks.
console.log("log: " + entry)mencetaklog: [object Object]. PakaiJSON.stringify(entry). - Menyaring dengan
===.filterLogs(logs, "warn")harus meloloskanerrorjuga. Pakai>=pada angka level-nya. - Semuanya dicatat sebagai
error. Kalau semua merah, alarm yang benar-benar penting ikut tenggelam. - Mencatat data sensitif. Password, token, nomor kartu, dan header
Authorizationgak boleh masuk log. CatatuserId, bukan email dan password-nya.
TL;DR: Structured logging menulis setiap log sebagai satu baris JSON dengan field tetap: level, timestamp, context, message, plus field tambahan seperti userId, sehingga log bisa disaring dan dihitung per field di tool seperti ELK, Loki, atau Datadog. Buat logger dengan createLogger(context) yang mengembalikan info, warn, error, dan mencetak JSON.stringify({ level, timestamp, context, message, ...meta }). Level dari paling kritis: error, warn, info, debug, trace. Menyaring dengan level minimum berarti LOG_LEVELS[log.level] >= LOG_LEVELS[level], jadi minimum warn meloloskan warn dan error. Sertakan correlation ID yang sama di setiap log satu request supaya perjalanannya bisa diikuti lintas service, dan jangan pernah mencatat password atau token.