@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 importedloggerwill 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
Errorobjects. - Asynchronous Fallback: The default
OmniLoggerqueues 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.nextTickorqueueMicrotask). - Child Loggers: Full support for
.child(meta)binding.
Installation
npm install @quicore/omniloggerUsage
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-inOmniLoggeris designed as a lightweight fallback — for production workloads, always inject a stream-based logger like Pino viasetLogger().
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
