Skip to content

Structured Logging

Rizky Zulkarnaen edited this page Sep 9, 2026 · 1 revision

Structured Logging

Source of truth: docs/logging.md · ADR: ADR-0024

A sink contract, not a logging product: Lugas emits structured, scalar-only entries to a pluggable sink — the entire adaptation surface for Pino, OpenTelemetry-aware loggers, or anything else. No logging dependency enters the package.

import { defineApp } from "lugas";

defineApp({
  logging: {
    level: "info",      // "debug" | "info" | "warn" | "error" (default "info")
    access: true,       // per-request access entry (default false)
    requestIds: true,   // UUID per request, x-request-id header, logged (default false)
    sink: (entry) => {
      // e.g. pino[entry.level](entry.fields, entry.message);
      console.log(JSON.stringify(entry));
    },
  },
  routes: { /* ... */ },
});

Entry contract

type LugasLogEntry = {
  time: string;   // ISO-8601 UTC
  level: "debug" | "info" | "warn" | "error";
  message: string;
  fields?: Record<string, string | number | boolean | null>;
};

Redaction by construction: the field schema is scalar and closed. Lugas never logs request bodies, query strings, headers, cookies, authorization values, or client IPs — there is no API through which the framework's entries could carry them. (Logging inside your own handlers is your sink, your responsibility.)

Access entries

With access: true, one info-level entry per completed request:

Field Example
method "GET"
path "/users/123"
route "GET /users/:id" — matched pattern, or "-" for unmatched/fallback
status 200
durationMs 1.42
requestId UUID, when requestIds: true

Coverage: route() descriptors, native function handlers, 404/fallback responses, held 503s from lifecycle startup failures, and redacted 500s — all with honest end-to-end durations. CORS preflights are not access-logged (handled at the outer boundary before any application wrapper).

Notes

  • Absent logging, behavior is byte-identical: no output, no headers.
  • requestIds echoes crypto.randomUUID() as the x-request-id response header and correlates it into the access entry.
  • Invalid configuration fails closed with LUGAS_LOG_001 (allowed keys: level, sink, requestIds, access).
  • Access logging defaults off — an explicit production policy, not an environment-dependent default.

Pinned by tests/logging/; evidence in docs/reports/issues/M8-003.md.

Clone this wiki locally