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

@knev/bitlogr

v3.4.8

Published

**BitLOGR** is a lightweight, bitwise logging library for JavaScript, designed for modular applications with no label dependencies between compilation units. It leverages bit flags for efficient, granular logging control, offering zero performance overhea

Readme

BitLOGR

BitLOGR is a lightweight, bitwise logging library for JavaScript, designed for modular applications with no label dependencies between compilation units. It leverages bit flags for efficient, granular logging control, offering zero performance overhead in production through lazy evaluation (thunks) and build-time optimizations.

The idea behind BitLOGR is to label execution paths (i.e., not INFO, WARN, DEBUG, ERROR and CRITICAL). This means bits could be chosen that correlate to event processing, an execution path or the like.

Key Features (USPs)

  • No Label Dependency: Each compilation unit can independently define, ignore, inherit, overwrite, or rename labels from submodules.
  • Flexible Label Management: Supports custom label sets with bitwise operations for combining and toggling logs.
  • Custom Logging Handler: It is possible to specify your own handler (e.g., console.log, custom function) for output flexibility.
  • Minimal Performance Hit: Use thunks, to defer argument evaluation ensuring minimal overhead when logging is disabled (in production).

Overview

Initializing BitLOGR

A BitLOGR instance is required in order to create a local logr. The local logr can be exported for further use.

To use BitLOGR, initialize the singleton instance. Create a local logr with labels and use the singleton instance to toggle labels:

  1. Labels: Define categories as an object mapping names to bit values (powers of 2).
  2. Toggled: Specify which labels are active using a key-value object with boolean values.
import { LOGR, l_array } from '@knev/bitlogr';

const LOGR_= LOGR.get_instance();
const logr_ = LOGR_.create({
    labels: {
        CXNS : 0b1 << 2,    // connections
        EVENTS : 0b1 << 3,
        HANDLERS : 0b1 << 4,
    }
});
const l_= logr_.l;

// Enable specific logs
LOGR_.toggle(l_, {
        EVENTS : true
    })

Label and Toggle Formats

Labels: An object where keys are strings and values are unique powers of 2 (e.g., 1, 2, 4, 8). These represent log categories and align with bits for efficient checking.     - Example: { EVENT: 1, CXN: 2, HANDLERS: 4 } → Bit positions 0, 1, 2.

Toggled: An object where keys match label names and values are booleans (true/false). Internally, this creates a bitmask.     - Example: { EVENT: true, CXN: false, HANDLERS: true } → Bitmask 0b101 (5).

The toggled bitmask is compared with the log statement’s bit value using bitwise AND (&) to determine if logging occurs.

Log Statement Format

The log method takes two arguments:

  1. nr_logged: A number (bitmask) representing the log categories (e.g., l_.EVENT | l_.CXN).
  2. argsFn: A thunk (function returning an array) that lazily provides log arguments.
logr_.log(l_.EVENT, () => ['Debug message']); // Logs if EVENT is toggled
  • Logging occurs only if labels are toggled true; the log function returns true/false in development (i.e., LOGR_ENABLED is true).
  • undefined: in production mode, the log function returns undefined.

Using OR for Labels

Combine labels with the bitwise OR (|) operator to log under multiple categories:

LOGR_.toggle(l_, {
        EVENTS : true,
        CXN: true
    }) // 0b101 (5)
    
logr_.log(l_.EVENT | l_.CXN, () => ['Debug or Info']);
// Logs because 0b101 & (0b001 | 0b100) = 0b101 & 0b101 = 0b101 !== 0

Utility Functions

BitLOGR provides helper functions to manage labels:

l_array(labels, start = 1)

Creates a label object from an array, assigning sequential bit values.

const l_ = l_array(['A', 'B', 'C']); // { A: 1, B: 2, C: 4 }
const l_shifted_ = l_array(['X', 'Y'], 4); // { X: 4, Y: 8 }

l_length(obj)

Returns the next power of 2 based on the maximum value in the object.

const l_len_= l_length({ a: 1, b: 4 }); // 8 (next power of 2 after 4)

l_concat(base, additional)

Combines label sets, appending new labels with next available bits.

const l_ = l_array(['A', 'B']); // { A: 1, B: 2 }
const l_more_ = l_concat(l_, ['C', 'D']); // { A: 1, B: 2, C: 4, D: 8 }

l_merge(obj1, obj2)

