Belajar ExpressJS - Logging & Debugging
Episode 17 of 28

Belajar ExpressJS - Logging & Debugging

Membangun observability aplikasi: morgan untuk request log, pino untuk structured logging dengan level dan request id, serta alur debugging interaktif dengan Node inspector dan debug.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Aplikasi yang berjalan adalah aplikasi yang perlu diselidiki saat gagal. Episode 17 membangun dua kemampuan investigasi: logging (merekam apa yang terjadi) dan debugging (mencari tahu mengapa sesuatu salah).

Mengapa ini bukan prioritas belakangan? Karena di produksi, satu-satunya jendela ke dalam aplikasi adalah log. Error handler episode 7 sudah memanggil console.error — sekarang kita menaikkan itu menjadi sistem yang benar: request log otomatis, structured logging yang bisa di-query, level log yang terkelola, dan request id yang menelusuri satu request dari masuk hingga keluar.

Request Logging dengan morgan

morgan (dikenalkan episode 5) mencatat tiap request. Konfigurasi level format dan skip dapat dibuat pintar:

JSmorgan dengan format dan filter
import morgan from "morgan"
 
app.use(
  morgan("combined", {
    skip: (req, res) =>
      req.path === "/health" || res.statusCode < 400,
  })
)

Format "combined" menghasilkan baris log standar Apache:

Contoh output morgan
::1 - - [16/Aug/2026:12:00:01 +0700] "GET /users HTTP/1.1" 200 512 "-" "Mozilla/5.0 ..."

Pola skip di atas menghindari spam: request sukses dan ping /health tidak dicatat, sementara error 4xx/5xx selalu tercatat. Di produksi, lewati log body dan token — mereka hanya akan membanjiri log dengan data sensitif.

Structured Logging dengan pino

Mengapa Structured

console.log mencetak teks bebas yang sulit di-query. Structured logging mencetak JSON — setiap baris berisi field yang bisa difilter (status, latency, error). Pino adalah pilihan populer karena cepat (overhead rendah) dan produksi-default:

Install pino
npm install pino
JSsrc/utils/logger.js - instance pino
import pino from "pino"
import { config } from "../config.js"
 
export const logger = pino({
  level: config.logging.level,
  redact: {
    paths: ["req.headers.authorization", "req.body.password", "res.body.token"],
    censor: "[REDACTED]",
  },
})

Dua opsi penting:

  • level mengontrol verbositas — debug di development, info di produksi (dari config.js episode 16).
  • redact menyensor field sensitif — bukti bahwa log tidak akan pernah berisi token atau password, meski ada yang salah memanggil.

Memakai Logger di Handler

JSpino di dalam handler
import { logger } from "../utils/logger.js"
 
export const getUser = async (req, res) => {
  const started = Date.now()
  try {
    const user = await User.findById(req.params.id).lean()
    if (!user) throw new AppError(404, "USER_NOT_FOUND", "User tidak ditemukan")
    res.json({ data: user })
  } finally {
    logger.info({
      userId: req.user?.id,
      latencyMs: Date.now() - started,
      path: req.path,
    }, "getUser selesai")
  }
}

Baris log yang dihasilkan (dalam bentuk JSON) memungkinkan pertanyaan seperti: "tunjukkan semua request getUser dengan latency di atas 500ms dari user X" — sesuatu yang mustahil dengan console.log biasa.

pino-pretty untuk Development

JSON mentah sulit dibaca manusia. Di development, cetak dengan pino-pretty:

Install pino-pretty
npm install -D pino-pretty
Script npm untuk dev
{
  "scripts": {
    "dev": "node --watch src/server.js | pino-pretty",
    "start": "node src/server.js"
  }
}

pino-pretty mengubah JSON menjadi baris berwarna yang mudah dipindai — tanpa mengubah kode, karena pino sudah menghasilkan JSON sejak awal.

Request ID: Menelusuri Satu Request

Di aplikasi yang sibuk, log dari banyak request tercampur. Request id mengelompokkan semua log milik satu request — trik sederhana dengan middleware custom:

JSMiddleware request id
import { randomUUID } from "node:crypto"
 
export const requestId = (req, res, next) => {
  const id = req.get("X-Request-Id") || randomUUID()
  req.id = id
  res.setHeader("X-Request-Id", id)
  next()
}

Dipasang paling awal (di atas morgan) dan dipakai di logger:

JSLogger memakai request id
import { logger } from "../utils/logger.js"
 
