Build on top of evlog
evlog is designed to be extensible from both ends. There are two distinct angles to "build on top" — and they answer different questions:
flowchart LR
App["Your app"] --> Pipeline["evlog drain pipeline"]
Pipeline --> Drain["Drains<br/>(Axiom, Datadog, fs, ...)"]
subgraph extend [Extend]
direction TB
Plugins["Plugins"]
Enrichers["Enrichers"]
CustomDrains["Custom drains"]
Catalogs["Error / audit<br/>catalogs"]
end
extend -.->|"hook into"| Pipeline
Pipeline -->|"events"| Stream["evlog/stream<br/>in-process pub/sub"]
Stream --> StreamServer["mini HTTP server<br/>(SSE bridge)"]
subgraph observe [Observe]
direction TB
InProc["Sync subscribers<br/>(scripts, tests)"]
Browser["Browser tab<br/>(devtool)"]
Cli["CLI / curl"]
FsReader["readFsLogs<br/>tailFsLogs"]
end
Stream -.->|"in-process"| InProc
StreamServer --> Browser
StreamServer --> Cli
Drain -->|"fs adapter"| FsReader
Observe — pages here
| What | When you want it | |
|---|---|---|
| Stream API | createStreamDrain(), getDefaultStream() — in-process subscribe / iterate | A consumer lives in the same Node process as your app |
| Stream server | Mini HTTP server on its own port that exposes the stream over SSE | A browser tab, a CLI, an external devtool needs to subscribe |
| Reading FS logs | readFsLogs() / tailFsLogs() — replay or follow the NDJSON drain | You want history (replay yesterday's errors, post-incident triage) |
| Identity headers | User-Agent: evlog/<version> + X-Evlog-Source on every drain request | You want receivers (Axiom, Datadog…) to identify evlog traffic |
| Recipes | Copy-paste patterns: build a devtool, curl + jq, replay-then-live, aggregate | You want a starting point |
Extend — where the docs live
These surfaces existed before this section — links into their canonical pages:
| What | Doc | |
|---|---|---|
| Plugins | definePlugin() — opt into any subset of evlog's lifecycle hooks | Custom adapter / plugin |
| Custom drains | defineDrain() / defineHttpDrain() — ship events anywhere | Building blocks: pipeline, HTTP drain |
| Custom enrichers | defineEnricher() — derive context (geo, deploy id, tenant…) | Custom enrichers |
| Error catalogs | defineErrorCatalog() — typed error factories with module-augmentation | Catalogs |
| Audit catalogs | defineAuditCatalog() — typed audit actions | Audit overview |
| Framework integrations | createMiddlewareLogger() + helpers — bring evlog to any HTTP framework | Custom integration |
| Catalogs as packages | Publish a catalog as a reusable npm package (Stripe errors, AWS audit…) | Catalogs as packages |
A note on serverless
Both observe-side network features (the stream server for live subscription, the in-process stream primitive) work everywhere a Node-like long-lived process runs — local dev, self-hosted servers, containers (Fly, Railway, Coolify), VMs.
They do not work on serverless platforms (Vercel Functions, Cloudflare Workers, AWS Lambda) because each invocation is an isolated process. Use a real broker (Redis Streams, NATS, Pub/Sub) for cross-instance fan-out in those environments.
The fs reader, identity headers, and the entire Extend axis work everywhere — they are not bound to a long-lived process.
Custom Integration
Build your own evlog framework integration using the toolkit API — defineFrameworkIntegration, createMiddlewareLogger, AsyncLocalStorage, and the full drain/enrich/keep pipeline.
Stream API
Subscribe to wide events flowing through evlog, in-process, with createStreamDrain — sync listeners, async iterators, and a ring buffer.