Contributing
Setup
pnpm install # repository development uses Node >=22.13, pnpm >= 11.5.3
pnpm check # the full gate — run before pushingpnpm check is the same gate CI runs on every pull request: format check (oxfmt), lint (oxlint), typecheck, tests (vitest), builds (rolldown + tsc), size budgets, export map verification, public type surface check, API report check, and npm pack validation. CI additionally runs pnpm bench:gate.
Node Version Policy
- Repository development: use Node
>=22.13.0. The rootpackage.jsonenginesfield and local tooling are intentionally set to this floor. - Full CI gate: runs
pnpm checkon Node 22 and 24, with releases built on Node 24. - Published package runtime compatibility: packed packages are smoke-tested as consumers on Node 20.19.0, 22, and 24. Node 20.19.0 is the runtime compatibility floor for Node consumers; it does not lower the repo development toolchain requirement.
Repository Layout
packages/core platform-neutral kernel: logger, record/event model, registry,
context, middleware, integration API, console/memory/test/batch
transports, json/safe-json/ndjson codecs
packages/browser browser transports + integrations
packages/node node transports + integrations + AsyncLocalStorage context
packages/processors middleware/processor toolbox
packages/codecs fast-event-json, built-in msgpackr, projector
packages/otel|sentry|datadog|elastic|loki|cloudwatch|database destination adapters
examples/ runnable examples per platform
scripts/ build/verify/bench/release tooling
docs/ this documentation
api-reports/ checked-in public API surface per packageTurbo orchestrates build/test/typecheck with caching; scope work with pnpm exec turbo run test --filter=...@loggerjs/core (the package and its dependents).
Rules That Fail CI
Commits follow Conventional Commits, enforced by commitlint. Allowed scopes: browser, build, codecs, core, deps, docs, examples, node, otel, processors, release, repo.
API reports: any public-surface change (including JSDoc on exported symbols) requires regenerating reports — pnpm build && pnpm api:report — and committing the diff. pnpm api:check fails on drift.
Size budgets (scripts/check-size-budgets.mjs): every package has raw + gzip ceilings checked after build. Raise a budget only together with the change that justifies it, in the same or an adjacent commit, with the reason in the message.
Component docs (scripts/verify-component-docs.mjs): every public transport-*, *-transport, or integration-* subpath must be listed in the matching transport/integration import-boundary docs. New components also need stability and reliability notes in the same change.
Benchmark gate (pnpm bench:gate): hot-path scenarios are limited as paired A/B ratios against the matching pino baseline. Limits are generous — they catch structural regressions (an accidental allocation per log, a dropped fast path), not noise. If your change legitimately shifts a ratio, update the limits in scripts/check-bench-regression.mjs with justification.
Changesets: user-facing changes to published packages need a changeset (pnpm changeset); pure repo tooling does not.
Engineering Conventions
- Core stays platform-neutral: no DOM types, no Node built-ins, feature-detect via
globalThis. The public type surface must compile withoutlib.dom. - Prefer stabilization over surface expansion before v1: harden existing transports/integrations first. A new built-in component needs a production use case, runtime-appropriate package placement, tests, stability docs, import-boundary docs, and size-budget evidence.
- The pipeline never throws into the app. Middleware, processors, codecs, and transports are error-isolated; failures report through
onInternalErrorand meta counters. New code keeps that property. - Codecs must not lose logs: wrap risky encodes and fall back to
safeJsonStringify; count fallbacks in meta. - Shared objects are frozen, replaced not mutated (
record.tags,record.ctx). - Hot-path changes need numbers. Run
pnpm bench:nodebefore/after and put the relevant line in the commit message; updatedocs/BENCHMARKS.mdwhen the snapshot moves materially. Benchmark warmup must stay proportionate to iterations — see the warmup note in BENCHMARKS.md for the time we got this wrong. - Performance has a documented boundary: read the record-pipeline decision in ARCHITECTURE.md before proposing a record-bypassing fast path.
Tests
Vitest per package, test/*.test.ts. House style:
- Pin behavior with hostile inputs (circular refs, BigInt, frozen objects, throwing callbacks) — most past regressions were caught by exactly these.
- Use
testTransport()from core for transport-side assertions; it provides snapshots, stats, andwaitForCount. - New transports/integrations ship with teardown tests: patch, capture, restore, assert no double-capture.
Additional CI gates cover the runtime and quality surface:
pnpm test:e2e:browserruns the browser E2E suite in Chromium, Firefox, and WebKit.pnpm compat:runtimes -- --runtime=bun|deno|workerssmoke-tests the packed packages in Bun, Deno, and a workerd/Miniflare runtime.pnpm test:qualityruns coverage thresholds, mutation testing, and the concurrent soak runner.pnpm test:live:localstarts Docker-backed Elasticsearch and Loki instances, writes real log events through the transports, and queries those services back.pnpm test:live:externalwrites to and queries Datadog Logs and CloudWatch Logs. It requiresDATADOG_API_KEY,DATADOG_APP_KEY,AWS_REGION,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, andCLOUDWATCH_LOG_GROUP; usepnpm test:live:configto audit which variables are present without printing secret values.
Releasing
See RELEASE.md. Short version: changesets accumulate on main; the release workflow versions, builds, runs the full gate, and publishes with provenance.