Merges two label sets, shifting conflicting values to unique bits. Same name with a different value throws.

const l1_ = { A: 1, B: 4 };
const l2_ = { C: 1, D: 8 };
const l_merged_ = l_merge(l1_, l2_); // { A: 1, B: 4, C: 16, D: 8 }

l_union(...objs)

Variadic merge by name. Collects the unique label names across all arguments in first-appearance order, discards the incoming bit values, and renumbers them contiguously from bit 0. Unlike l_merge it takes any number of sets, dedupes a shared name to one bit silently (no throw), and never needs l_LL/l_RR alignment. First-appearance order decides the bit position, so the first set keeps the low bits. Throws if the union would exceed 31 labels.

const l_ = l_union(
    { CONNECTIONS: 1, REFLECTION: 2 },
    { VALIDATION: 1 },              // collides with CONNECTIONS by value, but is a distinct name
    { VALIDATION: 4, HANDLERS: 8 }, // VALIDATION already seen -> deduped
);
// { CONNECTIONS: 1, REFLECTION: 2, VALIDATION: 4, HANDLERS: 8 }

l_LL(obj, shift)

Left-shifts all values in a label object.

const l_ = { A: 1, B: 2 };
const l_shifted_ = l_LL(l_, 2); // { A: 4, B: 8 }

l_RR(obj, shift)

Right-shifts all values in a label object.

const l_ = { A: 4, B: 8 };
const l_shifted_ = l_RR(l_, 2); // { A: 1, B: 2 }

l_assert(obj_actual, obj_required)

const b_res= l_assert(l_, { 
		DEL : 0b1 << 0,
		CXNS : 0b1 << 2,
	});

Examples

Importing Labels from Submodules

import { ..., logr as logr_ } from '@rootintf/json-msg';

console.log('logr_.lref', logr_.lref.get());

const LOGR_= LOGR.get_instance();
const l_= logr_.l;

LOGR_.toggle(l_, {
    EVENT : true
});

logr_.log(l_.EVENT, () => ['Debug warning']); // "WARN: Debug warning"

-- OR --

const obj_labels_= l_concat(
		logr_ipsme_.lref.get(), l_array(['DISCOVERY', 'WARP']) 
	);

let LOGR_ = LOGR.get_instance();
const logr_ = LOGR_.create({ labels : obj_labels_ });
const l_= logr_.l;

// doesn't use DISCOVERY AND WARP flags, so don't have to change it
// logr_ipsme_.lref= lref_; 

console.log('l_', l_.get());
LOGR_.toggle(l_, {
    // VALIDATION : true,
    // DISCOVERY : true,
    // WARP : true,
});

Note above that if the local the logr does not use altered labels such that the labels don't coincide with those of logr_ipsme_. That means if the alter labels are toggled, it won't affect the imported module anyways. If the labels were locally alter such that they didn't coincide with logr_ipsme_, then it is required to reassign the imported labels to be equal to the locally altered ones.

Reassigning Labels from Submodules

import { ..., logr as logr_json_msg_ } from '@rootintf/json-msg'
import { ..., logr as logr_evtlog_ } from './EventLog-mem.mjs'

const obj_labels_merge_= 
	l_merge(logr_evtlog_.lref.get(), logr_json_msg_.lref.get());

const obj_labels_concat_= l_concat(
		obj_labels_merge_, 
		l_array(['HANDLERS', 'DROPS', 'PROTOCOL_STATE', 'RECVER', 'SENDER']) 
	);

// create a new label ref
const lref_= lRef(obj_labels_concat_);

// change the label refs of the imported modules to reflect the changes
logr_json_msg_.lref= lref_;
logr_evtlog_.lref= lref_;

const LOGR_= LOGR.get_instance();

// don't let create() make a new ref
const logr_= LOGR_.create(); 
// assign lref_ from here
logr_.lref= lref_;

const l_= logr_.l;

This is the manual approach, and it still works — reach for it when you need the intermediate l_merge/l_concat steps (e.g. adding brand-new labels like HANDLERS/DROPS while merging). For the common case of "just share one table across submodules," wire() below does it in one call.

Reassigning Labels from Submodules — wire()

LOGR_.wire(arr_logr) collapses the whole workflow into one call. It reads every submodule's labels, l_unions them into one shared table, points every submodule and a new main logr at that single lref, and returns the main logr. Because logging and toggle resolve labels by name (through the live l proxy), the union can renumber bits freely — no l_merge chains, no l_LL/l_RR alignment, no per-submodule reassignment to keep in sync.

