Structured decision tracing with nested log trees for Node.js + TypeScript.
Capture execution paths once, then export them as JSON, flat events, ASCII trees, or Mermaid diagrams.
npm install @sharetech-labs/logtreeimport { Trace } from '@sharetech-labs/logtree';
const $order = new Trace('received-new-order', { orderId: 'ORD-4821' });
const $pricing = $order.log('calculate-pricing', { subtotal: 284.97 });
$pricing.log('apply-discount', { code: 'SAVE20', saved: 56.99 });
$order.log('charge-payment', { amount: 227.98, method: 'card' });
console.log($order.summary());Output:
received-new-order
├─ calculate-pricing (subtotal=284.97)
│ └─ apply-discount (code=SAVE20, saved=56.99)
└─ charge-payment (amount=227.98, method=card)
const json = $order.toJSON();Good for API responses, snapshots, or writing trace artifacts in CI.
const events = $order.flat();Good for analytics/event pipelines where each entry needs an id, timestamp, and _depth.
const diagram = $order.mermaid();
console.log(diagram);Output:
graph LR
root["received-new-order"]
n1["calculate-pricing"]
n2["apply-discount"]
n3["charge-payment"]
root --> n1
root --> n3
n1 --> n2
Use this directly in GitHub Markdown docs, issues, and PR descriptions.
new Trace(id: string, data?: Record<string, unknown>, options?: { consoleLogging?: boolean })
$order.log(label: string, data?: Record<string, unknown>): TraceContext
$order.toJSON(): TraceJSON
$order.flat(): FlatEntry[]
$order.summary(): string
$order.mermaid(options?: { direction?: 'TD' | 'LR' | 'BT' | 'RL'; order?: boolean }): string
$order.setConsoleLogging({ enabled: boolean }): Trace
// The returned context from log() supports:
$step.log(label: string, data?: Record<string, unknown>): TraceContextTraditional logging is flat — a stream of console.log calls. logtree is different: every .log() call returns a child context, and that return value is the whole point. Capturing it is how you build a tree. Ignoring it is how you record a leaf.
// Ignoring the return value → leaf (terminal fact, no children)
$trace.log('CONFIG_LOADED', { region: 'us-east' });
// Capturing the return value → scope (a phase that will contain sub-steps)
const $pricing = $trace.log('PRICING');
$pricing.log('DISCOUNT_APPLIED', { saved: 12.00 });
$pricing.log('RESULT', { total: 88.00 });If you never capture return values, you're back to flat logging and the library isn't doing anything for you.
Functions should accept a TraceContext and log into whatever scope they're given. The caller decides where the work nests in the tree — the function doesn't need to know.
import type { TraceContext } from '@sharetech-labs/logtree';
function resolveEntry(db: DB, input: Input, $trace: TraceContext) {
$trace.log('SEARCHED', { candidates: 50, query: input.code });
$trace.log('MATCH', { entryId: winner.id });
}
// Caller decides where it nests
const $resolution = $trace.log('RESOLUTION');
resolveEntry(db, input, $resolution);This keeps functions composable — the same function produces the same trace nodes regardless of where it's called from.
A label's meaning comes from its position. Deep inside a scope, short names like RESULT or MATCH are clear because the parent provides context. At the root, labels need to be self-describing.
CLAIM
├─ CONFIG_LOADED ← root-level: self-describing
├─ RESOLUTION ← scope label: names the phase
│ ├─ SEARCHED ← short: parent says what was searched
│ └─ MATCH ← short: parent says what matched
├─ PRICING
│ └─ LINE (code=99213)
│ └─ RESULT ← "RESULT" is clear inside PRICING > LINE
└─ CLAIM_RESULT ← root-level: self-describing
A $ prefix on trace variables (e.g. $trace, $step, $line) visually distinguishes trace contexts from business data. This is optional but helps readability, especially when traces are threaded through many functions.
import { Trace } from '@sharetech-labs/logtree';const { Trace } = require('@sharetech-labs/logtree');npm run dev # vitest watch
npm run test # run tests once
npm run test:coverage # coverage report
npm run lint # type check
npm run build # tsup build
npm run ci # build package
npm run check-exports # verify package type exports- Fork and create a branch.
- Run
npm ci. - Add tests in
tests/for behavior changes. - Run
npm run cibefore opening a PR.
MIT © Sharetech Labs