@bksystems/structured-tree-log
v2.1.0
Published
Winston-style structured logger for Node (ESM/CJS) with hierarchical trace trees, history output, and optional rotated file transport.
Maintainers
Readme
@bksystems/structured-tree-log
Winston-style structured logger for Node (ESM + CommonJS). Each log emits a header line, a JSON context block, and an indented history tree from child() breadcrumbs. Optional file transport with size rotation.
Install
npm install @bksystems/structured-tree-logRequires Node 18+ (uses node:crypto, node:fs, node:path only — no runtime dependencies).
Import
import createLogger, { LEVELS, structuredTreeLog } from "@bksystems/structured-tree-log";The default export is the same named export createLogger.
CommonJS
const { createLogger, LEVELS, structuredTreeLog } = require("@bksystems/structured-tree-log");Quick start
import { createLogger } from "@bksystems/structured-tree-log";
const log = createLogger({
service: "my-app",
minLevel: "info",
});
log.info("Server listening", { port: 3000 });
const job = log.child({ method: "worker", action: "process" });
job.debug("tick", { n: 1 });Log levels
error (0) → warn → info → http → verbose → debug → silly (6). Lower index = higher priority. A log prints if its level rank is ≤ the configured minLevel.
createLogger(options)
| Option | Description |
| ------------- | ---------------------------------------------------------------- |
| service | Label in the header (default structured-tree-log). |
| minLevel | One of the level names above (default info). |
| traceId | Root trace segment; if omitted, a UUID is generated. |
| baseContext | Object merged into every emit’s context (shallow). |
| history | Initial breadcrumb frames (array of plain objects). |
| file | Optional file transport — see File transport. |
API returned by createLogger
error,warn,info,http,verbose,debug,silly(message, context?)log(level, message, context?)— level as name or numeric rankchild(part)— extend history;partis a string (item bind shorthand) or a plain object framesetMinLevel(level),getMinLevel(),getTraceId(),getHistory()
Record shape (each emit)
- Header —
[ISO8601] [service] [LEVEL] message(message only; no trace in the header). - JSON —
{ "traceId": "…", "context": { … } }wherecontextisbaseContextmerged with the per-call context.traceIdis not duplicated insidecontext. - History tree — one line per history frame, optional
└ result → …, optional sub-step lines under a frame.
Hierarchical traceId
Every child() appends /<newUuid> to the parent traceId, e.g.root-uuid/child-uuid/grandchild-uuid. Parallel children from the same parent share the same prefix and differ in the last segment.
History tree rules
*line — Structural fields only:method,action,step/stepOf,itemId, plus any extra keys (notlineMessage,result,subSteps).└ result → …— Uses the first info / warn / error message stored aslineMessageon that frame. Logcontext.resultis not shown in the tree (still appears in JSONcontextif you pass it).itembind (action: 'item', nostep) — If there is nolineMessage,└can show the id fromchild({ result }).- Pipeline steps (frames with
step+stepOf, notaction: 'batch') — Share the same*column as the precedingitemframe (siblings under the same visual block). - Sub-steps — If the log context includes
subStep, an entry is appended to the current leaf’ssubStepsand printed as a mini step:* … › sub <id>then└ result → <message>.
Depth uses two spaces per level; a frame with a └ line advances depth by two for following generic frames (see source comments).
File transport
Enable with file: { dirname, … }. The directory is created if missing. Each console record (header + JSON + tree lines) is written as one appended block (same content as stdout).
| Field | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| dirname | Required to enable file logging (absolute or relative path). |
| filename | Template (default %{SERVICE}%-%DATE%.log). Placeholders: %DATE%, %{DATE}%, %{SERVICE}% (sanitized). %DATE% is UTC YYYY-MM-DD. |
| maxSize / maxsize | Max size of the current file before rotation: number (bytes) or string (512k, 10m, 1g). Omit for no size rotation. |
| maxFiles | The current segment is always name.000.log (first file of the day for a %DATE% template). Omit → keep 5 older segments (name.001.log … name.005.log). null, false, 0, Infinity, or 'unlimited' → no cap. Index 0–999 uses three digits (000…999); from 1000 onward the width is natural (name.1000.log, …). Positive integer → that many archived segments (not counting .000). |
Writers are cached per (dirname, filename template, service, maxSize, maxFiles) so child() loggers with the same file config share one rotator.
Example with file + debug
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createLogger } from "@bksystems/structured-tree-log";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const log = createLogger({
service: "my-service",
minLevel: "debug",
file: {
dirname: path.join(__dirname, "output"),
filename: "%{SERVICE}%-%DATE%.log",
maxSize: "50m",
maxFiles: 14,
},
history: [{ method: "main", action: "bootstrap", result: "my-service" }],
});Exports
createLogger— factoryLEVELS— level name → rank mapstructuredTreeLog— pre-built default instancedefault— same ascreateLogger
Package layout (this repo)
| Path | Role |
| -------------- | ------------------------------------------------------------ |
| index.ts | TypeScript source (compiled to dist/) |
| dist/ | Build output (.js, .d.ts, source maps) — not committed |
| LICENSE | MIT |
| CHANGELOG.md | Release notes |
| examples/ | Runnable examples (after npm run build + local path) |
Development
npm install
npm run build # tsc → dist/
npm run verify # build + run examples/quickstart.mjs against dist/
npm pack # runs prepack → build, then creates .tgz (inspect contents)
npm run clean # remove dist/Publish (maintainers)
npm login— account must be allowed to publish@bksystems/*(org member or owner).- Set
"author"inpackage.jsonif you want it on the npm page. npm pack— confirm the tarball listspackage.json,dist/*,README.md,LICENSE,CHANGELOG.md(nosrc/).npm publish --access public— enable 2FA / token policy required by npm for publishing.
Bump version with npm version patch|minor|major before subsequent publishes.