import { LOGR } from '@knev/bitlogr';
import { ..., logr as logr_json_msg_ } from '@rootintf/json-msg'
import { ..., logr as logr_twoPhW_ }   from '@rootintf/protocol-2phw'
import { ..., logr as logr_discovery_ } from './Responder_Discovery.js'

const logr_ = LOGR.get_instance().wire([
    logr_json_msg_,
    logr_twoPhW_,
    logr_discovery_,
]);

const l_ = logr_.l;

The array is the single source of truth for which submodules participate — you cannot union a submodule and forget to reassign it. First-appearance order decides the bit layout (first logr keeps the low bits). A submodule created without labels (no lref) throws with its index. In production (LOGR_ENABLED = false), wire returns the same no-op stub as create().

Optional position lock. Pass an expected table as the second argument to pin the layout; wire runs l_assert on the union and throws on mismatch (otherwise omit it):

const logr_ = LOGR.get_instance().wire(
    [ logr_json_msg_, logr_twoPhW_, logr_discovery_ ],
    { VALIDATION: 0b1 << 0, LOG_EVENTS: 0b1 << 1, /* ... */ } // optional
);

You can always inspect the resulting table with logr_.lref.get().

Pins are per-level. In a nested build each wire renumbers from scratch (l_union, first-appearance order), so a parent wire overwrites whatever positions a child pinned. A mid-level pin still runs and throws at its level, but it asserts a transient layout the parent renumbers away — only the top-level pin describes the final table you actually toggle/log against. Put the pin on the outermost wire.

Nesting composes. A wired main remembers its members (every logr sharing its lref), so passing one unit's wired logr into a parent wire() re-points that unit's hidden leaves too — transitively, at any depth. Each unit just wire()s its own submodules and exports its logr; a parent wire()s the child's logr without ever naming the child's internals. This is what lets a process build one shared label space bottom-up while keeping each unit encapsulated:

// unit A (e.g. a sub-bridge) — wires its own leaves, exports its main logr
export const logr = LOGR.get_instance().wire([ logr_leaf_a_, logr_leaf_b_ ]);

// unit B (the orchestrator) — wires unit A's main + its own leaf; A's leaves follow
export const logr = LOGR.get_instance().wire([ logr_unitA_, logr_net_ ]);

// entry — wires unit B + its own bridge; EVERY leaf below lands on this one table
const logr_ = LOGR.get_instance().wire([ logr_mainbridge_, logr_unitB_ ]);
LOGR.get_instance().toggle(logr_.lref.get(), { DISCOVERY: true });

Caveat — foreign packages built the old way. A third-party package that internally used the old manual l_merge + hand-reassignment (rather than wire) and exports a main logr won't carry a member list, so its sub-leaves aren't exposed — nesting it can orphan them. That's no worse than before, and any package built on wire composes perfectly. Packages that export plain leaf logrs (from create()) are unaffected — they wire as a single member and follow reassignment normally.

By hand. wire is sugar over l_union + lRef + the .lref setter (it also adds the missing-lref guard, the optional pin, and the production short-circuit). The core is:

const arr_logr_ = [ logr_json_msg_, logr_twoPhW_, logr_discovery_ ];

const lref_ = lRef( l_union(...arr_logr_.map(g => g.lref.get())) );
arr_logr_.forEach(g => g.lref = lref_);

const logr_ = LOGR.get_instance().create(); // don't let create() make a new ref
logr_.lref = lref_;
const l_ = logr_.l;

Adding a unit's own tag

When a unit wires submodules but also wants a label of its own (one that no submodule contributes), add it as another leaf in the wire array — a small logr that carries just what this unit introduces:

import { LOGR, l_array } from '@knev/bitlogr';

const LOGR_ = LOGR.get_instance();

// this unit's OWN tag(s) -- a leaf carrying just what it adds
const logr_self_ = LOGR_.create({ labels: l_array(['ORCH']) });

const logr_ = LOGR_.wire([
    logr_self_,   // <- your new tag joins the shared union
    logr_me_,
    logr_ds_,
]);

const l_ = logr_.l;
export { logr_ as logr };

// log through the wired main, resolved by name:
logr_.log(l_.ORCH, () => ['orchestrator: ...']);

