Structured logging for Node, Next.js and the edge

Log the event,
not just the text.

Write one message template and logit captures a structured event from it: the template, every named value, the ambient request context, the error with its cause chain. Every sink — console, file, Seq, OTLP, your own — sees the same data.

Read the docs

MIT · zero dependencies · ESM + CJS · TypeScript

One call, three sinks
shop/orders.ts · inside a request
log.info('Order {OrderId} shipped to {@Address} in {Elapsed:0.0} ms',
  1042, { city: 'Budapest', zip: '1011' }, 83.2);
consoleSink() on a terminal · colour, a short time, the source, extra properties dimmed
08:12:03.123 INF [shop.orders] Order 1042 shipped to { city: "Budapest", zip: "1011" } in 83.2 ms RequestId=req_9f3a
How it works · 01

Four ideas, borrowed from the library that got .NET logging right.

  1. 01

    Write a template

    log.info('User {UserId} bought {@Order}', 42, order). The holes are named, so the values keep their names. @ captures an object's structure; $ forces a string.

  2. 02

    Capture, don’t format

    The message is never rendered at the call site. The event stores the template and the captured values — depth-limited, cycle-safe, redacted by key — and each sink renders them its own way, or not at all.

  3. 03

    Enrich from context

    Run a request inside LogContext and every event written in it carries the request id, however deep the call stack and across every await. Enrichers add the host, the environment, the trace.

  4. 04

    Ship to any sink

    Pretty on a terminal, JSON in a container, CLEF to Seq, OTLP to a collector, a rolling file, a batched HTTP endpoint — each with its own minimum level, each flushed on close().

Features · 02

What Serilog taught .NET, for the JavaScript runtimes.

Message templates

Named holes, positional holes, :format and ,alignment — the messagetemplates.org grammar Serilog uses, so a template is a queryable event type, not a string to regex later.

Docs

Structured capture

Plain objects are data and come through whole; class instances need @; $ stringifies. Depth, string and collection limits, cycle safety, transforming policies and key-based redaction all apply once, at capture.

Docs

Levels, overrides, switches

Six levels. A minimum per source prefix — next at warn, app.db at verbose — and a LevelSwitch you flip at runtime from an admin route or a signal.

Docs

Ambient context and enrichers

LogContext.run({ requestId }, …) rides on AsyncLocalStorage, so the id reaches every event under it. Enrichers add the host, the process, the environment, the trace.

Docs

Sinks, and loggers as sinks

Console, rolling file, batched HTTP, Seq, OTLP, memory — each with its own minimum level. A logger is a sink too, so sub-loggers, conditional and audit sinks come for free.

Docs

Output templates, JSON, CLEF, pretty

{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception} works as written. Flat JSON for log pipelines, CLEF with the @i event-type hash for Seq, colour for the terminal.

Docs

Errors done properly

Pass the error first and the event carries its name, message, stack, own properties, the cause chain and an AggregateError’s members — rendered by every formatter, structured in every sink.

Docs

Request logging with a diagnostic context

One completion event per request for Next.js route handlers, middleware and onRequestError, and for Express. Set a value anywhere in the request and it lands on that event.

Docs

Timed operations

log.timed('Sync {Tenant}', fn) completes or abandons with Elapsed and Outcome. Or begin one, enrich it as you go, and let using abandon it if you forget.

Docs

Node, Bun, edge, browser. Zero dependencies.

The root entry has no Node imports and ships ESM and CJS with types. Node extras, Next.js, Express and OpenTelemetry are subpaths you import only where they apply.

Docs
Integrations & roadmap · 03

Where it writes today, and what comes next.

Out of the box logit writes to the console, rolling files, any HTTP endpoint, Seq and OpenTelemetry collectors, and hooks into Next.js and Express. The rest of Serilog’s ecosystem is a roadmap, not a promise — here is the order we’re working in.

  • Console sink

    Available today

    Pretty on a terminal, JSON on a pipe, CLEF or a text output template on request; stderr from a level; the browser console with real objects.

    Docs
  • Rolling file sink

    Available today

    Synchronous appends (nothing lost on a crash), rolling by minute / hour / day / month and by size, a retained-file count.

    Docs
  • HTTP sink

    Available today

    Batches with a size and an interval, retries with backoff, a bounded queue, NDJSON / array / CLEF bodies, pagehide flush in browsers.

    Docs
  • Seq

    Available today

    CLEF over HTTP to /ingest/clef with the API key header — Seq’s native format, @i event types and all.

    Docs
  • OpenTelemetry

    Available today

    OTLP/HTTP JSON to any collector without the SDK, honouring OTEL_EXPORTER_OTLP_*; or a bridge sink that hands events to an SDK LoggerProvider.

    Docs
  • Request logging for Next.js and Express

    Available today

    One completion event per request with method, path, status and elapsed time; a diagnostic context; withLogging() for route handlers and middleware, createOnRequestError() for instrumentation, requestLogger() for Express.

    Docs
  • Expression language

    Up next

    Filters, conditional sinks and ExpressionTemplate formatting written as text — RequestPath like '/health%' — instead of predicates in code.

  • Configuration from JSON and env

    Up next

    A whole logger from a JSON document or environment variables, with a registry that maps sink names to sinks.

  • Durable HTTP and Seq shipping

    Up next

    A disk buffer in front of the network, so events written while the collector is down are sent after a restart.

  • Remote level control

    Up next

    Seq (or any endpoint) tells the logger which minimum level to run at, and a LevelSwitch follows.

  • Automatic trace context

    Up next

    Trace and span ids read from the active OpenTelemetry span on every event, with no getter to write.

  • Vendor sink packages

    Planned

    Datadog, Grafana Loki, Splunk HEC, Elasticsearch / ECS, CloudWatch, Application Insights, Sentry.

  • Worker-thread transports

    Planned

    Heavy sinks run off the event loop, the way pino transports do.

  • Pattern-based masking

    Planned

    Emails, IBANs, card numbers and your own patterns masked inside any string, not only by key.

  • Fastify, Hono, Koa, NestJS

    Planned

    Request logging and a LoggerService for the other servers.

One template in. One structured event out, everywhere.

Get started