npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dev-loggers

v6.0.6

Published

Zero-dependency logging primitives + draggable in-page debug panel. Root export is the logger API (debug, getLogger, attachSink); `dev-loggers/panel` is the optional in-page UI.

Downloads

728

Readme

dev-loggers

Zero-dependency logging primitives + a draggable in-page debug panel. One tree-shakeable package, isolated subpath exports — pull in only what you use.

| Import | Side effects | DOM required | What you get | |-----------------------|--------------|--------------|---------------------------------------------------------------------------------------------------------------------------------| | dev-loggers | None | No | The full logger API (re-exported from dev-loggers/loggers at the root for the most common case). | | dev-loggers/loggers | None | No | Logger, PerformanceLogger, BufferedLogger, LoggerRegistry, ConsoleSink, Sink, LogEvent, attachSink, debug, runtime controls. Safe for Node / SSR / service workers. | | dev-loggers/panel | Injects CSS | Yes | DebugPanel, JsonView, mountDebugPanel, ScreenPosition, makeResizable, makeDraggable. Draggable in-page debug overlay with per-id tabs, JSON state inspection, namespace toggles, keyboard shortcut. |

Future tools (e.g. dev-loggers/perf, dev-loggers/inspector) will live as additional subpaths without polluting either the root or each other.

Installation

npm install dev-loggers

Quick start — core only (Node, server, library)

import { debug, attachSink } from 'dev-loggers';            // or 'dev-loggers/loggers'

debug('audio:attach', { tag: 'VIDEO', src: 'blob:…' });
debug.scope('audio:attach').log('attempt 1');

attachSink({ name: 'remote', write(event) { /* … */ } });

Quick start — with the panel (browser app)

import * as loggers from 'dev-loggers';
import { mountDebugPanel } from 'dev-loggers/panel';

// One-liner; returns a disposer suitable for useEffect / onCleanup.
const dispose = mountDebugPanel({ loggers, position: 'bottomRight' });

// Anywhere in your app:
loggers.debug('audio:attach', { tag: 'VIDEO' });

Shift+Alt+D toggles the panel by default. Override via shortcut (e.g. 'ctrl+alt+d'), or pass null to disable.

React / Preact

import * as loggers from 'dev-loggers';
import { mountDebugPanel } from 'dev-loggers/panel';
import { useEffect } from 'react';   // or 'preact/hooks'

export function App() {
  useEffect(() => mountDebugPanel({ loggers }), []);
  return <YourApp />;
}

The constructor is idempotent: re-mounting in React StrictMode (which fires effects twice in dev) tears down the prior panel before constructing a new one, so you never end up with duplicate panels or orphaned key listeners.

Solid

import { onMount, onCleanup } from 'solid-js';
import * as loggers from 'dev-loggers';
import { mountDebugPanel } from 'dev-loggers/panel';

onMount(() => {
  const dispose = mountDebugPanel({ loggers });
  onCleanup(dispose);
});

Logger core API (dev-loggers / dev-loggers/loggers)

debug(id, ...args) — canonical structured entry point

import { debug } from 'dev-loggers';

debug('audio:attach', { tag: 'VIDEO' });           // one-shot
const log = debug.scope('audio:attach');           // fluent
log.log('attempt 1');
log.log('ok', { state: 'running' });

Each call produces a structured LogEvent:

{
  namespace: 'audio',           // everything before the first ':'
  id: 'audio:attach',           // full id string
  level: 'debug',
  args: ['[audio:attach]', { tag: 'VIDEO' }],
  data: { tag: 'VIDEO' },       // first object arg, hoisted for inspection
  timestamp: 1719859200000,
}

UI sinks (like the panel) group entries by id into per-id tabs and render data as a collapsible JSON tree.

Namespaced loggers

import { getLogger, getPerformanceLogger, getBufferedLogger } from 'dev-loggers';

const { log, warn, error } = getLogger('MyApp', { color: 'cyan' });
log('starting');
warn('low memory');
error('connection failed');

const perf = getPerformanceLogger('Render');
perf.log('frame');              // first call: no elapsed
perf.log('frame');              // subsequent: ` (Nms)` appended

const buf = getBufferedLogger('Batch');
buf.log('a'); buf.log('b'); buf.flush();

Same namespace → same singleton. Re-registering with a different subtype throws.

Sinks (extension point)

import { attachSink, detachSink, Sink, LogEvent } from 'dev-loggers';

