Logging & Correlation ID Design Guide (Clean Architecture) This document defines a clean, strict, and production-ready logging architecture with correlation IDs. It removes ambiguity around: • where logging belongs • how context is attached • how logs stay machine-readable • how Clean Architecture boundaries are preserved The rules below are non-negotiable. ⸻ Core Principles Logs are data, not text. Context is injected, never constructed in the Core. Logging must: • be machine-readable • be framework-agnostic in the Core • support correlation across requests • be safe for parallel execution ⸻ Architectural Responsibilities Core • describes intent • never knows about correlation IDs • never knows about log destinations App Layer (API) • defines runtime context (request, user) • binds correlation IDs Adapters • implement concrete loggers (console, file, structured) • decide formatting and transport ⸻ 1. Logger Port (Core) Purpose Defines what logging means, without defining how logging works. ⸻ Rules • exactly one logging interface • no framework imports • no correlation or runtime context ⸻ Location core/shared/application/LoggerPort.ts ⸻ Contract • debug(message, meta?) • info(message, meta?) • warn(message, meta?) • error(message, meta?) Messages are semantic. Metadata is optional and structured. ⸻ 2. Request Context (App Layer) Purpose Represents runtime execution context. ⸻ Contains • correlationId • optional userId ⸻ Rules • never visible to Core • created per request ⸻ Location apps/api/context/RequestContext.ts ⸻ 3. Logger Implementations (Adapters) Purpose Provide concrete logging behavior. ⸻ Rules • implement LoggerPort • accept context via constructor • produce structured logs • no business logic ⸻ Examples • ConsoleLogger • FileLogger • PinoLogger • LokiLogger ⸻ Location adapters/logging/ ⸻ 4. Logger Factory Purpose Creates context-bound logger instances. ⸻ Rules • factory is injected • logger instances are short-lived • component name is bound here ⸻ Location adapters/logging/LoggerFactory.ts adapters/logging/LoggerFactoryImpl.ts ⸻ 5. Correlation ID Handling Where it lives • API middleware • message envelopes • background job contexts ⸻ Rules • generated once per request • propagated across async boundaries • never generated in the Core ⸻ 6. Usage Rules by Layer Layer Logging Allowed Notes Domain ❌ No Throw domain errors instead Use Cases ⚠️ Minimal Business milestones only API Services ✅ Yes Main logging location Adapters ✅ Yes IO, integration, failures Frontend ⚠️ Limited Errors + analytics only ⸻ 7. Forbidden Patterns ❌ Manual string prefixes ([ServiceName]) ❌ Global/singleton loggers with mutable state ❌ any in logger abstractions ❌ Correlation IDs in Core ❌ Logging inside domain entities ⸻ 8. File Structure (Final) core/ └── shared/ └── application/ └── LoggerPort.ts # * required apps/api/ ├── context/ │ └── RequestContext.ts # * required ├── middleware/ │ └── CorrelationMiddleware.ts └── modules/ └── */ └── *Service.ts adapters/ └── logging/ ├── LoggerFactory.ts # * required ├── LoggerFactoryImpl.ts # * required ├── ConsoleLogger.ts # optional ├── FileLogger.ts # optional └── PinoLogger.ts # optional ⸻ Mental Model (Final) The Core describes events. The App provides context. Adapters deliver telemetry. If any layer violates this, the architecture is broken. ⸻ Summary • one LoggerPort in the Core • context bound outside the Core • adapters implement logging destinations • correlation IDs are runtime concerns • logs are structured, searchable, and safe This setup is: • Clean Architecture compliant • production-ready • scalable • refactor-safe