Skip to main content

What is Sawdust?

Sawdust is one logging API for every JavaScript runtime you ship.

@cues/sawdust is a runtime-agnostic logging toolkit built on LogLayer. Import a single logger, call .info() / .warn() / .error(), and the right implementation runs whether your code executes in Node.js, the browser, or a background worker.

Core is provider-agnostic — console, pretty, and Consola transports out of the box, and no vendor lock-in. Need Datadog logs, APM trace injection, or RUM? Add @cues/sawdust-datadog and wire it through the extraTransports / plugins seams.

import { logger } from '@cues/sawdust/logger'

logger.info('checkout completed', { orderId, amountCents })
logger.error('payment failed', err, { orderId })

No per-environment logger. No factory to wire up before you can log. No conditional imports.

The problem it solves

Every non-trivial JavaScript codebase eventually grows a pile of logging glue:

  • A server logger with Datadog and pretty output.
  • A separate browser logger with Datadog browser logs and RUM.
  • A "get the request id into the logs" helper that everyone copies.
  • A different Jest/Vitest mock in every package.
  • Ad-hoc error instanceof Error ? error.message : 'unknown' at hundreds of call sites.

Each of those is written slightly differently in every package. Sawdust is that layer, built once, correctly, and shared everywhere — so consuming code just imports logger and moves on.

What you get

  • One API across runtimes — the same import and calls in Node, browser, and workers. The correct .node / .web build resolves automatically through conditional exports.
  • A singleton façade that upgrades in place — import logger before it is configured; bootstrap later promotes it to a richer logger without breaking existing imports.
  • Pluggable transports — console, pretty terminal, and Consola in core; Datadog server logs (+ APM trace injection), Datadog browser logs, and RUM via the @cues/sawdust-datadog provider. Enable per environment.
  • Request-scoped contextwithRequestContext uses AsyncLocalStorage on Node to stamp every log with requestId, userId, and route automatically.
  • A service locator — shared libraries call getLogger() / getRumClient() and always get the canonical instance. No props, no providers, no DI container.
  • Testing built inresetLoggerLocator, noopLogger, and mock helpers make suites deterministic.
  • Structured errors — round-trippable error serialization and context sanitization.

Where to go next

You want to…Read
Install and log in 5 minutesGetting Started
Sell it to your teamWhy Sawdust?
See real Node / browser flowsGuides
Understand how it works insideArchitecture
Copy a battle-tested patternPattern Catalog