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
Maintainers
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-loggersQuick 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 summariesThe 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.bodyis null at construct time (Next.js pre-hydration, scripts in<head>), mount is deferred toDOMContentLoaded→ microtask →requestAnimationFramefallback chain. - Window-level, capture-phase shortcut. MUI dialogs, code editors,
rich-text inputs all
stopPropagationon keydown for their own hotkeys; the panel listens onwindowin 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
