UNPKG

lsh-framework

Version:

A powerful, extensible shell with advanced job management, database persistence, and modern CLI features

275 lines (274 loc) 7.71 kB
/** * Logging Framework * Centralized logging utility with support for different log levels, * structured logging, and environment-based configuration. */ export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["WARN"] = 2] = "WARN"; LogLevel[LogLevel["ERROR"] = 3] = "ERROR"; LogLevel[LogLevel["NONE"] = 4] = "NONE"; })(LogLevel || (LogLevel = {})); /** * ANSI color codes for terminal output */ const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', // Foreground colors black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', // Background colors bgBlack: '\x1b[40m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m', bgBlue: '\x1b[44m', bgMagenta: '\x1b[45m', bgCyan: '\x1b[46m', bgWhite: '\x1b[47m', }; /** * Get log level from environment variable */ function getLogLevelFromEnv() { const level = process.env.LSH_LOG_LEVEL?.toUpperCase(); switch (level) { case 'DEBUG': return LogLevel.DEBUG; case 'INFO': return LogLevel.INFO; case 'WARN': return LogLevel.WARN; case 'ERROR': return LogLevel.ERROR; case 'NONE': return LogLevel.NONE; default: return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG; } } /** * Logger class for structured logging */ export class Logger { config; constructor(config) { this.config = { level: config?.level ?? getLogLevelFromEnv(), enableTimestamp: config?.enableTimestamp ?? true, enableColors: config?.enableColors ?? (!process.env.NO_COLOR && process.stdout.isTTY), enableJSON: config?.enableJSON ?? (process.env.LSH_LOG_FORMAT === 'json'), context: config?.context, }; } /** * Create a child logger with a specific context */ child(context) { return new Logger({ ...this.config, context: this.config.context ? `${this.config.context}:${context}` : context, }); } /** * Set log level dynamically */ setLevel(level) { this.config.level = level; } /** * Check if a log level is enabled */ isLevelEnabled(level) { return level >= this.config.level; } /** * Format timestamp */ formatTimestamp() { const now = new Date(); return now.toISOString(); } /** * Get level name string */ getLevelName(level) { switch (level) { case LogLevel.DEBUG: return 'DEBUG'; case LogLevel.INFO: return 'INFO'; case LogLevel.WARN: return 'WARN'; case LogLevel.ERROR: return 'ERROR'; default: return 'UNKNOWN'; } } /** * Get color for log level */ getLevelColor(level) { switch (level) { case LogLevel.DEBUG: return colors.cyan; case LogLevel.INFO: return colors.green; case LogLevel.WARN: return colors.yellow; case LogLevel.ERROR: return colors.red; default: return colors.white; } } /** * Format log entry as JSON */ formatJSON(entry) { const obj = { timestamp: entry.timestamp.toISOString(), level: this.getLevelName(entry.level), message: entry.message, }; if (entry.context) { obj.context = entry.context; } if (entry.metadata && Object.keys(entry.metadata).length > 0) { obj.metadata = entry.metadata; } if (entry.error) { obj.error = { name: entry.error.name, message: entry.error.message, stack: entry.error.stack, }; } return JSON.stringify(obj); } /** * Format log entry as text */ formatText(entry) { const parts = []; // Timestamp if (this.config.enableTimestamp) { const timestamp = this.formatTimestamp(); parts.push(this.config.enableColors ? `${colors.dim}${timestamp}${colors.reset}` : timestamp); } // Level const levelName = this.getLevelName(entry.level); const levelColor = this.getLevelColor(entry.level); const formattedLevel = this.config.enableColors ? `${levelColor}${levelName.padEnd(5)}${colors.reset}` : levelName.padEnd(5); parts.push(formattedLevel); // Context if (entry.context) { const formattedContext = this.config.enableColors ? `${colors.magenta}[${entry.context}]${colors.reset}` : `[${entry.context}]`; parts.push(formattedContext); } // Message parts.push(entry.message); // Metadata if (entry.metadata && Object.keys(entry.metadata).length > 0) { const metadataStr = JSON.stringify(entry.metadata); const formattedMetadata = this.config.enableColors ? `${colors.dim}${metadataStr}${colors.reset}` : metadataStr; parts.push(formattedMetadata); } return parts.join(' '); } /** * Core logging method */ log(level, message, metadata, error) { if (!this.isLevelEnabled(level)) { return; } const entry = { timestamp: new Date(), level, message, context: this.config.context, metadata, error, }; const output = this.config.enableJSON ? this.formatJSON(entry) : this.formatText(entry); // Output to appropriate stream if (level >= LogLevel.ERROR) { console.error(output); if (error?.stack) { console.error(error.stack); } } else if (level >= LogLevel.WARN) { console.warn(output); } else { // INFO and DEBUG go to stdout // Using console.log here is intentional for the logger itself // eslint-disable-next-line no-console console.log(output); } } /** * Debug level logging */ debug(message, metadata) { this.log(LogLevel.DEBUG, message, metadata); } /** * Info level logging */ info(message, metadata) { this.log(LogLevel.INFO, message, metadata); } /** * Warning level logging */ warn(message, metadata) { this.log(LogLevel.WARN, message, metadata); } /** * Error level logging */ error(message, error, metadata) { const err = error instanceof Error ? error : undefined; const errorMetadata = error && !(error instanceof Error) ? { error } : undefined; this.log(LogLevel.ERROR, message, { ...metadata, ...errorMetadata }, err); } } /** * Default logger instance */ export const logger = new Logger(); /** * Create a logger with a specific context */ export function createLogger(context, config) { return new Logger({ ...config, context, }); } /** * Export for default usage */ export default logger;