Logging

Logging terstruktur dengan pino serta level log.

Daftar isi

Logging pada aplikasi Node produksi berbeda dari sekadar console.log. Pada production, log diharapkan dapat dikumpulkan, difilter, dan dicari oleh sistem pemrosesan log seperti Loki atau Elasticsearch, sehingga keluaran harus berupa baris JSON yang dapat diuraikan mesin. Pustaka pino dirancang untuk kebutuhan ini dengan menulis JSON ringkas secara sinkron dan cepat, tanpa memblokir event loop. Setelah membaca topik ini, pembaca dapat membuat logger pino, mengatur level log, serta memahami mengapa log terstruktur lebih sesuai untuk production dibanding console.log.

Logger pino dasar#

Pembuatan logger pino dilakukan dengan memanggil pino(options) yang mengembalikan objek logger. Objek ini memiliki metode per level, yaitu trace, debug, info, warn, error, dan fatal, yang masing-masing menulis satu baris JSON ke stdout. Opsi base menambahkan field yang disematkan pada setiap baris log, biasanya berisi identitas aplikasi dan lingkungan. Opsi level menentukan ambang batas: pesan dengan level di bawah ambang tidak ditulis.

js logger.js
// tidak-dijalankan: jalankan lewat blok bash berikutnya setelah pasang pino di /tmp
const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  base: { app: 'catatan-suhu', env: process.env.NODE_ENV || 'development' },
  timestamp: false
});

logger.info('aplikasi siap');
logger.warn({ latensiMs: 230 }, 'latensi penulisan tinggi');
logger.error({ kode: 'ECONNREFUSED' }, 'gagal menyambung ke sensor');
logger.debug('pesan debug ini tidak muncul pada level info');
bash
# buat folder demo di /tmp
mkdir -p /tmp/logging-demo
# salin logger.js dari bundle topik
cp logger.js /tmp/logging-demo/
# pindah ke folder demo
cd /tmp/logging-demo
# inisialisasi package.json
npm init -y >/dev/null 2>&1
# pasang pino
npm install pino >/dev/null 2>&1
# jalankan logger
node logger.js
text Keluaran
{"level":30,"app":"catatan-suhu","env":"development","msg":"aplikasi siap"}
{"level":40,"app":"catatan-suhu","env":"development","latensiMs":230,"msg":"latensi penulisan tinggi"}
{"level":50,"app":"catatan-suhu","env":"development","kode":"ECONNREFUSED","msg":"gagal menyambung ke sensor"}

Setiap baris keluaran merupakan JSON yang dapat diuraikan langsung oleh JSON.parse. Field level berupa angka, yaitu 30 untuk info, 40 untuk warn, 50 untuk error, dan 60 untuk fatal. Field msg berisi pesan yang diberikan sebagai argumen kedua, sedangkan objek yang diberikan sebagai argumen pertama disebarkan sebagai field tambahan, contohnya latensiMs dan kode pada keluaran di atas. Pesan debug tidak muncul karena ambang level diatur ke info, sehingga level 20 dilewati.

Level log dan penyaringan#

Pino mengenal enam level dengan ambang numerik baku: trace (10), debug (20), info (30), warn (40), error (50), dan fatal (60). Pesan dengan level di bawah ambang tidak ditulis, sehingga pengaturan level: 'info' menyembunyikan trace dan debug, sedangkan pengaturan level: 'debug' menampilkan keduanya. Pengubahan ambang dapat dilakukan saat pembuatan logger melalui opsi level, atau saat runtime melalui logger.level = 'debug', yang berguna ketika level ingin diturunkan sementara untuk diagnosis masalah.

Ambang level sering dikendalikan oleh variabel lingkungan LOG_LEVEL agar dapat diubah tanpa mengubah kode. Pada logger di atas, nilai LOG_LEVEL dibaca lewat process.env.LOG_LEVEL dengan fallback 'info'. Praktik ini sejalan dengan prinsip 12-factor yang menempatkan konfigurasi pada lingkungan, sehingga production dapat berjalan pada level info sementara staging pada level debug tanpa perubahan kode.