You don't log through logr_self_ — it's just the label carrier; you always log through logr_ / l_. Because logr_self_ is a member of logr_, the tag composes upward: a parent wire pulls it in via the member chain, so it lands on the final table and is toggleable from the top.

If you'd rather not introduce a carrier logr for a single tag, append it to the shared ref in place (everyone on that ref sees it, since it's mutated, not replaced):

logr_.lref.set( l_concat(logr_.lref.get(), l_array(['ORCH'])) );

Both propagate to a parent wire. The leaf-in-wire form is the cleaner idiom — every set of labels is a logr; reach for the l_concat one only when adding a tag after the fact.

Custom Handler

LOGR_.handler = (...args) => console.warn('WARN:', ...args);
LOGR_.toggle(l_, {
        EVENTS : true
    })
    
logr_.log(l_.EVENT, () => ['Debug warning']); // "WARN: Debug warning"

Raw logging

Don't use labels, just log.

logr_.raw('const l_ ', l_.get())

Warnings & errors (warn / error) — the always-on severity channel

Labels are the opt-in debug axis: a log(l_.X, …) only fires if X is toggled. A warning is a different axis — it must surface regardless of what's toggled (you don't want a warning silently swallowed because nobody enabled its label). So warn / error are an always-on channel beside the label system, with their own handlers (default console.warn / console.error):

logr_.warn('discovery svc unreachable:', host);  // always prints, ungated by toggle
logr_.error('rpc failed:', err.message);

LOGR_.handler_warn  = (...a) => console.warn('WARN:', ...a);   // overridable, like handler
LOGR_.handler_error = (...a) => console.error('ERR:', ...a);

They are not gated by toggle, and unlike log they are not stripped in production (LOGR_ENABLED = false) — a warning should still fire in a shipped build. Keep them for genuine severity; use labelled log() for debug tracing.

Call-site tracing (LOGR_.trace) — the JS __FUNC__

Instead of hand-typing a 'Orchestrator: ' prefix on every line, turn on tracing and each fired log gets its caller's Class.method appended, in parens, after the message:

LOGR.get_instance().trace = true;   // off by default

// inside a class method:
logr_.log(l_.DISCOVERY, () => ['curated-list query: id=', id]);
// → prints:  curated-list query: id= ... (Orchestrator._on_curated_query)

