A lazy logging library with bitwise level control
npm install log-lazy


A high-performance lazy logging library with bitwise level control, designed to keep logging statements in production code with zero performance impact when disabled.
- Lazy Evaluation: Log arguments wrapped in functions are only evaluated if logging is enabled
- Zero Performance Impact: Disabled logs have virtually no runtime cost
- Bitwise Level Control: Combine multiple log levels with bitwise operations
- Production-Ready: Keep all logging statements in production code safely
- Bun.sh Optimized: Built and tested specifically for Bun runtime
``bash`
bun i log-lazy
npm i log-lazy
yarn add log-lazy
pnpm add log-lazy
deno add npm:log-lazy
`javascript
import makeLog from 'log-lazy';
const log = makeLog({ level: 'info' }); // โจ Direct, simple, efficient!
// Preferred: Use log() directly (defaults to info level)
log('Server started');
log(() => User ${user.id} logged in); // With lazy evaluation
// Also available: Explicit level methods
log.debug('This will not be logged'); // Below info level
log.error('An error occurred'); // Error level
// ๐ Make it efficient - just add () => to your template literals!
log.debug(() => Debug data: ${JSON.stringify(largeObject)}); // Not evaluated!Processing ${items.length} items
log(() => ); // Evaluated and logged at info level`
That's it! Simple, direct, and efficient!
Traditional logging forces developers to either:
1. Remove log statements from production code (losing valuable debugging capability)
2. Leave them in and suffer performance penalties from string concatenation and JSON serialization
log-lazy solves this with lazy evaluation - expensive operations are wrapped in functions that only execute when logging is actually enabled.
javascript
// This ALWAYS runs JSON.stringify, even when logging is disabled!
log.debug(User data: ${JSON.stringify(user)}, Posts: ${JSON.stringify(posts)});// This ALWAYS performs the calculation, even when logging is disabled!
log.debug(
Found ${users.filter(u => u.active).length} active users);// This ALWAYS builds the entire string, even when not logging!
log.info(
Processing order #${order.id} with ${order.items.length} items totaling $${order.calculateTotal()});
`$3
`javascript
import makeLog from 'log-lazy';
const log = makeLog({ level: 'info' }); // Direct and simple!// Preferred: Use log() for info-level logging
log(() =>
Processing order #${order.id} with ${order.items.length} items totaling $${order.calculateTotal()});// For debug: JSON.stringify only runs if debug is enabled
log.debug(() =>
User data: ${JSON.stringify(user)}, Posts: ${JSON.stringify(posts)});// Calculation only happens if debug logging is enabled!
log.debug(() =>
Found ${users.filter(u => u.active).length} active users);
`The beauty is in the simplicity - just wrap your existing template literals with
() => and you get lazy evaluation!๐ฅ Performance Benefits
With lazy evaluation, you can keep detailed logging in production:
`javascript
import makeLog from 'log-lazy';const log = makeLog({ level: 'error' }); // Only errors in production
function processOrder(order) {
// These debug logs have ZERO performance impact in production!
log.debug(() =>
Processing order ${JSON.stringify(order)});
log.debug(() => Validation details: ${performExpensiveValidation(order)});
try {
const result = submitOrder(order);
// Preferred: Use log() for info-level messages
log(() => Order #${result.id} submitted with ${result.items.length} items, total: $${result.calculateTotal()});
return result;
} catch (error) {
// Error logging is enabled, so this will execute
log.error(() => Order ${order.id} failed: ${error.message}. Snapshot: ${JSON.stringify(order)});
throw error;
}
}
`๐๏ธ Bitwise Level Control
Log levels are bit flags that can be combined for fine-grained control:
`javascript
import makeLog, { levels } from 'log-lazy';// Standard levels (powers of 2)
const logLevels = {
none: 0, // 0b00000000 - No logging
fatal: 1, // 0b00000001
error: 2, // 0b00000010
warn: 4, // 0b00000100
info: 8, // 0b00001000
debug: 16, // 0b00010000
verbose: 32, // 0b00100000
trace: 64, // 0b01000000
silly: 128, // 0b10000000
all: 255 // 0b11111111 - All levels
};
// Combine levels with bitwise OR
const customLevel = levels.error | levels.warn | levels.fatal;
const log = makeLog({ level: customLevel });
// Or use preset combinations
const prodLog = makeLog({ level: 'production' }); // fatal, error, warn
const devLog = makeLog({ level: 'development' }); // fatal, error, warn, info, debug
`๐ API Usage
$3
`javascript
import makeLog from 'log-lazy';// Create log directly - clean and simple!
const log = makeLog({ level: 'info' }); // โจ That's it!
// Preferred: Use log() directly (defaults to info level)
log(() =>
Server started on port ${port});
log(() => Processing request: ${JSON.stringify(request)});// For other levels: Use explicit methods when needed
log.error(() =>
Failed to connect: ${error.message});
log.debug(() => State: ${JSON.stringify(state)}); // Zero cost when disabled!// Clean, direct API with zero overhead! ๐ฏ
`$3
`javascript
const log = makeLog({ level: process.env.LOG_LEVEL || 'info' });// Preferred: Use log() with lazy evaluation
log(() =>
Processing order with ${items.length} items);
log(() => Stats: ${calculateActiveUsers()} active users, revenue: $${calculateRevenue()});// For specific levels: Use explicit methods
log.debug(() =>
State snapshot: ${JSON.stringify(largeStateObject)});
log.error(() => Operation failed: ${error.message});// Multi-line template literals work great too
log.trace(() => {
const result = performExpensiveAnalysis();
return
Analysis complete:;
});// Note: log() is equivalent to log.info()
// Both work, but log() is shorter and cleaner!
`$3
`javascript
import makeLog, { levels } from 'log-lazy';
const log = makeLog({ level: levels.warn });// Enable specific levels at runtime
log.enableLevel('debug');
log.enableLevel('info');
// Disable specific levels
log.disableLevel('debug');
// Check what's enabled
console.log(log.getEnabledLevels()); // ['warn', 'info']
// Check if a specific level would log
if (log.shouldLog('debug')) {
// Perform debug-only operations
}
`$3
`javascript
const log = makeLog({
level: 'custom',
presets: {
custom: levels.error | levels.debug,
minimal: levels.fatal | levels.error,
verbose: levels.all & ~levels.silly
}
});
`$3
`javascript
// Different loggers for different modules
const dbLog = makeLog({ level: 'error' });
const apiLog = makeLog({ level: 'info' });
const authLog = makeLog({ level: 'debug' });// In production, update all to error-only
if (process.env.NODE_ENV === 'production') {
[dbLog, apiLog, authLog].forEach(log => {
log.level = levels.error;
});
}
`๐ Best Practices
$3
`javascript
const log = makeLog({ level: 'info' });// โ Bad - Always evaluates
log(
Found ${items.length} items worth $${calculateTotal(items)});
log.debug(Data: ${JSON.stringify(data)});// โ
Good - Just add () => for lazy evaluation!
log(() =>
Found ${items.length} items worth $${calculateTotal(items)}); // Preferred: log() for info
log.debug(() => Data: ${JSON.stringify(data)}); // Use explicit level when needed// Remember: log() === log.info() - use the shorter one!
`$3
`javascript
const log = makeLog({ level: process.env.LOG_LEVEL || 'error' });// You can now safely leave these in production code!
function processPayment(payment) {
// These won't execute in production (when level is 'error')
log.debug(() =>
Processing payment: ${JSON.stringify(payment)});
log.trace(() => Validation rules: ${JSON.stringify(gatherValidationRules())});
// Business logic...
// Preferred: Use log() for info-level messages
log(() => Payment ${payment.id} processed: $${payment.amount}, fees: $${calculateFees(payment)});
if (payment.amount > 10000) {
log.warn(() => Large payment detected: ${payment.id} for $${payment.amount});
}
}
`$3
`javascript
// Now use log.debug, log.info, etc.log.fatal('System is shutting down'); // System unusable
log.error('Failed to save user', error); // Error conditions
log.warn('API rate limit approaching'); // Warning conditions
log.info('User logged in', userId); // Informational
log.debug('Cache miss for key:', key); // Debug-level
log.verbose('Entering function', funcName); // Verbose debug
log.trace('Variable state:', () => state); // Detailed trace
log.silly('Every little detail'); // Extremely detailed
`$3
`javascript
const getLogLevel = () => {
switch(process.env.NODE_ENV) {
case 'production': return 'error';
case 'staging': return 'warn';
case 'development': return 'debug';
case 'test': return 'none';
default: return 'info';
}
};const log = makeLog({ level: getLogLevel() });
`๐ฏ Real-World Example
`javascript
import makeLog from 'log-lazy';class OrderService {
constructor() {
this.log = makeLog({
level: process.env.LOG_LEVEL || 'info'
}); // Direct and clean!
}
async createOrder(orderData) {
// These debug logs have ZERO cost in production - just wrapped with () =>
this.log.debug(() =>
Creating order with ${orderData.items.length} items, total: $${orderData.calculateTotal()}, customer: ${orderData.customerId}); try {
// Validate
this.log.trace(() =>
Validating with rules: ${JSON.stringify(this.getValidationRules())});
const validation = await this.validate(orderData);
// Process payment
this.log.debug(() => Processing ${orderData.paymentMethod} payment for $${orderData.total});
const payment = await this.processPayment(orderData);
// Create order
const order = await this.saveOrder(orderData, payment);
// Preferred: Use log() for info-level messages
this.log(() => Order ${order.id} created successfully);
// This expensive operation only runs if verbose is enabled
this.log.verbose(() => Order details: ${JSON.stringify(order.toDetailedJSON())});
return order;
} catch (error) {
// Error logging with context - still lazy!
this.log.error(() => Order creation failed: ${error.message});
throw error;
}
}
}
`๐ง Advanced Usage
$3
`javascript
const log = makeLog({
level: 'all',
log: {
fatal: (msg) => alerting.critical(msg),
error: (msg) => sentry.captureMessage(msg),
warn: (msg) => monitoring.warning(msg),
info: (msg) => console.log([INFO] ${msg}),
debug: (msg) => debug(msg),
verbose: (msg) => verbose(msg),
trace: (msg) => trace(msg),
silly: (msg) => silly(msg)
}
});
`๐ Integration with Popular Logging Libraries
log-lazy can seamlessly integrate with existing logging libraries, adding lazy evaluation to improve their performance.
$3
The
debug library is popular for its simplicity and namespace support:`javascript
import makeLog from 'log-lazy';
import createDebug from 'debug';// Create debug instances for different namespaces
const debugApp = createDebug('app');
const debugDB = createDebug('app:db');
const debugHTTP = createDebug('app:http');
// Integrate with log-lazy
const log = makeLog({
level: process.env.DEBUG ? 'all' : 'warn',
log: {
fatal: (...args) => debugApp('FATAL:', ...args),
error: (...args) => debugApp('ERROR:', ...args),
warn: (...args) => debugApp('WARN:', ...args),
info: (...args) => debugApp('INFO:', ...args),
debug: (...args) => debugDB(...args),
verbose: (...args) => debugHTTP(...args),
trace: (...args) => debugApp('TRACE:', ...args),
silly: (...args) => debugApp('SILLY:', ...args)
}
});
// Now use log.debug, log.info, etc.
// Use with lazy evaluation
log.debug('DB Query:', () => JSON.stringify(query));
log.verbose('HTTP Request:', () => ({
method: req.method,
url: req.url,
headers: req.headers
}));
`$3
Winston is a multi-transport async logging library:
`javascript
import makeLog from 'log-lazy';
import winston from 'winston';// Configure Winston
const winstonLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// Integrate with log-lazy
const log = makeLog({
level: 'all',
log: {
fatal: (...args) => winstonLogger.error('FATAL', ...args),
error: (...args) => winstonLogger.error(...args),
warn: (...args) => winstonLogger.warn(...args),
info: (...args) => winstonLogger.info(...args),
debug: (...args) => winstonLogger.debug(...args),
verbose: (...args) => winstonLogger.verbose(...args),
trace: (...args) => winstonLogger.silly(...args), // Winston uses 'silly' for trace
silly: (...args) => winstonLogger.silly(...args)
}
});
// Now use log.debug, log.info, etc.
// Lazy evaluation with Winston's metadata support
log.info('User action', () => ({
userId: user.id,
action: 'login',
metadata: computeExpensiveMetadata()
}));
`$3
Log4js provides a familiar logging interface similar to Log4j:
`javascript
import makeLog from 'log-lazy';
import log4js from 'log4js';// Configure Log4js
log4js.configure({
appenders: {
console: { type: 'console' },
file: { type: 'file', filename: 'app.log' },
errors: { type: 'file', filename: 'errors.log' }
},
categories: {
default: { appenders: ['console', 'file'], level: 'info' },
errors: { appenders: ['errors', 'console'], level: 'error' }
}
});
const log4jsLogger = log4js.getLogger();
const errorLogger = log4js.getLogger('errors');
// Integrate with log-lazy
const log = makeLog({
level: 'all',
log: {
fatal: (...args) => log4jsLogger.fatal(...args),
error: (...args) => errorLogger.error(...args),
warn: (...args) => log4jsLogger.warn(...args),
info: (...args) => log4jsLogger.info(...args),
debug: (...args) => log4jsLogger.debug(...args),
verbose: (...args) => log4jsLogger.trace(...args), // Log4js uses trace for verbose
trace: (...args) => log4jsLogger.trace(...args),
silly: (...args) => log4jsLogger.trace('SILLY:', ...args)
}
});
// Now use log.debug, log.info, etc.
// Use with lazy evaluation
log.debug('Processing batch', () => ({
size: batch.length,
items: batch.map(item => item.id)
}));
`$3
Pino is an extremely fast Node.js logger with low overhead:
`javascript
import makeLog from 'log-lazy';
import pino from 'pino';// Configure Pino
const pinoLogger = pino({
level: process.env.PINO_LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true
}
}
});
// Integrate with log-lazy - Pino expects objects as first argument
const log = makeLog({
level: 'all',
log: {
fatal: (...args) => {
const [msg, ...rest] = args;
pinoLogger.fatal(rest[0] || {}, msg);
},
error: (...args) => {
const [msg, ...rest] = args;
pinoLogger.error(rest[0] || {}, msg);
},
warn: (...args) => {
const [msg, ...rest] = args;
pinoLogger.warn(rest[0] || {}, msg);
},
info: (...args) => {
const [msg, ...rest] = args;
pinoLogger.info(rest[0] || {}, msg);
},
debug: (...args) => {
const [msg, ...rest] = args;
pinoLogger.debug(rest[0] || {}, msg);
},
verbose: (...args) => {
const [msg, ...rest] = args;
pinoLogger.trace(rest[0] || {}, msg);
},
trace: (...args) => {
const [msg, ...rest] = args;
pinoLogger.trace(rest[0] || {}, msg);
},
silly: (...args) => {
const [msg, ...rest] = args;
pinoLogger.trace(rest[0] || {}, msg);
}
}
});
// Now use log.debug, log.info, etc.
// Pino-friendly lazy evaluation
log.info('Request completed', () => ({
responseTime: Date.now() - startTime,
statusCode: res.statusCode,
path: req.url
}));
`$3
Bunyan provides structured JSON logging:
`javascript
import makeLog from 'log-lazy';
import bunyan from 'bunyan';// Configure Bunyan
const bunyanLogger = bunyan.createLogger({
name: 'myapp',
streams: [
{
level: 'info',
stream: process.stdout
},
{
level: 'error',
path: '/var/log/myapp-error.log'
}
],
serializers: bunyan.stdSerializers
});
// Create child logger for specific component
const componentLogger = bunyanLogger.child({ component: 'api' });
// Integrate with log-lazy
const log = makeLog({
level: 'all',
log: {
fatal: (...args) => {
const [msg, ...rest] = args;
componentLogger.fatal(rest[0] || {}, msg);
},
error: (...args) => {
const [msg, ...rest] = args;
componentLogger.error(rest[0] || {}, msg);
},
warn: (...args) => {
const [msg, ...rest] = args;
componentLogger.warn(rest[0] || {}, msg);
},
info: (...args) => {
const [msg, ...rest] = args;
componentLogger.info(rest[0] || {}, msg);
},
debug: (...args) => {
const [msg, ...rest] = args;
componentLogger.debug(rest[0] || {}, msg);
},
verbose: (...args) => {
const [msg, ...rest] = args;
componentLogger.trace(rest[0] || {}, msg);
},
trace: (...args) => {
const [msg, ...rest] = args;
componentLogger.trace(rest[0] || {}, msg);
},
silly: (...args) => {
const [msg, ...rest] = args;
componentLogger.trace({ level: 'silly', ...rest[0] }, msg);
}
}
});
// Now use log.debug, log.info, etc.
// Bunyan-style structured logging with lazy evaluation
log.error('Database error', () => ({
err: error, // Bunyan will serialize this
query: query,
duration: Date.now() - queryStart,
user: req.user.id
}));
`$3
By integrating log-lazy with existing loggers, you get:
1. Zero-cost disabled logs - Expensive computations only run when needed
2. Keep existing infrastructure - Continue using your current logging setup
3. Gradual migration - Add lazy evaluation incrementally
4. Production safety - Leave detailed logs in production code without performance impact
$3
`javascript
// Now use log.debug, log.info, etc. for cleaner code// Only compute expensive debug info when actually debugging
log.debug(() => {
if (complexCondition()) {
return calculateExpensiveDebugInfo();
}
return 'Condition not met';
});
`๐ฆ Performance Comparison
`javascript
// Traditional logging - string always built
console.time('traditional');
for(let i = 0; i < 1000000; i++) {
// This ALWAYS builds the string, even when not logging!
const message = Iteration ${i}: ${JSON.stringify({data: i, timestamp: Date.now()})};
if(logLevel >= DEBUG) {
console.log(message);
}
}
console.timeEnd('traditional'); // ~500ms even when not logging!// Lazy logging with log-lazy
console.time('lazy');
const log = makeLog({ level: 'error' }); // Debug disabled
for(let i = 0; i < 1000000; i++) {
// Just add () => and the function never executes since debug is disabled!
log.debug(() =>
Iteration ${i}: ${JSON.stringify({data: i, timestamp: Date.now()})});
}
console.timeEnd('lazy'); // ~5ms - near zero cost!// The difference: 100-1000x faster when logs are disabled (benchmarked!)
`๐ Benchmarks
Real benchmark results from benchmarks/README.md:
$3
๐ How to read this chart: This shows a realistic production scenario with
warn log level. Debug and info logs are disabled, but traditional logging still evaluates their expensive operations. The bars show execution time (lower is better).`mermaid
---
config:
themeVariables:
xyChart:
backgroundColor: "transparent"
---
xychart-beta
title "Production Performance: Mixed Workload at Warn Level (lower is better)"
x-axis ["No Logging", "Lazy Logging", "Traditional Logging"]
y-axis "Time (milliseconds)" 0 --> 20
bar [0.017, 0.08, 19.04]
`If you see mermaid markup here, you better go to this README page on GitHub to see actual rendering.
Key Results:
- No Logging: 0.017 ms (16.68 ยตs) - Clean code with zero logging
- Lazy Logging: 0.08 ms (79.93 ยตs) - All debug/info evaluations skipped
- Traditional: 19.04 ms - Still evaluates all expressions even when disabled
- Performance Gain: 238x faster than traditional in production conditions
This mixed workload includes:
- Disabled debug logs with
JSON.stringify(largeObject)
- Disabled info logs with array filtering and calculations
- Enabled warn and error logs (both approaches log these)$3
For detailed comparisons including overhead vs clean code, see benchmarks/README.md.
Summary: Lazy logging is 100-1000x faster than traditional logging when disabled, with minimal overhead (1.5-5x) compared to having no logs at all for typical operations.
Run benchmarks yourself:
bun run bench๐งช Testing
`bash
Run tests
bun testWith coverage
bun test --coverageRun benchmarks
bun run bench
``This project is released into the public domain under the Unlicense.
Contributions are welcome! The lazy evaluation pattern ensures that detailed debugging can coexist with production performance requirements.
> "The best debugging logs are the ones that exist in production but never slow it down."
With log-lazy, you never have to choose between observability and performance. Keep your logs, keep your speed.