bash
# jalankan ulang dengan level debug
cd /tmp/logging-demo
LOG_LEVEL=debug node logger.js
text Keluaran
{"level":30,"app":"catatan-suhu","env":"development","msg":"aplikasi siap"}
{"level":40,"app":"catatan-suhu","env":"development","latensiMs":230,"msg":"latensi penulisan tinggi"}
{"level":50,"app":"catatan-suhu","env":"development","kode":"ECONNREFUSED","msg":"gagal menyambung ke sensor"}
{"level":20,"app":"catatan-suhu","env":"development","msg":"pesan debug ini tidak muncul pada level info"}

Pengaturan LOG_LEVEL=debug menurunkan ambang ke 20, sehingga baris debug muncul dengan field level: 20. Pemisahan antara pesan dan konteks pada pino, yaitu argumen objek pertama dan argumen pesan kedua, membuat pencarian log menjadi sederhana. Filter latensiMs > 200 dapat dijalankan langsung pada field JSON tanpa penguraian teks bebas.

Log terstruktur vs console.log#

console.log menulis teks ke stdout dengan format yang ditujukan untuk manusia, sedangkan pino menulis JSON yang ditujukan untuk mesin. Perbedaan ini menentukan apakah log dapat diproses otomatis. Sebuah baris Server berhasil jalan di port 8080 tidak dapat difilter berdasarkan port tanpa ekspresi reguler, sedangkan baris {"level":30,"port":8080,"msg":"server siap"} memungkinkan filter port == 8080 secara langsung pada pipeline log.

Pino juga menulis secara sinkron dengan biaya rendah, sehingga teks keluar tanpa buffering yang dapat tertahan saat stdout penuh. Keluaran console.log pada Node ditulis ke stream Writable yang dapat terkena backpressure, yaitu kondisi saat penulis lebih cepat daripada pembaca sehingga penulisan tertunda dan berpotensi memengaruhi event loop secara tak terduga. Pada production dengan throughput tinggi, biaya ini menjadi alasan utama migrasi dari console.log ke pino.

Kesalahan umum#

Pemakaian console.log untuk production adalah kesalahan yang paling sering. console.log tidak menambah timestamp, tidak menyertakan level, dan menulis objek dengan urutan field yang tidak stabil. Sistem pemrosesan log yang mengharapkan JSON terstruktur akan gagal mengurai keluaran console.log, sehingga log tidak dapat dicari atau difilter.

Kesalahan lain adalah memasukkan data sensitif ke dalam log. Token, kata sandi, dan kunci API yang dicatat sebagai field JSON dapat bocor ke sistem log yang diakses banyak pihak. Pino menyediakan opsi redact berupa daftar path field yang otomatis diganti dengan string '[Redacted]', sehingga data sensitif tidak ikut tersimpan. Daftar redact sebaiknya mencakup field seperti req.headers.authorization, password, dan token.

Ringkasan#

  • Pino menulis satu baris JSON per peristiwa dengan field level, msg, dan field konteks dari argumen objek pertama, sehingga log dapat dikumpulkan, difilter, dan dicari oleh sistem pemrosesan log.
  • Enam level baku pino adalah trace, debug, info, warn, error, dan fatal, dengan ambang numerik 10 sampai 60. Pesan di bawah ambang level tidak ditulis.
  • Ambang level sering dikendalikan oleh variabel lingkungan LOG_LEVEL agar dapat diubah tanpa mengubah kode, sesuai prinsip 12-factor.
  • console.log tidak cocok untuk production karena tidak menambah timestamp, tidak stabil urutan fieldnya, dan dapat memengaruhi event loop saat stdout di-backpressure.

Referensi#

  • pino — getpino.io — Dokumentasi resmi pino yang mencakup opsi logger, level, dan redact.
  • Logger — Node.js — Catatan resmi tentang util.log dan keterbatasan console.log pada Node.