trace = true appends the site as (site). Pass a function instead to format it yourself (it's still appended after the message — you control the text):

LOGR.get_instance().trace = (site) => `[${site}]`;
// → prints:  curated-list query: id= ... [Orchestrator._on_curated_query]

Unit name — identity, prefix, and scope

Give a unit a name at create (before labels). The name is both its display prefix (printed with an implicit :, so the name itself doesn't carry one) and its filter identity for muting:

const logr_ = LOGR_.create({ name: 'Orchestrator', labels: l_array(['DISCOVERY']) });
logr_.log(l_.DISCOVERY, () => ['curated-list query: id=', id]);
// → prints:  Orchestrator: curated-list query: id= ...

logr_.name;   // 'Orchestrator'  (no colon)

Unlike trace, the name costs nothing (no stack walk), and unlike the old global prefix it's per-unit — each unit names itself. It composes with trace (name prepended, call site appended): Orchestrator: … (Orchestrator._on_curated_query).

Scoping by unit — mute / verbose

Because the name lives on the logr and is checked at log time, you can scope output by name without holding a buried unit's logr — which matters under wire, where a parent doesn't have its sub-units' loggers:

LOGR_.toggle(l_, { REFLECTOR: true });    // REFLECTOR on everywhere
LOGR_.mute('net_DiscoverySvc');           // ...but hush that one unit (by name)
LOGR_.mute('net_DiscoverySvc', false);    // restore it

LOGR_.verbose('net_DiscoverySvc');        // force ONE unit fully verbose: all its logs fire
LOGR_.verbose('net_DiscoverySvc', false); // regardless of the label mask -> back to label-gated

Two per-unit axes, both keyed by the unit's name:

  • mute — silence a unit (regardless of what's toggled).
  • verbose — force a unit to fire all its logs, ignoring the label mask, scoped to that unit only.

Each takes one name, several names, or an array, plus an optional trailing true|false to turn it off/on (default true): mute('a'), mute('a', 'b'), mute(['a', 'b']), mute('a', false).

Hierarchical names + wildcards. Name units hierarchically (app:reflector, app:db) and scope a whole subtree with a trailing *:

LOGR_.mute('app:*');   // mute every unit whose name starts with "app:"
LOGR_.mute('*');       // mute all named units
LOGR_.mute('app:*', false);   // clear it

Exact names keep the O(1) fast path; wildcards are checked per fired log against the named units. (There's no negation — to un-mute one unit under a *, clear the wildcard and re-mute the rest, or mute the specific names instead.)

verbose is per-unit — it does not enable a shared label everywhere (that's just toggle(labels, {…})). warn/error ignore mute; severity always surfaces.

LOGR_.labeled — which label fired

Prepend each fired log with the label name(s) that actually triggered it — the bits present in both the statement and the toggled mask:

LOGR.get_instance().labeled = true;
logr_.log(l_.DISCOVERY | l_.CURATED_LISTS, () => ['query...']);
// with only DISCOVERY toggled on -> prints:  [DISCOVERY] query...
// with both on                    -> prints:  [DISCOVERY|CURATED_LISTS] query...

labeled is independent of trace/name and composes with them — the label goes first, then the unit name, then the message, then any trace tag:

LOGR.get_instance().labeled = true;
const logr_ = LOGR_.create({ name: 'Orchestrator', labels: l_array(['DISCOVERY']) });
// → prints:  [DISCOVERY] Orchestrator: query...

Pass a function to own the tag — abbreviate, re-order, bracket, or return '' to suppress. It receives the fired names as an array (so you can map each), and controls the whole tag (like the trace formatter). Unmapped names fall through to their full name:

const ABBR = { DISCOVERY: 'DISC', CURATED_LISTS: 'CURL', REFLECTION: 'REFL' };
LOGR.get_instance().labeled = (names) => `[${names.map(n => ABBR[n] ?? n).join('|')}]`;
// → prints:  [DISC] query...   /   [DISC|CURL] query...

There is no auto-abbreviation in the library (2-letter codes collide and, once collision-resolved, become unstable as the label set grows) — the formatter hands that policy to you, so uniqueness and stability stay in your control.

How it works and what to expect:

  • It reads V8's structured stack (Error.captureStackTrace + a prepareStackTrace hook), so it gets Class.method directly — no string parsing. A method call yields Orchestrator._on_curated_query; a plain/object-literal function yields its name (do_curated_query); a truly anonymous frame falls back to the file name.
  • Only the name is used — not file:line. After bundling, the line number is bundle-relative junk (main.bundle.dev.js:78755), and source maps aren't applied to the programmatic stack, so it's dropped. Function/class names do survive tsc + unminified (dev) bundling.
  • Cost is paid only on logs that actually fire (the stack read happens after the toggle check), and only in dev — in production LOGR_ENABLED=false strips the whole path. So a checked-but-skipped log pays nothing.
  • V8-only (Node / Electron / Chromium). On other engines trace degrades to no prefix rather than breaking. A minified build would mangle the names (t.e), but tracing is a dev aid and prod is disabled, so names are intact where it runs.

spec/logr.spec.mjs

See spec/logr.spec.mjs in the source for more examples.

Development vs. Production

Development Mode

Set LOGR_ENABLED = true (default or via build config) to enable logging:

LOGR_.toggle(l_, {
        EVENTS : true
    })
    
logr_.log(l_.EVENT, () => ['Dev log']); // Logs "Dev log"
logr_.log(l_.CXNS, () => ['No log']); // Skipped

Production Mode

Set LOGR_ENABLED = false (e.g., via Webpack DefinePlugin) to disable logging with no performance cost:

// In production with LOGR_ENABLED = false
logr_.log(l_.EVENT, () => ['Expensive computation']); // No-op, thunk not evaluated

Use a build tool to replace LOGR_ENABLED at compile time:

Object.defineProperty(globalThis, 'LOGR_ENABLED', {
	value: true,
	writable: true,
	configurable: true
});
// rollup.config.mjs

import terser from '@rollup/plugin-terser';
const isProduction = process.env.NODE_ENV === 'production';

export default [
	// ...
	{
		plugins: [
			// ...
			isProduction && terser({
				compress: {
					dead_code: true,
					global_defs: {
						'LOGR_ENABLED': false   // ← Terser understands this
					}
				}
			})
		]
	}
};

Limitations

  • 32-Bit Limit: Supports up to 31 labels before overflow (JavaScript’s 32-bit integer limit).