Skip to content

[TASK-PINO-LOGGER-REFACTOR] Standarisasi Structured Logging dengan Pino dan Request Lifecycle Tracking ​

Modul: Pengangkut | Tipe: Refactoring | Stage Saat Ini: STAGING | Tanggal Dokumen: 2026-09-03


1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: TASK-PINO-LOGGER-REFACTOR

  • Sumber Instruksi: Task Clean Code / Architecture Standardization

  • Dokumen Referensi / Lampiran: Tidak ada

  • Task / Dependensi Terkait (Cross-Service): Tidak ada

  • Modul Terkait: Pengangkut

  • Service / Repository: pengangkut-backend

  • PIC Pengembang: Hermansyah Efendi

  • Latar Belakang & Ringkasan Kebutuhan: Standarisasi sistem logging aplikasi pengangkut-backend dengan mengganti penggunaan console.log, console.warn, dan console.error yang tidak terstruktur ke Pino structured JSON logger. Refactoring ini mencakup implementasi HTTP Request/Response logger middleware terisolasi menggunakan AsyncLocalStorage untuk request context tracking (request_id), pencatatan lifecycle incoming request dan outgoing response beserta latensi durasi, otomatisasi injection request_id pada payload error response (HTTP status >= 400), formatting query SQL parametrik untuk kemudahan debugging developer, serta penyesuaian unit test suite mocking.

  • Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):

    • [x] Standardisasi konfigurasi Pino logger (app/helpers/logger.js) dengan support LOG_LEVEL environment variable dan AsyncLocalStorage context mixin request_id.
    • [x] Implementasi HTTP Request/Response lifecycle logger middleware (app/middlewares/http_logger.js) yang mengisolasi setiap request masuk dengan UUID unik, mencatat log incoming request (method, url, IP, user-agent) dan outgoing response (status code, latency duration, level warn/error otomatis).
    • [x] Intersepsi res.json pada http_logger middleware untuk otomatis menyematkan request_id pada response body saat HTTP status code >= 400.
    • [x] Implementasi fungsi formatter query SQL parametrik (formatQueryForLog) di database connection layer (app/connection.js & app/config/database/database.js) untuk log query multiline yang siap di-copy ke DBMS.
    • [x] Refactoring seluruh pemanggilan console.* di layer controllers, models, services, helpers, dan middlewares menjadi instance child logger Pino.
    • [x] Pembaruan dan penambahan unit test suites Vitest (tests/middlewares/http_logger.test.js, tests/connection.test.js, dll.) untuk memverifikasi logging dan behavior middleware.

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-08-11 14:25Hermansyah EfendiDEPLOYEDBranch: feat/pino-logger
2Staging (UAT)2026-09-09 11:34achmad fauziDEPLOYEDRelease Candidate v1.18.2 via MR !2080. Co-deployed: [TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026]
3Production——PENDING—

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-09-09StagingPromosi ke STAGING via MR !2080 (Release Candidate v1.18.2)Hermansyah Efendi
2026-09-03DevelopmentInitial log createdHermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    1. HTTP Request Isolation & Lifecycle Logging: Middleware httpLogger membungkus seluruh lifecycle request ke dalam loggerStorage.run({ request_id }, ...). Mencatat log event Incoming request dengan atribut method, url, ip, dan user_agent, serta menangkap event res.on("finish") untuk mencatat status_code, kalkulasi response_time (nanosecond to millisecond), dan severity level (info/warn/error).
    2. Error Correlation Context: Mengintersepsi method res.json agar seluruh response error (statusCode >= 400) menyertakan atribut request_id pada payload JSON body untuk mempermudah QA/developer merekonsiliasi error klien dengan log server.
    3. Database Query Debug Formatting: Menambahkan fungsi formatQueryForLog yang mereplace placeholder parameterized query ($1, $2, ...) menjadi format literal SQL utuh pada log level debug di file app/connection.js dan app/config/database/database.js.
    4. Clean Code & Elimination of console.*: Menghapus seluruh direct call console.log, console.warn, dan console.error pada core application code, digantikan oleh structured logger instance parentLogger.child({ service_name: "..." }).
  • Perubahan Database (DDL / DML):

    sql
    -- Tidak ada migration file yang terdeteksi. Task refactoring logging dan runtime middleware.
  • Kontrak API & Response Payload: Tidak ada penambahan endpoint baru atau perubahan breaking change pada payload bisnis. Terdapat penambahan field standar request_id pada seluruh response error (statusCode >= 400).

    [ALL ERROR RESPONSES] {*} ​

    • Deskripsi: Seluruh endpoint yang mengembalikan HTTP Status Code >= 400 otomatis disematkan atribut korelasi request_id.
    • Autentikasi / Guard: Sesuai guard route masing-masing endpoint
    • Response Error (400 Bad Request / 404 Not Found / 500 Internal Server Error):
      json
      {
        "status": 404,
        "message": "Resource Not Found",
        "request_id": "a81c4e72-2f08-4e89-91bc-0d123456789a"
      }

