Structured logging for Node.js

Structured logs.
One per event

A small Node.js package for collecting event data and writing structured JSON logs.

npm install log-pal

TypeScript One runtime dependency: Zod

EXAMPLE OUTPUT
example.logJSON
❯
{
  "eventType": "req-summary-log",
  "requestId": "req_7z2k",
  "request": {
    "method": "POST",
    "path": "/checkout"
  },
  "response": {
    "statusCode": 200,
    "durationMs": 84
  },
  "custom": { "userId": "usr_42" },
  "errors": [{
    "type": "payment-retry",
    "message": "Payment service unavailable; retry succeeded"
  }],
  "warnings": [],
  "externalCalls": [{
    "url": "https://payments.example.com/charges",
    "method": "POST",
    "status": "finished",
    "statusCode": 503,
    "durationMs": 31
  }, {
    "url": "https://payments.example.com/charges",
    "method": "POST",
    "status": "finished",
    "statusCode": 200,
    "durationMs": 42
  }]
}
One event log84 ms
01 Event data 02 Automatic finalization 03 Schema validation

01 / Inconsistent logs

Problem (Why?)

Applications use different log formats and record different details. Information about one request is often spread across several messages, making it hard to follow what happened. Missing request IDs or outcomes make those messages harder to connect.

02 / A shared approach

Solution (How?)

Logging needs a shared format and clear rules for what to record. A simple, minimal interface would make this consistent across applications, with one record per event where practical.

Three requests · one fails early

SEPARATE MESSAGES14 logs

req_aPOST /orders received

req_bGET /health received

req_cPOST /payments received

req_cPOST payments.example.com/charges

req_auserId=usr_42

req_bprocess=running

req_cpayments.example.com → 503 · 9 ms

req_cFATAL Payment service unavailable

req_aorderId=ord_2048

req_aPOST inventory.example.com/reservations

req_bstatus=ok

req_b200 OK · 42 ms

req_ainventory.example.com → 200 · 35 ms

req_a201 Created · 84 ms

ONE EVENT PER REQUEST3 logs

Fatal error · event written immediately

{
  "eventType": "req-summary-log", "level": "error",
  "requestId": "req_c",
  "request": { "method": "POST", "path": "/payments" },
  "response": { "statusCode": 500, "durationMs": 11 },
  "externalCalls": [{
    "url": "https://payments.example.com/charges",
    "method": "POST", "status": "finished",
    "statusCode": 503, "durationMs": 9
  }],
  "errors": [{
    "type": "payment-unavailable", "fatal": true,
    "message": "Payment service unavailable"
  }]
}
{
  "eventType": "req-summary-log",
  "requestId": "req_b",
  "request": { "method": "GET", "path": "/health" },
  "health": { "process": "running", "status": "ok" },
  "response": { "statusCode": 200, "durationMs": 42 },
  "externalCalls": []
}
{
  "eventType": "req-summary-log",
  "requestId": "req_a",
  "request": { "method": "POST", "path": "/orders" },
  "userId": "usr_42", "orderId": "ord_2048",
  "response": { "statusCode": 201, "durationMs": 84 },
  "externalCalls": [{
    "url": "https://inventory.example.com/reservations",
    "method": "POST", "status": "finished",
    "statusCode": 200, "durationMs": 35
  }]
}

Inspired by loggingsucks.com.

03 / Node.js package

LogPal (What?)

LogPal collects data during a request or invocation and writes a JSON log when the handler finishes. It includes HTTP and Lambda formats, inferred TypeScript types, and Zod validation.

01logpal.event()

Create an event

One instance per app. One event per invocation, available to every helper.

02logpal.addData()

Add data

Add event fields here. Use addError(), addWarning(), and initExternalCall() for issues and outgoing calls.

03// automatic

Finalize the log

When the handler finishes, LogPal validates event data and writes JSON to the console by default. Validation issues appear in _logpal.errors.

SCHEMA EXPLORER

AWS Lambda

AWS invocation fields, supplied through addData().

Zod validated
schema.tsTypeScript
output.jsonExample event

Custom formats use your own Zod object schema. See how

Getting started

Install and configure

Install the package, create one instance for your app, and wrap each handler in an event.

npm install log-pal
handler.tsTypeScript
import LogPal from 'log-pal';

// One instance for your application.
const logpal = new LogPal({
  eventType: 'req-summary-log',
  format: 'httpRequest',
  logInitialization: false,
});

export const handleRequest = (
  request: { requestId: string; path: string; method: string },
  handle: () => Promise<{ statusCode: number }>,
) =>
  logpal.event({ requestId: request.requestId }, async () => {
    const startedAt = performance.now();
    logpal.addData({
      request: { path: request.path, method: request.method },
    });

    const response = await handle();
    logpal.addData({
      response: {
        statusCode: response.statusCode,
        durationMs: performance.now() - startedAt,
      },
    });
    return response;
  });