Universal pluggable logging utility with configurable levels and namespacing support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Abstract writer system for creating custom log output handlers. Writers subscribe to log events and handle actual output formatting and destination routing, enabling pluggable log output across different environments and platforms.
Base class for creating custom log writers that handle log output.
/**
* Creates a log writer instance
* @param {object} env - Environment variables object
* @param {object} [options] - Optional configuration
* @param {string} [options.defaultNamespace] - Default namespace for logs
* @throws {Error} If called without 'new' keyword
*/
function LogWriter(env, options);Usage Examples:
const LogWriter = require("log/lib/abstract-writer");
// Basic writer setup
const writer = new LogWriter(process.env);
// Writer with options
const writer = new LogWriter(process.env, {
defaultNamespace: "myapp"
});
// Environment variables used:
// - LOG_LEVEL: threshold level (error, warning, notice, info, debug)
// - LOG_DEBUG: namespace-based debug filter
// - DEBUG: fallback debug filter
// - LOG_TIME: timestamp format configurationClass-level properties and methods for writer configuration.
/**
* Maps log levels to display symbols/prefixes
* @type {object}
*/
LogWriter.levelPrefixes;
/**
* Resolves namespace message prefix for display
* @param {Logger} logger - Logger instance
* @returns {string|null} Namespace prefix or null
*/
LogWriter.resolveNamespaceMessagePrefix(logger);Usage Examples:
const LogWriter = require("log/lib/abstract-writer");
// Level symbols/prefixes
console.log(LogWriter.levelPrefixes);
// { debug: "●", info: "ℹ", notice: "ℹ", warning: "⚠", error: "✖" }
// Namespace prefix resolution
const log = require("log");
const appLogger = log.get("myapp:service");
const prefix = LogWriter.resolveNamespaceMessagePrefix(appLogger);
console.log(prefix); // "myapp:service" or shortened versionMethods available on writer instances for handling log events.
/**
* Check if a logger is enabled for output
* @param {Logger} logger - Logger instance to check
* @returns {boolean} True if logger should output
*/
writer.isLoggerEnabled(logger);
/**
* Set up level-specific properties on logger
* @param {Logger} logger - Logger instance to configure
*/
writer.setupLevelLogger(logger);
/**
* Set up level message prefix on logger
* @param {Logger} logger - Logger instance to configure
*/
writer.setupLevelMessagePrefix(logger);
/**
* Abstract method - must be implemented by subclasses
* @param {LogEvent} event - Log event to write
*/
writer.writeMessage(event);
/**
* Resolve message tokens into formatted strings
* @param {LogEvent} event - Log event to process
*/
writer.resolveMessageTokens(event);
/**
* Resolve final message from tokens
* @param {LogEvent} event - Log event to process
*/
writer.resolveMessage(event);Global writer registration system for managing the active log writer.
/**
* Get the currently registered master writer
* @returns {LogWriter|null} Active writer or null
*/
function getMasterWriter();
/**
* Register a master log writer (only one allowed)
* @param {LogWriter} writer - Writer instance to register
* @returns {LogWriter} The registered writer
* @throws {Error} If master writer already registered or invalid writer
*/
getMasterWriter.register(writer);Usage Examples:
const getMasterWriter = require("log/lib/get-master-writer");
// Check current master writer
const currentWriter = getMasterWriter();
console.log(currentWriter); // null if none registered
// Register a new writer
const writer = new MyCustomWriter(process.env);
getMasterWriter.register(writer);
// Attempting to register another throws error
try {
getMasterWriter.register(anotherWriter);
} catch (e) {
console.log(e.message); // "Cannot register: Master log writer already registered"
}Writers receive log events through the global event emitter.
/**
* Log event structure passed to writers
*/
interface LogEvent {
/** Logger instance that generated the event */
logger: Logger;
/** Array of message arguments passed to logger */
messageTokens: any[];
/** Resolved message string (set by writer) */
message?: string;
}
/**
* Init event structure for new logger initialization
*/
interface InitEvent {
/** Newly initialized logger instance */
logger: Logger;
}Global event emitter for log system events.
/**
* Global log event emitter
* Events:
* - 'log': Emitted when logger is called with message
* - 'init': Emitted when new logger is initialized
*/
const emitter = require("log/lib/emitter");Usage Examples:
const emitter = require("log/lib/emitter");
// Listen for log events
emitter.on("log", (event) => {
console.log("Log event:", {
level: event.logger.level,
namespace: event.logger.namespace,
tokens: event.messageTokens
});
});
// Listen for logger initialization
emitter.on("init", (event) => {
console.log("New logger:", {
level: event.logger.level,
namespace: event.logger.namespace
});
});const LogWriter = require("log/lib/abstract-writer");
class ConsoleWriter extends LogWriter {
constructor(env, options) {
super(env, options);
}
writeMessage(event) {
const { logger, message } = event;
// Get level prefix
const levelPrefix = this.constructor.levelPrefixes[logger.level];
// Get namespace prefix
const namespacePrefix = this.constructor.resolveNamespaceMessagePrefix(logger);
// Format output
let output = `${levelPrefix} `;
if (namespacePrefix) {
output += `[${namespacePrefix}] `;
}
output += message;
// Write to console
console.log(output);
}
}
// Initialize writer
new ConsoleWriter(process.env);class AdvancedWriter extends LogWriter {
constructor(env, options) {
super(env, options);
this.logFile = options?.logFile;
}
writeMessage(event) {
const { logger } = event;
// Custom filtering
if (!this.shouldLog(logger)) {
return;
}
// Custom formatting
const formattedMessage = this.formatMessage(event);
// Multiple outputs
console.log(formattedMessage);
if (this.logFile) {
this.writeToFile(formattedMessage);
}
}
shouldLog(logger) {
// Custom logic for log filtering
return logger.isEnabled && logger.levelIndex <= 2;
}
formatMessage(event) {
const timestamp = new Date().toISOString();
const { logger, message } = event;
return `${timestamp} [${logger.level.toUpperCase()}] ${logger.namespace || 'ROOT'}: ${message}`;
}
writeToFile(message) {
// File writing logic
require('fs').appendFileSync(this.logFile, message + '\n');
}
}Writers use environment variables for configuration:
// LOG_LEVEL: Sets visibility threshold
// Values: error, warning, notice, info, debug
// Default: notice
process.env.LOG_LEVEL = "info";
// LOG_DEBUG: Namespace-based debug filtering
// Format: comma-separated namespaces, "-" prefix to disable
// Examples: "myapp", "service:*", "db,-db:verbose"
process.env.LOG_DEBUG = "myapp:service,database";
// DEBUG: Fallback debug filter (debug-lib compatible)
process.env.DEBUG = "myapp:*";
// LOG_TIME: Timestamp configuration
// Implementation-specific format
process.env.LOG_TIME = "iso";Writers automatically apply visibility control based on level thresholds and namespace filters:
const LogWriter = require("log/lib/abstract-writer");
// Environment setup
process.env.LOG_LEVEL = "notice"; // Show notice and above
process.env.LOG_DEBUG = "myapp"; // Also show debug logs for "myapp" namespace
const writer = new MyWriter(process.env);
// These will be visible (notice and above):
log.error("Error message"); // ✓ Visible (error >= notice)
log.warning("Warning"); // ✓ Visible (warning >= notice)
log.notice("Notice"); // ✓ Visible (notice >= notice)
// These will be hidden (below notice threshold):
log.info("Info message"); // ✗ Hidden (info < notice)
log.debug("Debug message"); // ✗ Hidden (debug < notice)
// But debug namespace is enabled:
log.get("myapp").debug("Debug in myapp"); // ✓ Visible (namespace filter)