OpenLog — Structured Logging Library
A lightweight TypeScript library for structured, context-aware logging in Node.js applications, with built-in OpenTelemetry trace correlation.
The Problem
Every Node.js project I worked on had a different logging setup — some using raw console.log, some using Winston, some using Pino. None of them correlated logs to distributed traces out of the box, which made debugging production incidents painful: you’d have a trace ID in Datadog but couldn’t find the matching logs without manual grep work.
My Approach
I wanted a library that was: (1) zero-config for the happy path, (2) automatically injected OpenTelemetry trace/span IDs into every log line, (3) structured (JSON output for production, pretty-printed for development), and (4) tiny — no bloated dependency tree.
I studied the internals of Pino and Winston, then designed the API contract first (what would the ideal call site look like?) before writing a single line of implementation.
What I Built
The library exposes a simple factory function that creates a logger pre-configured for the environment:
const log = createLogger({ service: 'payment-service' });
log.info('Payment processed', { orderId, amount, currency });
// → {"level":"info","service":"payment-service","msg":"Payment processed",
// "orderId":"ord_123","traceId":"abc123","spanId":"def456","timestamp":"..."}
Key features:
- Automatic trace correlation — reads active OpenTelemetry span from context, adds
traceIdandspanIdto every log - Child loggers —
log.child({ requestId })propagates context through request lifecycle - Log level filtering — env-controlled, zero-overhead when below threshold
- Transport abstraction — ships stdout by default, extensible for Datadog/Logtail/etc.
Results
- 1,200+ weekly npm downloads after 3 months
- Adopted as standard logging library across all new Node.js services at my employer
- Full test coverage (98%) — extensive edge case testing for async context propagation
- Listed in the OpenTelemetry JS contrib ecosystem docs as a community library
Key Learnings
AsyncLocalStorage for context propagation is powerful but has surprising gotchas with unhandled promise rejections — they can “escape” the storage context. The solution was wrapping the request handler at the framework level, not just at the logger level.