Minimal lightweight logging for JavaScript, adding reliable log level methods to any available console.log methods
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extend loglevel with custom logging behavior through the method factory system, enabling advanced features like remote logging, custom formatting, and output redirection.
/**
* Plugin API entry point for custom method factories
* Called for each enabled method when level is set
* @type {MethodFactory}
*/
methodFactory: MethodFactory;
/**
* Method factory function type
* @param {string} methodName - Log method name ('trace', 'debug', 'info', 'warn', 'error')
* @param {number} level - Current logging level (0-5)
* @param {string | symbol} loggerName - Name of the logger (undefined for root logger)
* @returns {Function} Logging method implementation
*/
type MethodFactory = (
methodName: 'trace' | 'debug' | 'info' | 'warn' | 'error',
level: number,
loggerName: string | symbol | undefined
) => (...message: any[]) => void;Usage Examples:
import log from 'loglevel';
// Basic method factory override
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Add timestamp prefix
const timestamp = new Date().toISOString();
rawMethod(`[${timestamp}]`, ...args);
};
};
// Apply the factory
log.setLevel(log.getLevel()); // Rebuilds methods with new factory
log.info('This message has a timestamp'); // [2023-10-01T12:00:00.000Z] This message has a timestamp/**
* Rebuild logging methods using current methodFactory
* Forces regeneration of all logging methods
* Updates child loggers if called on root logger
*/
rebuild(): void;Usage Examples:
import log from 'loglevel';
// Change method factory
log.methodFactory = customFactory;
// Rebuild methods to apply changes
log.rebuild();
// For root logger, also rebuilds all child loggers
const childLogger = log.getLogger('child');
log.rebuild(); // Updates both root and child logger methodsAdd prefixes to all log messages:
import log from 'loglevel';
function createPrefixPlugin(prefix) {
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
const loggerPrefix = loggerName ? `[${String(loggerName)}]` : '';
rawMethod(`${prefix}${loggerPrefix}`, ...args);
};
};
log.rebuild();
}
// Usage
createPrefixPlugin('[MyApp] ');
const dbLogger = log.getLogger('DB');
log.info('Application started'); // [MyApp] Application started
dbLogger.warn('Connection slow'); // [MyApp] [DB] Connection slowSend logs to a remote server:
import log from 'loglevel';
function createRemotePlugin(endpoint) {
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Local logging
rawMethod(...args);
// Remote logging for errors and warnings
if (level >= log.levels.WARN) {
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
level: methodName,
logger: loggerName,
message: args.join(' '),
timestamp: Date.now(),
url: window.location?.href
})
}).catch(err => {
// Fail silently to avoid logging loops
});
}
};
};
log.rebuild();
}
// Usage
createRemotePlugin('/api/logs');
log.error('Critical error'); // Logs locally AND sends to serverAdd conditional logging based on environment or feature flags:
import log from 'loglevel';
function createConditionalPlugin(shouldLog) {
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Check condition before logging
if (shouldLog(methodName, level, loggerName, args)) {
rawMethod(...args);
}
};
};
log.rebuild();
}
// Usage examples
// Only log in development
createConditionalPlugin(() => process.env.NODE_ENV === 'development');
// Only log specific modules
createConditionalPlugin((method, level, logger) => {
return !logger || logger === 'database' || logger === 'api';
});
// Rate limiting
const logCounts = new Map();
createConditionalPlugin((method, level, logger, args) => {
const key = `${logger}-${method}`;
const count = logCounts.get(key) || 0;
logCounts.set(key, count + 1);
return count < 100; // Max 100 logs per method per logger
});Custom message formatting and structured logging:
import log from 'loglevel';
function createFormattingPlugin() {
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Structured logging format
const logEntry = {
timestamp: new Date().toISOString(),
level: methodName.toUpperCase(),
logger: loggerName || 'root',
message: args.length === 1 && typeof args[0] === 'string'
? args[0]
: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ')
};
// Format as JSON for structured logging
rawMethod(JSON.stringify(logEntry));
};
};
log.rebuild();
}
// Usage
createFormattingPlugin();
log.info('User logged in', { userId: 123 });
// {"timestamp":"2023-10-01T12:00:00.000Z","level":"INFO","logger":"root","message":"User logged in {\"userId\":123}"}Always wrap the original method factory to maintain loglevel's reliability:
const originalFactory = log.methodFactory;
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Your custom logic here
try {
// Custom processing
processCustomLogic(methodName, args);
} catch (error) {
// Don't break logging if plugin fails
}
// Always call original method
return rawMethod(...args);
};
};Plugin failures shouldn't break logging:
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
try {
// Plugin logic
customBehavior(...args);
} catch (error) {
// Silently fail or use fallback
console.error('Logging plugin error:', error);
}
return rawMethod(...args);
};
};Ensure plugins work with both root and named loggers:
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Handle both root logger (loggerName === undefined) and named loggers
const prefix = loggerName ? `[${String(loggerName)}]` : '[ROOT]';
return rawMethod(prefix, ...args);
};
};
// Rebuild all loggers
log.rebuild();Be aware of memory usage in plugins:
// Avoid memory leaks in stateful plugins
const logCache = new Map();
log.methodFactory = function(methodName, level, loggerName) {
const rawMethod = originalFactory(methodName, level, loggerName);
return function(...args) {
// Limit cache size
if (logCache.size > 1000) {
logCache.clear();
}
// Cache logic here
return rawMethod(...args);
};
};