Enterprise structured logging for TypeScript — Serilog-style message templates, CLEF format, batching sinks, correlation tracking.
Most Node.js loggers produce unstructured text that is painful to query. console.log and basic loggers lose context across async boundaries, and correlating requests through a distributed system requires custom plumbing.
@cleverbrush/logbrings .NET's Serilog model to TypeScript: message templates that preserve named properties, automatic CLEF formatting for machine-readable output, batching sinks with retry and circuit breaking, and ambient correlation IDs via AsyncLocalStorage.
import { createLogger, consoleSink } from '@cleverbrush/log';
const logger = createLogger({
minimumLevel: 'information',
sinks: [consoleSink({ theme: 'dark' })],
enrichers: ['hostname', 'processId'],
});
logger.info('Server started on port {Port}', { Port: 3000 });
logger.error(new Error('oops'), 'Request failed for {UserId}', { UserId: 42 });
// Graceful shutdown
await logger.dispose();Properties in {braces} are captured as structured data and rendered into the message. Use @ to destructure objects.
// Named properties — queryable in Seq/ClickHouse
logger.info('Order {OrderId} placed by {UserId}', { OrderId: 123, UserId: 'u-42' });
// Destructure with @
logger.info('Config loaded: {@Config}', { Config: { port: 3000, env: 'prod' } });import { createLogger, consoleSink, SeqSink, FileSink } from '@cleverbrush/log';
const logger = createLogger({
minimumLevel: 'debug',
sinks: [
consoleSink({ theme: 'dark', minimumLevel: 'information' }),
new SeqSink({ serverUrl: 'http://localhost:5341' }),
new FileSink({ path: './logs/app.clef', rotationPolicy: 'size', maxFileSize: 10_000_000 }),
],
});Pass a ParseStringSchemaBuilder (from @cleverbrush/schema) directly to any log method. TypeScript enforces the parameter types at the call site, and the logger uses the raw {Property} pattern as messageTemplate so all events of the same shape are grouped in Seq, ClickStack, ClickHouse, etc.
import { s } from '@cleverbrush/schema';
import { createLogger, consoleSink } from '@cleverbrush/log';
// Define once — compile-time checked parameter types
const TodoCreated = s.parseString('Todo #{TodoId} "{Title}" created by {UserId}');
const logger = createLogger({ sinks: [consoleSink()] });
// TypeScript enforces { TodoId, Title, UserId }
logger.info(TodoCreated, { TodoId: 1, Title: 'Buy milk', UserId: 'u-42' });
// messageTemplate → 'Todo #{TodoId} "{Title}" created by {UserId}'
// renderedMessage → 'Todo #1 "Buy milk" created by u-42'import { useLogging, createLogger, consoleSink } from '@cleverbrush/log';
const logger = createLogger({
minimumLevel: 'information',
sinks: [consoleSink()],
enrichers: ['correlationId'],
});
// Returns [correlationIdMiddleware, requestLoggingMiddleware]
const [correlationId, requestLogging] = useLogging(logger, {
excludePaths: ['/health'],
// Set to false when @cleverbrush/otel's tracingMiddleware already
// sets a traceparent header — avoids a redundant second ID header
correlationResponseHeader: false,
});
// Add to your @cleverbrush/server pipeline
// Every request gets a unique correlation ID, logged on completionimport { ServiceCollection } from '@cleverbrush/di';
import { configureLogging, ILogger, consoleSink } from '@cleverbrush/log';
const services = new ServiceCollection();
configureLogging(services, logger);
const provider = services.buildServiceProvider();
const logger = provider.getService(ILogger);
logger.info('Resolved from DI');