4. Bukti Teknis (Technical Proof & Audit Trail) ​

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
b60a9ac6513400bc4d12033fc183fabc84b63092feat/pino-loggerHermansyah Efendi2026-08-11 14:25refactor(logger): replace console logging with Pino logger and enhance HTTP/SQL logging

Cuplikan Kode / Implementasi Inti ​

javascript
// app/middlewares/http_logger.js
export const httpLogger = (req, res, next) => {
  const request_id = crypto.randomUUID();
  const startTime = process.hrtime.bigint();

  // Attach request_id ke request context
  req.request_id = request_id;

  const originalJson = res.json;
  res.json = function (body) {
    if (this.statusCode >= 400 && body && typeof body === "object" && !Array.isArray(body)) {
      if (!body.request_id) {
        body.request_id = request_id;
      }
    }
    return originalJson.call(this, body);
  };

  // Isolasi seluruh async flow di bawah context request_id
  loggerStorage.run({ request_id }, () => {
    // 1. Log Incoming Request
    logger.info(
      {
        method: req.method,
        url: req.originalUrl,
        ip: req.ip,
        user_agent: req.headers["user-agent"],
      },
      "Incoming request"
    );

    // 2. Log Outgoing Response Lifecycle saat selesai
    res.on("finish", () => {
      const durationNs = process.hrtime.bigint() - startTime;
      const durationMs = Number(durationNs / 1_000_000n);
      const statusCode = res.statusCode;

      const logData = {
        method: req.method,
        url: req.originalUrl,
        status_code: statusCode,
        response_time: `${durationMs}ms`,
      };

      if (statusCode >= 500) {
        logger.error(logData, "Request completed with server error");
      } else if (statusCode >= 400) {
        logger.warn(logData, "Request completed with client error");
      } else {
        logger.info(logData, "Request completed");
      }
    });

    next();
  });
};
javascript
// app/connection.js
export const formatQueryForLog = (queryText, params) => {
  if (typeof queryText !== "string") return queryText;
  let formatted = queryText.replace(/\\n/g, "\n");
  if (Array.isArray(params) && params.length > 0) {
    params.forEach((param, index) => {
      const placeholder = new RegExp(`\\$${index + 1}(?![0-9])`, "g");
      let valueStr;
      if (param === null || param === undefined) {
        valueStr = "NULL";
      } else if (typeof param === "number" || typeof param === "boolean") {
        valueStr = String(param);
      } else if (param instanceof Date) {
        valueStr = `'${param.toISOString()}'`;
      } else if (Array.isArray(param)) {
        const items = param.map((item) =>
          typeof item === "string" ? `'${String(item).replace(/'/g, "''")}'` : item
        ).join(", ");
        valueStr = `ARRAY[${items}]`;
      } else {
        valueStr = `'${String(param).replace(/'/g, "''")}'`;
      }
      formatted = formatted.replace(placeholder, valueStr);
    });
  }
  return formatted;
};

5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification) ​

A. Verifikasi Unit Test (Development Stage) ​

  • Test Suite File: tests/middlewares/http_logger.test.js, tests/connection.test.js
  • Status Eksekusi CLI: PASSED
