Skip to main content

Structured logging

Reqon's structured logging system provides context-rich logs with hierarchical spans, multiple output formats, and integration with the event system.

Creating a logger

import { createStructuredLogger } from 'reqon';

const logger = createStructuredLogger({
prefix: 'MyApp', // Log prefix
level: 'info', // Minimum log level
console: true, // Enable console output
jsonLines: false, // Enable JSON lines output
context: { // Default context
service: 'data-sync'
}
});

Log levels

logger.debug('Detailed debugging info', { key: 'value' });
logger.info('General information', { count: 42 });
logger.warn('Warning condition', { threshold: 100 });
logger.error('Error occurred', { error: err.message });

Level filtering

const logger = createStructuredLogger({ level: 'warn' });

logger.debug('Not logged'); // Filtered out
logger.info('Not logged'); // Filtered out
logger.warn('Logged'); // Output
logger.error('Logged'); // Output

// Change level at runtime
logger.setLevel('debug');

Context

Adding context

logger.info('Processing item', {
itemId: item.id,
itemType: item.type,
size: item.size
});

Output:

[MyApp] INFO  Processing item itemId="123" itemType="order" size=42

Child loggers

Create child loggers with inherited context:

const logger = createStructuredLogger({ prefix: 'App' });

const actionLogger = logger.child({ action: 'FetchCustomers' });
actionLogger.info('Starting');
// [App] INFO Starting action="FetchCustomers"

const stepLogger = actionLogger.child({ step: 1 });
stepLogger.info('Fetching page');
// [App] INFO Fetching page action="FetchCustomers" step=1

Timing spans

Track operation duration with spans:

const span = logger.span('fetchData');

try {
const data = await fetchData();
span.addContext({ itemCount: data.length });
} finally {
const duration = span.end();
// [App] DEBUG span:end spanName="fetchData" itemCount=100 (234ms)
}

Nested spans

const missionSpan = logger.span('mission');

const fetchSpan = missionSpan.child('fetch');
await fetch('/api/data');
fetchSpan.end();

const processSpan = missionSpan.child('process');
await processData();
processSpan.end();

missionSpan.end();

Output handlers

ConsoleOutput

Human-readable console output:

import { ConsoleOutput } from 'reqon';

const output = new ConsoleOutput({
prefix: 'Reqon',
colors: true
});

logger.addOutput(output);

JsonLinesOutput

JSON lines format for log aggregation:

import { JsonLinesOutput } from 'reqon';
import { createWriteStream } from 'fs';

const stream = createWriteStream('logs.jsonl');
const output = new JsonLinesOutput(stream);

logger.addOutput(output);

Output format:

{"level":"info","message":"Processing","timestamp":"2025-01-20T10:00:00Z","context":{"itemId":"123"}}

BufferOutput

For testing and inspection:

import { BufferOutput } from 'reqon';

const buffer = new BufferOutput();
logger.addOutput(buffer);

// Later
const errors = buffer.filter(e => e.level === 'error');
const hasWarnings = buffer.find(e => e.level === 'warn') !== undefined;
buffer.clear();

EventOutput

Bridge logs to the event system:

import { EventOutput, createEmitter } from 'reqon';

const emitter = createEmitter();
const output = new EventOutput(emitter);

logger.addOutput(output);

Multiple outputs

Configure multiple outputs simultaneously:

import {
createStructuredLogger,
ConsoleOutput,
JsonLinesOutput
} from 'reqon';

const logger = createStructuredLogger({
console: false, // Disable default console
silent: false
});

// Add custom outputs
logger.addOutput(new ConsoleOutput({ prefix: 'App' }));
logger.addOutput(new JsonLinesOutput(logStream));

Log entry structure

interface LogEntry {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
timestamp: string; // ISO 8601
context: Record<string, unknown>;
spanId?: string; // For span entries
parentSpanId?: string; // For nested spans
duration?: number; // For span:end entries
}

Best practices

Use consistent context keys

// Good: consistent naming
logger.info('Fetching', { userId: user.id, orderId: order.id });
logger.info('Processing', { userId: user.id, orderId: order.id });

// Avoid: inconsistent naming
logger.info('Fetching', { user_id: user.id });
logger.info('Processing', { uid: user.id });

Log at appropriate levels

// DEBUG: Detailed information for debugging
logger.debug('Parsed response', { fields: Object.keys(data) });

// INFO: General operational information
logger.info('Synced customers', { count: 150 });

// WARN: Unusual but handled conditions
logger.warn('Rate limited, waiting', { retryAfter: 60 });

// ERROR: Errors requiring attention
logger.error('Failed to connect', { error: err.message });

Include actionable context

// Good: includes context for debugging
logger.error('Failed to store item', {
store: 'customers',
itemId: item.id,
error: err.message,
retryCount: 3
});

// Poor: missing context
logger.error('Failed');

Use spans for performance

const span = logger.span('syncCustomers');

for (const customer of customers) {
const itemSpan = span.child('processCustomer');
await processCustomer(customer);
itemSpan.end();
}

const duration = span.end();
if (duration > 5000) {
logger.warn('Slow sync detected', { duration });
}

Integration with execution

import { execute, createStructuredLogger, createEmitter } from 'reqon';

const emitter = createEmitter();
const logger = createStructuredLogger({
eventEmitter: emitter,
level: 'debug'
});

// Events flow to logger
emitter.on('fetch.complete', (event) => {
logger.info('Fetch complete', {
url: event.url,
status: event.status,
duration: event.duration
});
});

await execute(source, { eventEmitter: emitter });