Skip to main content

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 } from '@zeltjs/core';
import { Logger } from '@zeltjs/core/modules/logger';

@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:

LevelMethodDescription
debuglogger.debug()Detailed debugging information
infologger.info()General informational messages
warnlogger.warn()Warning messages
errorlogger.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:

this.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:

import { withLogContext, Logger } from '@zeltjs/core/modules/logger';

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:

import { Config, inject } from '@zeltjs/core';
import {
LoggerConfig,
ConsoleTransport,
JsonlFormatter,
type TransportBinding,
type LogLevel,
} from '@zeltjs/core/modules/logger';

@Config
export class AppLoggerConfig extends LoggerConfig {
constructor(
private console = inject(ConsoleTransport),
private jsonl = inject(JsonlFormatter),
) {
super();
}

override get level(): LogLevel {
return (process.env.LOG_LEVEL as LogLevel) ?? 'info';
}

override get transports(): readonly TransportBinding[] {
return [{ transport: this.console, formatter: this.jsonl }];
}
}

Using PrettyFormatter

For human-readable output in development, use PrettyFormatter:

import { Config, inject } from '@zeltjs/core';
import {
LoggerConfig,
ConsoleTransport,
PrettyFormatter,
type TransportBinding,
} from '@zeltjs/core/modules/logger';

@Config
export class DevLoggerConfig extends LoggerConfig {
constructor(
private console = inject(ConsoleTransport),
private pretty = inject(PrettyFormatter),
) {
super();
}

override get level() {
return 'debug' as const;
}

override get transports(): readonly TransportBinding[] {
return [{ transport: this.console, formatter: this.pretty }];
}
}

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:

import { createHttpApp } from '@zeltjs/core';
import { AppLoggerConfig } from './logger.config';
import { AppController } from './app.controller';

const app = createHttpApp({
controllers: [AppController],
configs: [AppLoggerConfig],
});

Transports and Formatters

The Logger uses a pluggable transport/formatter architecture:

ComponentDescription
ConsoleTransportWrites to stdout/stderr
JsonlFormatterJSON Lines format (one JSON object per line)
PrettyFormatterHuman-readable format with optional colors

Custom Transport

Implement LoggerTransport for custom output destinations:

import type { LoggerTransport } from '@zeltjs/core/modules/logger';

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/modules/logger';

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