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

@quicore/omnilogger

v0.1.0

Published

Zero-dependency, framework-agnostic logger facade with async buffering, hot-swap support, and safe Error serialization.

Readme

@quicore/omnilogger

A zero-dependency, framework-agnostic logger facade built for high-throughput, petabyte-scale library ecosystems.

@quicore/omnilogger allows library authors to emit logs without forcing a heavy dependency (like Pino or Winston) on the end-user. It provides a highly performant, non-blocking asynchronous fallback logger out of the box, which can be instantly hot-swapped for a heavy-duty production logger at application startup.

Features

  • Zero Dependencies: Keeps your library footprint tiny.
  • Proxy Hot-Swapping: Call setLogger(pino) at any time. Modules that already imported logger will pick up the new instance immediately—no race conditions.
  • Universal API Normalization: Seamlessly handles both Winston-style (message, meta) and Pino-style (meta, message) signatures.
  • Safe Serialization: Prevents circular structure crashes and destructive spreading of class instances or Error objects.
  • Asynchronous Fallback: The default OmniLogger queues entries and flushes them non-blockingly, with built-in backpressure (drops logs gracefully if the queue hits 10,000 to prevent OOM crashes).
  • Environment Agnostic: Works across Node.js, browsers, and Edge runtimes (uses process.nextTick or queueMicrotask).
  • Child Loggers: Full support for .child(meta) binding.

Installation

npm install @quicore/omnilogger

Usage

1. In your library code

Simply import the logger and use it. You don't need to worry about what logger the consuming application uses.

import { logger } from '@quicore/omnilogger';

// Create a component-bound logger
const log = logger.child({ component: 'StorageEngine' });

// Both conventions are supported and normalized automatically!
log.info('Connecting to database...', { url: 'localhost' }); // Simple / Winston style
log.info({ url: 'localhost' }, 'Connecting to database...'); // Structured / Pino style

try {
    // ...
} catch (err) {
    // Safely serializes Error objects, extracting message, stack, and code
    log.error(err, 'Failed to connect to database'); 
}

2. In the consuming application (Injecting a custom logger)

At the entry point of your application, inject your preferred production logger (e.g., Pino, Winston, or Bunyan). All internal libraries using @quicore/omnilogger will instantly pipe their logs through the injected instance.

import pino from 'pino';
import { setLogger } from '@quicore/omnilogger';
import { startStorage } from 'my-storage-lib'; // Uses @quicore/@quicore/omniloggerger internally

const appLogger = pino({ level: 'info' });

// Hot-swap the default OmniLogger for Pino
setLogger(appLogger);

// Now, all logs inside 'my-storage-lib' will be routed through Pino!
startStorage();

API Reference

logger (Proxy instance)

The exported logger object is a proxy. It implements the standard logging methods:

  • logger.trace(meta, msg)
  • logger.debug(meta, msg)
  • logger.info(meta, msg)
  • logger.warn(meta, msg)
  • logger.error(meta, msg)
  • logger.fatal(meta, msg)
  • logger.child(meta): Returns a new logger instance with bound metadata.

setLogger(instance)

Replaces the internal logger. The provided instance must be an object implementing at least info, warn, error, and debug.

getLogger()

Returns the current active logger instance (either the default OmniLogger or whatever was injected via setLogger).

OmniLogger

The default logger class provided out-of-the-box. You can instantiate it manually if you need a lightweight JSON logger.

import { OmniLogger } from '@quicore/omnilogger';

const myLogger = new OmniLogger({
    level: 'debug',       // Minimum log level
    json: true,           // Emit ndjson instead of human-readable text
    maxQueue: 5000,       // Max queued entries before dropping
    baseMeta: { app: 1 }  // Fields merged into every log entry
});

Production Notes

Console output can block. In Node.js, console.log (stdout/stderr) may block when piped to a file or when the OS pipe buffer is full. The built-in OmniLogger is designed as a lightweight fallback — for production workloads, always inject a stream-based logger like Pino via setLogger().

Queue & Flush Behavior

  • Entries are flushed in chunks of 500, yielding back to the event loop between chunks to avoid blocking I/O.
  • If the queue exceeds 10,000 entries (configurable via maxQueue), new entries are dropped and a warning is emitted on the next flush.
  • Level filtering happens at enqueue time — suppressed levels never hit the queue.

License

MIT