Extend

FS reader

Replay and tail the local NDJSON drain with readFsLogs and tailFsLogs — works in-process or from any external Node tool, survives restarts.

The filesystem drain writes wide events as NDJSON to .jsonl files under .evlog/logs/ (one file per day, e.g. 2026-05-08.jsonl, plus rotation suffixes like .1.jsonl when size-based rotation is enabled). The evlog/fs module also ships readers that let any Node tool replay or follow that history without hooking into the running app.

tailFsLogs()·following
.evlog/logs/2026-05-08.jsonl0/5
[info]POST /api/auth/login → 200
[info]GET /api/me → 200
[error]POST /api/checkout → 500
[info]POST /api/email → 200
[info]GET /healthz → 200
.evlog/logs/2026-05-09.jsonlawaiting rotation
[info]POST /api/auth/refresh → 200
[warn]GET /api/cart → 401
[info]POST /api/checkout → 200
reader0/8
consumed0
errors0
files seen1
NDJSON · 1 line / event daily rotation partial-write safe

Build a script that consumes evlog's local NDJSON history (no app hook required).

  • Confirm the filesystem drain is wired up (evlog/fs adapter writing NDJSON to .evlog/logs/*.jsonl)
  • For replay: import readFsLogs from evlog/fs and iterate for await (const event of readFsLogs({ since, until, level, filter }))
  • For follow mode: import tailFsLogs and iterate the same way. It watches for new lines, handles rotation, and accepts an AbortSignal
  • Apply filters at read time (level, since, until, custom filter predicate) instead of post-processing
  • Treat malformed lines as silently skipped (partial writes happen), and never crash the script on a bad line

Docs: https://www.evlog.dev/extend/fs-reader

Replay history

import { readFsLogs } from 'evlog/fs'

for await (const event of readFsLogs({ since: '2026-03-01', level: 'error' })) {
  console.log(event.timestamp, event.action ?? event.message)
}

readFsLogs(options) walks the NDJSON files in chronological order, parses them line by line, and yields events that pass all filters. Files outside the date window are skipped entirely.

Options

OptionTypeDescription
dirstringDirectory to read from. Default: .evlog/logs.
sinceDate | stringYield events with timestamp >= since.
untilDate | stringYield events with timestamp <= until.
levelLogLevel | LogLevel[]Filter by event level.
filter(event) => booleanCustom predicate.

Malformed lines (partial writes, manual edits) are silently skipped, so your script never crashes on a bad line.

Live tail

import { tailFsLogs } from 'evlog/fs'

const ac = new AbortController()
process.on('SIGINT', () => ac.abort())

for await (const event of tailFsLogs({ signal: ac.signal })) {
  console.log('live:', event.action ?? event.message)
}

tailFsLogs(options) first yields existing events (unless fromEnd: true), then keeps yielding new ones as they're appended, including events written into newly created daily files. Partial writes split across polls are recombined transparently.

Tail-specific options

OptionTypeDescription
pollIntervalMsnumberPolling interval. Default: 500ms (minimum 50ms).
fromEndbooleanSkip existing events; only yield future ones. Default: false.
signalAbortSignalStop tailing when aborted.

All readFsLogs options also apply.

Use cases

  • A local Electron / Tauri dashboard reading .evlog/logs/ from a target project directory
  • A CI report aggregator that scans logs after a test run
  • A grep-style CLI that pipes filtered events into jq
  • Replaying historic events into a dashboard before switching to a live in-process subscription. See the replay-then-live recipe