Logging
Stratal includes a built-in logging system with structured output, configurable log levels, pluggable formatters, and non-blocking writes. Logs are dispatched through Cloudflare Workers’ waitUntil API so they never block your response.
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, contextOrError?: LogContext | Error): voidAll methods return immediately. The actual writing happens asynchronously via waitUntil.
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 contextthis.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 transports.
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, message, and timestamp. 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', },})Transports
Section titled “Transports”Transports are responsible for writing formatted log entries to their destination. Stratal ships with a ConsoleTransport that routes logs to the appropriate console method:
| Level | Console method |
|---|---|
debug | console.debug() |
info | console.info() |
warn | console.warn() |
error | console.error() |
Custom transports
Section titled “Custom transports”You can create custom transports by implementing the ILogTransport interface:
import type { ILogTransport, LogEntry } from 'stratal/logger'
export class ExternalLogTransport implements ILogTransport { readonly name = 'external'
async write(entry: LogEntry, formatted: string): Promise<void> { // Send to an external logging service await fetch('https://logs.example.com/ingest', { method: 'POST', body: formatted, headers: { 'Content-Type': 'application/json' }, }) }}The write method receives both the structured LogEntry and the pre-formatted string from the configured formatter. Use whichever is more appropriate for your transport.
Transport errors are caught and logged to console.error as a fallback, so a failing transport never crashes your application.
Non-blocking logging
Section titled “Non-blocking logging”Stratal uses Cloudflare Workers’ waitUntil API to ensure logs are written without blocking your response:
- Your code calls
logger.info('message', context). - The logger checks the log level. If the message is below the threshold, it returns immediately.
- The logger builds a
LogEntry, formats it, and dispatches write promises to all transports. - The combined promise is passed to
executionContext.waitUntil(). - The logger returns immediately. Your response is sent.
- The worker runtime keeps the isolate alive until the log writes complete.
This means logging adds zero latency to your response times. Even if a transport is slow (for example, sending logs to an external service), the response is already on its way.
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.