export const errorHandler = (err, req, res, next) => {
  const status = err.status || 500
  logger.error({
    requestId: req.id,
    status,
    path: req.path,
    err: err.stack,
  })
  res.status(status).json({ error: err.code || "INTERNAL_ERROR", message: err.message })
}

Saat user melapor "request dengan id abc-123 gagal", kalian tinggal menggrep id itu di seluruh log dan melihat perjalanan lengkap request — termasuk error handler episode 7 yang kini terhubung dengan sistem ini.

Tip

Konsumen API juga bisa mengirim X-Request-Id sendiri; server meneruskannya ke log. Ini menciptakan korelasi ujung-ke-ujung antara client dan server — pola yang di episode 25 diperluas ke distributed tracing.

Debugging: Node Inspector

Debugger Interaktif

Node.js punya inspector bawaan — tanpa dependency tambahan:

Jalankan server dengan inspector
node --inspect src/server.js
# tampilkan Chrome DevTools: chrome://inspect

Jalankan dengan breakpoint:

Breakpoint di baris 5
node --inspect-brk src/server.js

--inspect-brk berhenti di baris pertama kode sebelum dieksekusi — kalian bisa menambahkan breakpoint di DevTools, lalu resume. Debugger ini menunjukkan stack, variabel lokal, dan bisa mengevaluasi ekspresi di titik berhenti.

Alur Debug yang Efektif

JSBreakpoint untuk menyelidiki req
export const createUser = async (req, res) => {
  debugger  // Node inspector berhenti di sini
  const { name, email } = req.body
  // periksa nilai name, email, dan req.headers
  const user = await User.create({ name, email })
  res.status(201).json({ data: user })
}

Aturan praktis debugging:

  1. Reproduksi masalah (curl dengan input yang sama).
  2. Pasang breakpoint di awal handler.
  3. Telusuri nilai req.body, req.params, dan hasil query.
  4. Perbaiki, jalankan ulang dengan input yang sama.

debug Package

Untuk log debug yang bisa dihidupkan/matikan per modul tanpa mengubah kode, pakai debug:

Install debug
npm install debug
JSDebug per namespace
import debug from "debug"
 
const dbg = debug("app:auth")
 
export const login = async (req, res) => {
  dbg("login dimulai untuk %s", req.body?.email)
  // ...
  dbg("login berhasil untuk %s", req.body.email)
}
Aktifkan namespace tertentu
DEBUG=app:auth npm run dev

Namespace (app:auth, app:users) membuat log debug granular: matikan semua, nyalakan hanya yang sedang diselidiki.

Common Pitfalls

Log Body atau Token

Body request berisi data pribadi, header berisi token. Selalu redact (pino redact) dan jangan pernah mencatat body login.

Log untuk Segalanya

Log di tiap baris kode = noise. Log peristiwa bernilai: awal/akhir request, error, transisi state penting. Verbositas diturunkan di produksi lewat level.

Menggantung di Inspector di Produksi

--inspect membuka port debug yang berbahaya jika terekspos. Jangan pernah menjalankannya di produksi tanpa pembatasan akses.

Note

Urutan observasi: error handler (episode 7) → config (episode 16) → logging pino + request id (episode 17). Di episode 21 dan 25 kita tambahkan metrik dan tracing — seluruhnya berpijak pada struktur log JSON yang konsisten ini.

Penutup

Episode 17 membuat aplikasi bisa diselidiki: morgan untuk request log otomatis, pino untuk structured JSON dengan level dan redaction, request id untuk korelasi, dan Node inspector + debug untuk debugging interaktif.

Inti yang harus dibawa pulang:

  • morgan "combined" mencatat request; lewati /health dan status sukses agar tidak spam.
  • pino menghasilkan log JSON yang bisa di-query; redact menyensor field sensitif.
  • pino-pretty untuk development, JSON mentah untuk produksi.
  • Request id (header X-Request-Id) mengelompokkan semua log satu request.
  • Debug dengan node --inspect + debugger, dan namespace debug yang bisa dinyalakan selektif.
  • Jangan pernah menjalankan inspector di produksi.

Di episode 18 selanjutnya kita akan mengeraskan keamanan: security best practices — Helmet, konfigurasi CORS, sanitasi input, SQLi/NoSQLi, XSS, CSRF, dan secure headers. Sampai jumpa di episode 18!

Belajar ExpressJS - Logging & Debugging | Belajar ExpressJS