Structured Logging — Debugging

Structured Logging Structured logging adalah teknik menulis log dalam format yang terstruktur (biasanya JSON) agar mudah dicari, difilter, dan dianalisis —…

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", dan error "db connection failed". Kalau disaring dengan level minimum warn, 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}

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}

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

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.

Yang akan kamu pelajari