[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-REFACTORSumber Instruksi: Task Clean Code / Architecture Standardization
Dokumen Referensi / Lampiran: Tidak ada
Task / Dependensi Terkait (Cross-Service): Tidak ada
Modul Terkait:
PengangkutService / Repository:
pengangkut-backendPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Standarisasi sistem logging aplikasi pengangkut-backend dengan mengganti penggunaan
console.log,console.warn, danconsole.erroryang tidak terstruktur ke Pino structured JSON logger. Refactoring ini mencakup implementasi HTTP Request/Response logger middleware terisolasi menggunakanAsyncLocalStorageuntuk request context tracking (request_id), pencatatan lifecycle incoming request dan outgoing response beserta latensi durasi, otomatisasi injectionrequest_idpada 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 supportLOG_LEVELenvironment variable danAsyncLocalStoragecontext mixinrequest_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.jsonpadahttp_loggermiddleware untuk otomatis menyematkanrequest_idpada 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.
- [x] Standardisasi konfigurasi Pino logger (
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-08-11 14:25 | Hermansyah Efendi | DEPLOYED | Branch: feat/pino-logger |
| 2 | Staging (UAT) | 2026-09-09 11:34 | achmad fauzi | DEPLOYED | Release 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] |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-09-09 | Staging | Promosi ke STAGING via MR !2080 (Release Candidate v1.18.2) | Hermansyah Efendi |
| 2026-09-03 | Development | Initial log created | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- HTTP Request Isolation & Lifecycle Logging: Middleware
httpLoggermembungkus seluruh lifecycle request ke dalamloggerStorage.run({ request_id }, ...). Mencatat log eventIncoming requestdengan atributmethod,url,ip, danuser_agent, serta menangkap eventres.on("finish")untuk mencatatstatus_code, kalkulasiresponse_time(nanosecond to millisecond), dan severity level (info/warn/error). - Error Correlation Context: Mengintersepsi method
res.jsonagar seluruh response error (statusCode >= 400) menyertakan atributrequest_idpada payload JSON body untuk mempermudah QA/developer merekonsiliasi error klien dengan log server. - Database Query Debug Formatting: Menambahkan fungsi
formatQueryForLogyang mereplace placeholder parameterized query ($1, $2, ...) menjadi format literal SQL utuh pada log leveldebugdi fileapp/connection.jsdanapp/config/database/database.js. - Clean Code & Elimination of
console.*: Menghapus seluruh direct callconsole.log,console.warn, danconsole.errorpada core application code, digantikan oleh structured logger instanceparentLogger.child({ service_name: "..." }).
- HTTP Request Isolation & Lifecycle Logging: Middleware
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_idpada 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" }
- Deskripsi: Seluruh endpoint yang mengembalikan HTTP Status Code >= 400 otomatis disematkan atribut korelasi
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
pengangkut-backend - Branch Utama:
feat/pino-logger - Merge Request / PR: MR !2080 (Release Candidate v1.18.2)
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
b60a9ac6513400bc4d12033fc183fabc84b63092 | feat/pino-logger | Hermansyah Efendi | 2026-08-11 14:25 | refactor(logger): replace console logging with Pino logger and enhance HTTP/SQL logging |
Cuplikan Kode / Implementasi Inti
// 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();
});
};// 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
// 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);
});
}); 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 Validasi | LogQL 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)
Skenario 1 — Request/Response Lifecycle & Isolation Test (Happy Path):
- Pre-kondisi: Server berjalan dengan konfigurasi
httpLoggeraktif di environment target. - Trigger / Request:
GET/api/v1/some-endpointvia Postman dengan token valid. - Ekspektasi Response: HTTP
200 OK, body response bersih tanpa atributrequest_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_idyang identik:Incoming request(levelinfo, mencatatmethod,url,ip,user_agent).Request completed(levelinfo, mencatatstatus_code: 200, danresponse_time).
- Pre-kondisi: Server berjalan dengan konfigurasi
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_iddari 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) atauerror(jika 5xx), membuktikan korelasi 100% antara client error dan log server.
- Salin nilai
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA dan observasi Grafana."
}