Logger
Overview
Armature uses a structured logger built on a port/adapter pattern that decouples logging consumers from the transport.
LoggerPort— abstract interface every adapter must implementConsoleLoggerAdapter— default adapter: coloured text indevelopment, JSON inproductionLoggerService— injectable service used throughout the app
LoggerModule is @Global() — LoggerService is available everywhere without importing the module.
Usage
Always inject LoggerService and create a scoped instance in the constructor:
import { LoggerService } from '../common/logger/logger.service.js';
@Injectable()
export class MyService {
private readonly logger: LoggerService;
constructor(logger: LoggerService) {
this.logger = logger.withContext('MyService');
}
doSomething(id: string) {
this.logger.debug('Starting operation', { id });
try {
// ...
this.logger.log('Operation completed', { id, result: 'ok' });
} catch (err) {
this.logger.error('Operation failed', {
id,
error: (err as Error).message,
});
}
}
}
Log levels
| Method | Level | When to use |
|---|---|---|
debug() |
DEBUG |
Troubleshooting — values, branching, step-by-step |
log() |
LOG |
Normal application events — user created, email sent |
warn() |
WARN |
Non-critical unexpected states — Redis unavailable, deprecated path hit |
error() |
ERROR |
Failures that need attention — unhandled exception, external API down |
The minimum level is controlled by LOG_LEVEL (default: DEBUG). Messages below the threshold are silently discarded.
Rules
- Never use
console.logorconsole.error— always injectLoggerService - Always call
logger.withContext('ClassName')in the constructor - Pass structured data as the second argument — never interpolate it into the message string
- HTTP request logging and unhandled exception logging are automatic (global interceptor + filter) — do not re-log them manually in controllers
Automatic logging
| What | Where |
|---|---|
| Incoming requests (method, path, status, duration) | HttpLoggingInterceptor — applied globally |
| Unhandled HTTP exceptions | HttpExceptionFilter — applied globally |
Output format
Coloured, human-readable:
Newline-delimited JSON (ready for log aggregation):
Adding a custom adapter
To add a new transport (file rotation, Datadog, Loki…):
- Create a class that implements
LoggerPort:
// src/common/logger/adapters/datadog.adapter.ts
import type { LogEntry, LoggerPort } from '../logger.port.js';
export class DatadogAdapter implements LoggerPort {
write(entry: LogEntry): void {
// send to Datadog — never throw
}
}
- Register it in
LoggerModule:
The rest of the application requires no changes.