Files
gridpilot.gg/docs/architecture/shared/LOGGING.md
2026-01-11 13:04:33 +01:00

245 lines
4.0 KiB
Markdown

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