javascript
// tests/middlewares/http_logger.test.js
describe('httpLogger middleware', () => {
  it('attaches req.request_id and passes through 200 OK without adding request_id to response payload', () => {
    httpLogger(req, res, next);
    expect(req.request_id).toBeDefined();
    expect(next).toHaveBeenCalled();

    const payload = { status: 200, message: 'Success', data: [1, 2, 3] };
    res.json(payload);
    expect(payload.request_id).toBeUndefined();
  });

  it('automatically attaches request_id to response payload when statusCode >= 400', () => {
    res.statusCode = 404;
    httpLogger(req, res, next);

    const payload = { status: 404, message: 'Not Found' };
    res.json(payload);
    expect(payload.request_id).toBe(req.request_id);
  });
});
text
 RUN  v4.1.10 D:/WORK/LNSW/pengangkut-backend
 ✓ tests/middlewares/http_logger.test.js (2 tests) 10ms
 ✓ tests/connection.test.js (7 tests) 17ms

 Test Files  2 passed (2)
      Tests  9 passed (9)
   Start at  11:05:26
   Duration  489ms (transform 93ms, setup 0ms, import 331ms, tests 27ms, environment 0ms)

B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook) ​

Diisi saat development sebagai panduan eksekusi pengujian QA, kemudian dilengkapi bukti response JSON saat verifikasi di Staging.

  • Cakupan / Surface Pengujian: API / Postman + Observability (Grafana / Loki)
  • Kebutuhan Akses / Role Akun:
    • Token autentikasi API pengangkut
    • Akses dashboard Grafana (menu Explore / Data Source Loki)
  • Hasil Pengujian UAT: PENDING
  • Catatan QA: —
Query Validasi Log di Grafana (Loki LogQL) ​
Tujuan ValidasiLogQL Query di Grafana Explore
Trace seluruh log satu request flow{app="pengangkut-backend"} | json | request_id="<UUID_DARI_RESPONSE>"
Filter log incoming request{app="pengangkut-backend"} | json | msg="Incoming request"
Filter log error HTTP (4xx / 5xx){app="pengangkut-backend"} | json | status_code >= 400
Filter log query SQL (Debug Mode){app="pengangkut-backend"} | json | msg="Database query executed"
Skenario Pengujian QA (Test Scenarios) ​
  1. Skenario 1 — Request/Response Lifecycle & Isolation Test (Happy Path):

    • Pre-kondisi: Server berjalan dengan konfigurasi httpLogger aktif di environment target.
    • Trigger / Request: GET /api/v1/some-endpoint via Postman dengan token valid.
    • Ekspektasi Response: HTTP 200 OK, body response bersih tanpa atribut request_id.
    • Verifikasi Log di Grafana:
      • Buka Grafana > Explore > pilih datasource Loki.
      • Jalankan query: {app="pengangkut-backend"} | json | url="/api/v1/some-endpoint"
      • Verifikasi 2 event log berurutan dengan request_id yang identik:
        1. Incoming request (level info, mencatat method, url, ip, user_agent).
        2. Request completed (level info, mencatat status_code: 200, dan response_time).
  2. Skenario 2 — Error Lifecycle & Correlation ID Tracing via Grafana:

    • Pre-kondisi: Endpoint ditargetkan dengan payload tidak valid atau route tidak ditemukan.
    • Trigger / Request: GET /api/v1/invalid-route (HTTP 404) atau trigger endpoint dengan invalid parameter (HTTP 400).
    • Ekspektasi Response: HTTP 4xx / 5xx, body response JSON memuat atribut "request_id": "<UUID>".
    • Verifikasi Log di Grafana:
      • Salin nilai request_id dari response body Postman.
      • Di Grafana Explore, masukkan query LogQL: {app="pengangkut-backend"} | json | request_id="<UUID_TERSEBUT>"
      • Log server menampilkan lifecycle lengkap request tersebut dengan severity warn (jika 4xx) atau error (jika 5xx), membuktikan korelasi 100% antara client error dan log server.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan observasi Grafana."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend