All projects

Work · 2026

jsDevLogging

Logging for browser and Node with no runtime dependencies, usable from a script tag in PHP projects.

LiveSource

At a glance

  • No runtime dependencies, 17 KB minified as a browser bundle
  • Transports for console, HTTP, WebSocket, localStorage and file
  • Buffers entries offline and sends them once the connection is back
  • Middleware pipeline for enriching, filtering and sampling entries
  • Sub-path exports, so unused transports never reach the bundle
  • 152 tests, ESM and CJS side by side, complete type definitions

Why another logging library

There is pino, there is winston, there is consola and a dozen more libraries besides. There is not much to be won on features. All of them do assume a bundler, though.

A traditional PHP site has none. There is a template with a script tag in it, and when something goes wrong in the browser the server never hears about it. For that case there is a 17 KB IIFE bundle: add the script tag, point HttpTransport at a hand-written logs.php, and the errors from the browser land somewhere you actually see them.

<script src="/assets/browser.min.js"></script>
<script>
  const logger = jsDevLogging.createLogger({
    transports: [new jsDevLogging.HttpTransport({ url: '/api/logs.php' })],
  });

  logger.error('Checkout failed', { orderId: 4711 });
</script>

No dependencies

dependencies is an empty object. Whatever the library needs is in the library, including a small event emitter with exactly the three methods ConfigManager uses.

The reason is a practical one. A logger runs on every request, and above all it keeps running when other things have already stopped working. A transitive dependency nobody ever read is particularly awkward in that position.

Offline behaviour

You need logs most urgently when something is not working, and in a browser that often means the connection is gone. A transport that discards its entries at exactly that moment is not much help.

BufferedTransport therefore listens for the browser’s offline events, keeps collecting into the buffer instead of sending, and sends everything once the connection is back. The HTTP transport batches entries anyway and retries failed sends with a growing delay.

Sub-path exports

Transports, formatters and middleware sit behind sub-path exports of their own. Write to the console only and you never import the WebSocket transport, so it never reaches your bundle, no tree-shaking involved.

Alongside that: ESM and CJS side by side, complete .d.ts types, and 152 tests that run before every npm publish.