Enterprise structured logging for TypeScript — Serilog-style message templates, CLEF format, batching sinks with circuit breaking, ambient correlation IDs.
npm install @cleverbrush/logimport {
createLogger,
consoleSink,
hostnameEnricher,
processIdEnricher,
} from '@cleverbrush/log';
const logger = createLogger({
minimumLevel: 'information',
sinks: [consoleSink({ theme: 'dark' })],
enrichers: [hostnameEnricher(), processIdEnricher()],
});
logger.info('Server started on port {Port}', { Port: 3000 });
logger.error(new Error('oops'), 'Request failed for {UserId}', { UserId: 42 });
await logger.dispose();- Message Templates —
{Named}properties captured as structured data - Typed Templates —
TypedTemplate<T>via@cleverbrush/schemafor compile-time checked, groupable log events - CLEF Format — Compact Log Event Format for Seq, ClickHouse, etc.
- Sinks — Console, File (with rotation), Seq, ClickHouse, custom
- Batching — All network sinks batch with retry & circuit breaking
- Enrichers — hostname, processId, environment, application, correlationId, caller
- Correlation IDs — UUID v7, extracted from headers, propagated via AsyncLocalStorage
- Middleware — Request logging & correlation ID for
@cleverbrush/server - DI —
configureLogging()for@cleverbrush/di - Sampling — Per-level sampling filters
Pass a ParseStringSchemaBuilder (from @cleverbrush/schema) directly to any log method. The logger uses the raw {Property} pattern as messageTemplate so all events of the same shape are grouped in Seq, SigNoz, ClickHouse, etc., while the rendered message is interpolated as usual.
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' });import { useLogging } from '@cleverbrush/log';
// Returns [correlationIdMiddleware, requestLoggingMiddleware]
const [correlationId, requestLogging] = useLogging(logger, {
excludePaths: ['/health'],
// Set to false when OTel traceparent already provides traceability
correlationResponseHeader: false,
});correlationResponseHeader: false suppresses the X-Correlation-Id response header entirely — useful when @cleverbrush/otel's tracing middleware already sets a traceparent / traceresponse header and a second ID would be redundant.
Trace correlation, OTLP log export, and the traceEnricher (which copies the active span's traceId / spanId onto every log event) live in @cleverbrush/otel. Wire them up like this:
import { createLogger, consoleSink } from '@cleverbrush/log';
import { otelLogSink, traceEnricher } from '@cleverbrush/otel';
const logger = createLogger({
sinks: [consoleSink(), otelLogSink()],
enrichers: [traceEnricher()],
});@cleverbrush/log itself stays free of any OpenTelemetry runtime dependency.
BSD-3-Clause