const networkSink: Sink = {
  name: 'network',
  write(event: LogEvent) {
    if (event.level === 'error') postToServer(event);
  },
};

const handle = attachSink(networkSink);
detachSink(handle);

The default ConsoleSink is always present unless explicitly removed.

Runtime controls

import {
  setLogAllMode, enableLogger, disableLogger, getLoggerStates, printLogCounts,
} from 'dev-loggers';

setLogAllMode(true);                       // force-enable everything
setLogAllMode(true, ['Audio', 'Render']);  // … but only these namespaces
enableLogger('Audio');
disableLogger('Render');
getLoggerStates();                         // [{ namespace, enabled }, …]
printLogCounts();                          // PerformanceLogger summaries

The panel subpath ships a built-in "Config" tab that calls these for you.

Panel API (dev-loggers/panel)

import { DebugPanel } from 'dev-loggers/panel';
import * as loggers from 'dev-loggers';

const panel = new DebugPanel({
  loggers,                  // optional: auto-attaches as a Sink + wires Config tab
  position: 'bottomRight',  // initial position on first-ever launch
  width: 600, height: 400,
  shortcut: 'shift+alt+d',  // or 'ctrl+alt+d', or null to disable
  snap: true,
  mount: true,              // false → call `parent.appendChild(panel.element)` yourself
  parent: document.body,    // custom mount root
});

panel.show(); panel.hide(); panel.toggle();
panel.debug('user.state', currentUser);       // JSON tree for state debugging
panel.log('namespace', { any: 'object' });    // legacy free-form log
panel.attachToLoggers(loggers);               // if you didn't pass `loggers` to ctor
panel.destroy();

Why the panel "just works" on first load

The constructor handles every flaky-init footgun:

  • Idempotent. A second new DebugPanel(...) tears down the prior instance — React StrictMode's double-effect-fire, Next.js HMR, fast-refresh, accidental double-mounts all settle into a single panel and a single key listener.
  • DOM-readiness gated. If document.body is null at construct time (Next.js pre-hydration, scripts in <head>), mount is deferred to DOMContentLoaded → microtask → requestAnimationFrame fallback chain.
  • Window-level, capture-phase shortcut. MUI dialogs, code editors, rich-text inputs all stopPropagation on keydown for their own hotkeys; the panel listens on window in capture phase so the shortcut fires regardless of where focus lives.
  • Idempotent CSS injection. Re-importing the module (HMR) doesn't duplicate <style> tags.

Environment variables (loggers)

| Var | Default | Notes | |-----|---------|-------| | LOG_COLORS_ENABLED | true | Turn off ANSI codes for non-color terminals. | | LOG_COLOR_DEFAULT | (none) | Fallback color for unconfigured loggers. | | LOG_ERRORS_ALWAYS | true | Emit errors even when the logger is disabled. | | LOG_WARNINGS_ALWAYS | false | Same for warnings. | | LOG_ERROR_TRACES | false | Append a stack trace to every error. |

Migration

From [email protected]

v6 keeps the same package name. Bump and re-install:

# package.json
- "dev-loggers": "^5.0.1"
+ "dev-loggers": "^6.0.0"

All imports stay the same. The v3-era addLogModule / LogModule aliases are still exported.

From @rw3iss/[email protected] (short-lived rename)

v6 of dev-loggers is the same code as @rw3iss/[email protected], renamed back to the original npm name. Swap the package:

# package.json
- "@rw3iss/dev-debug": "^1.0.0"
+ "dev-loggers": "^6.0.0"
- import { debug, getLogger } from '@rw3iss/dev-debug';
+ import { debug, getLogger } from 'dev-loggers';

- import { DebugPanel, mountDebugPanel } from '@rw3iss/dev-debug/panel';
+ import { DebugPanel, mountDebugPanel } from 'dev-loggers/panel';

From dev-debug-panel + [email protected] (legacy)

- "dev-debug-panel": "^1.2.3",
- "dev-loggers": "^3.1.1",
+ "dev-loggers": "^6.0.0",
- import { DebugPanel, ScreenPosition, mountDebugPanel } from 'dev-debug-panel';
- import { addLogModule, getLogger } from 'dev-loggers';
+ import { addLogModule, getLogger, debug } from 'dev-loggers';
+ import { DebugPanel, ScreenPosition, mountDebugPanel } from 'dev-loggers/panel';

addLogModule(...) still works as an alias of attachSink(...). The LogModule interface still exists as an alias of Sink. Legacy code compiles.

License

ISC