Logging
Zelt provides a built-in Logger module with structured logging, configurable transports, and context propagation.
Basic Usage
Inject the Logger into your services or controllers:
import { Injectable, inject, Logger } from '@zeltjs/core';
@Injectable()
export class OrderService {
constructor(private logger = inject(Logger)) {}
processOrder(orderId: string) {
this.logger.info(`Processing order: ${orderId}`);
try {
// ... process order
this.logger.debug('Order validation passed');
} catch (error) {
this.logger.error(`Failed to process order: ${orderId}`);
throw error;
}
}
}
Log Levels
The Logger supports four log levels in order of severity:
| Level | Method | Description |
|---|---|---|
debug | logger.debug() | Detailed debugging information |
info | logger.info() | General informational messages |
warn | logger.warn() | Warning messages |
error | logger.error() | Error messages |
Messages are only output if their level is equal to or higher than the configured level. For example, with level: 'info', debug() messages are suppressed.
Structured Logging
Pass context as the second argument to include structured data:
logger.info('Order processed', { orderId, userId, duration: 150 });
// Output: 13:45:23 INFO Order processed {"orderId":"123","userId":"456","duration":150}
Child Loggers
Create child loggers with bound context that persists across all log calls:
@Injectable()
export class OrderService {
private logger: Logger;
constructor(baseLogger = inject(Logger)) {
this.logger = baseLogger.child({ service: 'OrderService' });
}
processOrder(orderId: string) {
const orderLogger = this.logger.child({ orderId });
orderLogger.info('Processing started');
// Output includes: {"service":"OrderService","orderId":"123"}
}
}
Global Context with withLogContext
Use withLogContext to propagate context across async boundaries using AsyncLocalStorage:
const logger = inject(Logger);
withLogContext({ requestId: 'abc-123' }, () => {
logger.info('Request received');
// Context is automatically included in all logs within this scope
someService.process();
});
Configuration
Basic Configuration
Configure the Logger using LoggerConfig:
@Config
export class AppLoggerConfig extends LoggerConfig {
constructor(
private env = inject(Env),
private consoleTransport = inject(ConsoleTransport),
private jsonlFormatter = inject(JsonlFormatter),
) {
super();
}
override get level(): LogLevel {
return (this.env.getString('LOG_LEVEL') as LogLevel) ?? 'info';
}
override get transports(): readonly TransportBinding[] {
return [{ transport: this.consoleTransport, formatter: this.jsonlFormatter }];
}
}
Using PrettyFormatter
For human-readable output in development, use PrettyFormatter:
@Config
export class DevLoggerConfig extends LoggerConfig {
constructor(
private consoleTransport = inject(ConsoleTransport),
private prettyFormatter = inject(PrettyFormatter),
) {
super();
}
override get level() {
return 'debug' as const;
}
override get transports(): readonly TransportBinding[] {
return [{ transport: this.consoleTransport, formatter: this.prettyFormatter }];
}
}
PrettyFormatter outputs colored logs in TTY environments:
13:45:23 INFO Order processed {"orderId":"123"}
13:45:23 ERROR Failed to process {"error":"timeout"}
Register the config when creating the app:
const app = createApp([http({
controllers: [AppController],
})], { configs: [AppLoggerConfig] });
Transports and Formatters
The Logger uses a pluggable transport/formatter architecture:
| Component | Description |
|---|---|
ConsoleTransport | Writes to stdout/stderr |
JsonlFormatter | JSON Lines format (one JSON object per line) |
PrettyFormatter | Human-readable format with optional colors |
Custom Transport
Implement LoggerTransport for custom output destinations:
import type { LoggerTransport } from '@zeltjs/core';
export class FileTransport implements LoggerTransport {
write(message: string): void {
// Write to file
}
}
Custom Formatter
Implement LoggerFormatter for custom output formats:
import type { LoggerFormatter, LogEntry } from '@zeltjs/core';
export class CustomFormatter implements LoggerFormatter {
format(entry: LogEntry): string {
return `[${entry.level}] ${entry.message}`;
}
}
Default Behavior
Without custom configuration:
- Level:
'info'(debug messages are suppressed) - Transport:
ConsoleTransport - Formatter:
JsonlFormatter