Skip to content

feat: structured logging with correlation_id propagation - #10

Merged
lmoraesdev merged 6 commits into
mainfrom
feat/structured-logging
May 15, 2026
Merged

feat: structured logging with correlation_id propagation#10
lmoraesdev merged 6 commits into
mainfrom
feat/structured-logging

Conversation

@lmoraesdev

@lmoraesdev lmoraesdev commented May 15, 2026

Copy link
Copy Markdown
Owner

What

Adds structured logging to the application and propagates a correlation_id through every layer of a single request — automatically, with no parameter threading.

Every log line is a single JSON object with a fixed shape:

{
  "when": "2026-05-14T20:32:00.123Z",
  "level": "info",
  "correlation_id": "req_8h2k3pX...",
  "where": "CreateChargeService",
  "what": "charge_created",
  "why": "user_request",
  "how": "POST /charges",
  "charge_id": "ch_..."
}

Logs emitted in this PR:

Service Event
CreateChargeService charge_created, idempotency_cache_hit, idempotency_conflict
ProcessWebhookService webhook_received, webhook_already_processed, charge_state_transitioned

Why

When a payment incident hits at 2am, the on-call doesn't have time to grep across services to assemble what happened. Structured logs with a shared correlation_id give them every request's story in one query, with the same field names every time.

The correlation_id:

  • Is read from X-Correlation-Id request header if present (clients can pass their own to trace cross-service)
  • Otherwise generated as req_<uuid>
  • Returned on the response in the same header
  • Stored in AsyncLocalStorage for the duration of the request, so every service that logs picks it up automatically — no parameter threading

Approach

Five commits:

  1. feat: add StructuredLogger with 5W1H format
  2. feat: add correlation id middleware with AsyncLocalStorage
  3. test: add unit tests for StructuredLogger and CorrelationIdMiddleware (including request isolation test)
  4. feat: emit structured logs from CreateChargeService and ProcessWebhookService
  5. docs: document logging format in README
  6. refactor: improve structured logger and add tests

Notes

  • AsyncLocalStorage is the Node equivalent of Go's context.Context or Java's ThreadLocal — it propagates values down the call stack without polluting function signatures.
  • The middleware uses storage.run(value, callback), not enterWith(), which is what guarantees per-request isolation under concurrency.
  • A test exercises that guarantee directly: two requests are started simultaneously, captured at different timings, and asserted to each see their own correlation_id.

@lmoraesdev
lmoraesdev force-pushed the feat/structured-logging branch from 143dcbe to 1a1b863 Compare May 15, 2026 17:43
@lmoraesdev
lmoraesdev merged commit 62c2ac1 into main May 15, 2026
1 check passed
@lmoraesdev
lmoraesdev deleted the feat/structured-logging branch May 15, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant