Logging
Stratal includes a built-in logging system with structured output, configurable log levels, and pluggable formatters. Each log entry is formatted and written to the console method that matches its level.
Configuration
Section titled “Configuration”Pass logging options when creating your Stratal instance:
import { Stratal } from 'stratal'import { AppModule } from './app.module'
export default new Stratal({ module: AppModule, logging: { level: 'info', formatter: 'json', },})| Option | Type | Default | Description |
|---|---|---|---|
level | 'debug' | 'info' | 'warn' | 'error' | 'info' | Minimum log level to output |
formatter | 'json' | 'pretty' | 'json' | Output format |
Log levels
Section titled “Log levels”Stratal supports four log levels, ordered by priority:
| Level | Priority | Description |
|---|---|---|
debug | 0 | Detailed diagnostic information for development |
info | 1 | General operational events |
warn | 2 | Unexpected situations that are not errors |
error | 3 | Failures that need attention |
The configured level acts as a minimum threshold. A level of info outputs info, warn, and error messages but suppresses debug. A level of error outputs only error messages.
Using the logger
Section titled “Using the logger”Inject LoggerService using the LOGGER_TOKENS.LoggerService token:
import { Transient, inject } from 'stratal/di'import { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'
@Transient()export class OrderService { constructor( @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService, ) {}
async createOrder(userId: string, items: string[]) { this.logger.info('Creating order', { userId, itemCount: items.length })
// ... create order logic
this.logger.debug('Order items', { items }) }}The logger provides four methods, one for each level:
logger.debug(message: string, context?: LogContext): voidlogger.info(message: string, context?: LogContext): voidlogger.warn(message: string, context?: LogContext): voidlogger.error(message: string, error: Error, context?: LogContext): voidlogger.error(message: string, context?: LogContext): voidEach method checks the configured level, formats the entry, and writes it to the console synchronously before returning.
Log context
Section titled “Log context”Pass a LogContext object (a Record<string, unknown>) as the second argument to attach structured data to the log entry:
this.logger.info('Payment processed', { orderId: 'order_123', amount: 49.99, currency: 'USD', gateway: 'stripe',})The context is merged with internal metadata (like timestamp) and included in the formatted output.
Error logging
Section titled “Error logging”The error method accepts either a LogContext object or an Error instance:
// With an Error objecttry { await database.query(sql)} catch (error) { this.logger.error('Database query failed', error)}
// With an Error object and additional contextthis.logger.error('Database query failed', error, { query: 'getUser' })
// With context onlythis.logger.error('Payment declined', { orderId: 'order_123', reason: 'insufficient_funds',})When an Error is passed, the logger serializes its message, name, and stack into a structured error field on the log entry.
Formatters
Section titled “Formatters”Formatters control how log entries are serialized to strings before being written to the console.
A formatter is any class implementing the ILogFormatter interface, which has a single method:
import type { LogEntry } from 'stratal/logger'
interface ILogFormatter { format(entry: LogEntry): string}A LogEntry carries the level, message, the enriched context (your LogContext plus a timestamp), and an optional serialized error.
JsonFormatter
Section titled “JsonFormatter”The default formatter for production. Outputs each log entry as a single JSON line:
{"level":"info","message":"Payment processed","timestamp":1708934400000,"orderId":"order_123","amount":49.99}All context fields are spread into the top-level JSON object alongside level and message. The timestamp comes from the entry context. If an error is present, it appears as a nested error object.
PrettyFormatter
Section titled “PrettyFormatter”Designed for local development. Outputs color-coded, human-readable logs:
[2026-02-26T10:00:00.000Z] INFO : Payment processed orderId: "order_123" amount: 49.99 currency: "USD"Each level gets its own color:
| Level | Color |
|---|---|
debug | Cyan |
info | Green |
warn | Yellow |
error | Red |
Error stack traces are printed below the context when present.
Switch to the pretty formatter in development:
export default new Stratal({ module: AppModule, logging: { level: 'debug', formatter: 'pretty', },})Console output
Section titled “Console output”LoggerService formats each entry with the configured formatter and writes the result to the console method that matches its level:
| Level | Console method |
|---|---|
debug | console.debug() |
info | console.info() |
warn | console.warn() |
error | console.error() |
On Cloudflare Workers these console calls are captured by the platform and surfaced through wrangler tail, the dashboard, and any Logpush or analytics integration you have wired up.
Custom formatters
Section titled “Custom formatters”Formatters are the extension point for log output. To control the exact shape of what gets written, implement ILogFormatter and register your class on the LOGGER_TOKENS.Formatter token.
-
Implement the
ILogFormatterinterface. Theformatmethod receives the fullLogEntryand returns the string to write:import type { ILogFormatter, LogEntry } from 'stratal/logger'export class LogfmtFormatter implements ILogFormatter {format(entry: LogEntry): string {const fields: Record<string, unknown> = {level: entry.level,message: entry.message,...entry.context,}if (entry.error) {fields.error = entry.error.message}return Object.entries(fields).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(' ')}} -
Register it on the
LOGGER_TOKENS.Formattertoken from a module. A provider registered by your module takes precedence over the built-injsonorprettyformatter:import { Module } from 'stratal/module'import { LOGGER_TOKENS } from 'stratal/logger'import { LogfmtFormatter } from './logfmt-formatter'@Module({providers: [{ provide: LOGGER_TOKENS.Formatter, useClass: LogfmtFormatter },],})export class AppModule {}
Every log entry now flows through your formatter before it is written to the console.
Next steps
Section titled “Next steps”- Error Handling to learn how the global error handler uses the logger.
- Cron Jobs for logging in scheduled tasks.
- Dependency Injection for injecting
LoggerServiceinto your providers.