UNPKG

qerrors

Version:

Intelligent error handling middleware with AI-powered analysis, environment validation, caching, and production-ready logging. Provides OpenAI-based error suggestions, queue management, retry mechanisms, and comprehensive configuration options for Node.js

350 lines (312 loc) 18.4 kB
/** * Enhanced Winston logger configuration for qerrors module * * This module provides a comprehensive logging infrastructure that supports structured * logging, multiple log levels, performance monitoring, security-aware sanitization, * and production-ready log management. It extends Winston with enhanced features * designed for error handling applications that require detailed audit trails. * * Enhanced features: * - Security-aware message sanitization to prevent logging sensitive data * - Request correlation IDs for tracking user journeys across services * - Performance monitoring with memory usage tracking * - Structured JSON logging with consistent metadata * - Environment-specific configuration for development vs production * - Multi-transport approach ensures logs are captured in multiple formats/locations * - Custom printf format balances readability with structured data * - Stack trace inclusion aids debugging complex error scenarios */ const { createLogger, format, transports } = require('winston'); //import winston logging primitives const path = require('path'); //path for building log file paths const fs = require('fs'); //filesystem for logDir creation const config = require('./config'); //load configuration for env defaults const { sanitizeMessage, sanitizeContext } = require('./sanitization'); //import sanitization utilities // DailyRotateFile loaded dynamically in buildLogger to ensure stub system works in tests /** * Log Levels Configuration with Enhanced Metadata * * Purpose: Defines hierarchical log levels for filtering and routing messages * Each level has a numeric priority for comparison and filtering operations. * Higher numbers indicate higher priority/severity levels. * * Level usage guidelines: * - DEBUG: Detailed debugging information for development * - INFO: General operational messages about system behavior * - WARN: Warning conditions that should be noted but don't stop operation * - ERROR: Error conditions that require attention * - FATAL: Critical errors that may cause system shutdown * - AUDIT: Security and compliance-related events requiring permanent retention */ const LOG_LEVELS = { DEBUG: { priority: 10, color: '\x1b[36m', name: 'DEBUG' }, // Cyan INFO: { priority: 20, color: '\x1b[32m', name: 'INFO' }, // Green WARN: { priority: 30, color: '\x1b[33m', name: 'WARN' }, // Yellow ERROR: { priority: 40, color: '\x1b[31m', name: 'ERROR' }, // Red FATAL: { priority: 50, color: '\x1b[35m', name: 'FATAL' }, // Magenta AUDIT: { priority: 60, color: '\x1b[34m', name: 'AUDIT' } // Blue }; const rotationOpts = { maxsize: Number(process.env.QERRORS_LOG_MAXSIZE) || 1024 * 1024, maxFiles: Number(process.env.QERRORS_LOG_MAXFILES) || 5, tailable: true }; //(use env config when rotating logs) // maxDays is calculated dynamically in buildLogger to respect environment changes const logDir = process.env.QERRORS_LOG_DIR || 'logs'; //directory to store log files let disableFileLogs = !!process.env.QERRORS_DISABLE_FILE_LOGS; //track file log state //(respect env flag) /** * Enhanced Log Entry Creation with Performance Monitoring * * Purpose: Creates consistent, enriched log entries with comprehensive metadata * Includes request correlation, performance metrics, and environment context. */ function createEnhancedLogEntry(level, message, context = {}, requestId = null) { //create structured log entry with enhanced metadata const levelConfig = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO; const entry = { timestamp: new Date().toISOString(), level: levelConfig.name, message: sanitizeMessage(message, levelConfig.name), service: config.getEnv('QERRORS_SERVICE_NAME'), //use existing qerrors service name configuration version: process.env.npm_package_version || '1.0.0', //application version for debugging environment: process.env.NODE_ENV || 'development', pid: process.pid, //process ID for multi-instance debugging hostname: require('os').hostname() //hostname for distributed system tracking }; // Add request correlation ID if available if (requestId) { entry.requestId = requestId; } // Add sanitized context data if provided if (context && Object.keys(context).length > 0) { entry.context = sanitizeContext(context, levelConfig.name); } // Add memory usage for performance monitoring on higher severity levels if (levelConfig.priority >= LOG_LEVELS.WARN.priority) { const memUsage = process.memoryUsage(); entry.memory = { heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), //heap memory in MB heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), //total heap in MB external: Math.round(memUsage.external / 1024 / 1024), //external memory in MB rss: Math.round(memUsage.rss / 1024 / 1024) //resident set size in MB }; } return entry; } const fileFormat = format.combine( //formatter for JSON file logs with timestamp and stack format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), //timestamp for chronological sorting format.errors({ stack: true }), //include stack traces in log object format.splat(), //enable printf style interpolation format.json() //output structured JSON for log processors ); //makes structured logs easy to parse const consoleFormat = format.combine( //formatter for readable console output format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), //timestamp for readability format.errors({ stack: true }), //include stack format.splat(), //support sprintf like syntax format.printf(({ timestamp, level, message, stack }) => `${timestamp} ${level}: ${message}${stack ? '\n' + stack : ''}`) //custom printable string ); //keeps console output compact and readable /** * Asynchronously initializes the logging directory structure * * This function handles the complexity of directory creation in both development * and production environments. It uses async operations to avoid blocking the * main thread during filesystem operations. * * Design rationale: * - Async/await prevents blocking during directory creation * - Recursive creation handles nested directory paths * - Error handling allows graceful degradation when filesystem access fails * - Separate function enables testing and reusability */ async function initLogDir() { //prepare log directory asynchronously ensuring proper filesystem structure try { await fs.promises.mkdir(logDir, { recursive: true }); } //(create directory asynchronously with recursive option for nested paths) catch (err) { console.error(`Failed to create log directory ${logDir}: ${err.message}`); disableFileLogs = true; } //(record failure and disable file logging to prevent repeated errors) } /** * Winston logger instance with multi-format, multi-transport configuration * * Transport strategy: * 1. Error-only file for focused error analysis and alerting * 2. Combined file for comprehensive audit trail and debugging * 3. Console for immediate development feedback and debugging * * Format strategy uses dedicated configurations: * - File transports log JSON for ingestion by aggregation tools * - Console transport uses printf for readable development output * - Each includes timestamp, stack traces and splat interpolation */ async function buildLogger() { //(create logger after directory preparation complete) await initLogDir(); //(ensure directory exists before configuring file transports) const maxDays = Number(process.env.QERRORS_LOG_MAX_DAYS) || 0; //days to retain logs //(calculate dynamically to respect env changes) disableFileLogs = disableFileLogs || !!process.env.QERRORS_DISABLE_FILE_LOGS; //(preserve directory failure flag while applying env override) const DailyRotateFile = require('winston-daily-rotate-file'); //(load dynamically to ensure test stubs work) const log = createLogger({ //(build configured winston logger instance with multi-transport setup) level: config.getEnv('QERRORS_LOG_LEVEL'), //(log level from env variable defaulting to 'info' for errors, warnings, and info messages) defaultMeta: { service: config.getEnv('QERRORS_SERVICE_NAME') }, //(default metadata added to all log entries, service identification helps in multi-service environments) transports: (() => { //(multi-transport configuration for comprehensive log coverage) const arr = []; //(start with empty transport array) if (!disableFileLogs) { //(add file transports when directory creation successful) if (maxDays > 0) { //(use daily rotation when retention period configured) arr.push(new DailyRotateFile({ filename: path.join(logDir, 'error-%DATE%.log'), level: 'error', datePattern: 'YYYY-MM-DD', maxFiles: `${maxDays}d`, maxSize: rotationOpts.maxsize, format: fileFormat })); //(error-only file with daily rotation for focused error analysis) arr.push(new DailyRotateFile({ filename: path.join(logDir, 'combined-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxFiles: `${maxDays}d`, maxSize: rotationOpts.maxsize, format: fileFormat })); //(combined log file with daily rotation for comprehensive audit trail) } else { const fileCap = rotationOpts.maxFiles > 0 ? rotationOpts.maxFiles : 30; //(fallback file count cap when time rotation disabled) arr.push(new transports.File({ filename: path.join(logDir, 'error.log'), level: 'error', ...rotationOpts, maxFiles: fileCap, format: fileFormat })); //(size-based rotation for error files with count limit) arr.push(new transports.File({ filename: path.join(logDir, 'combined.log'), ...rotationOpts, maxFiles: fileCap, format: fileFormat })); //(size-based rotation for combined files with count limit) } } if (process.env.QERRORS_VERBOSE === 'true') { arr.push(new transports.Console({ format: consoleFormat })); } //(console transport only when verbose mode enabled) if (arr.length === 0) { arr.push(new transports.Console({ format: consoleFormat })); } //fallback console transport ensures logger always has output return arr; //(return configured transport array) })() }); if (process.env.QERRORS_VERBOSE === 'true') { log.warn('QERRORS_VERBOSE=true can impact performance at scale'); } //warn when verbose may slow logging if (maxDays === 0 && !disableFileLogs) { log.warn('QERRORS_LOG_MAX_DAYS is 0; log files may grow without bound'); } //(warn about unlimited log retention) return log; //(return fully configured logger instance) } const logger = buildLogger(); //(create promise-based logger instance when module imported) async function logStart(name, data) { const log = await logger; log.info(`${name} start ${JSON.stringify(data)}`); } //(log function start with data, uses promise to ensure logger ready) async function logReturn(name, data) { const log = await logger; log.info(`${name} return ${JSON.stringify(data)}`); } //(log function return with result data, uses promise to invoke logger safely) /** * Enhanced Logging Functions with Security and Performance Monitoring * * These functions provide enhanced logging capabilities with built-in sanitization, * request correlation, and performance monitoring while maintaining backward * compatibility with the existing Winston logger. */ /** * Enhanced debug logging with sanitization and context * * @param {string} message - Log message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logDebug(message, context = {}, requestId = null) { //enhanced debug logging with sanitization const log = await logger; const entry = createEnhancedLogEntry('DEBUG', message, context, requestId); log.debug(entry); } /** * Enhanced info logging with sanitization and context * * @param {string} message - Log message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logInfo(message, context = {}, requestId = null) { //enhanced info logging with sanitization const log = await logger; const entry = createEnhancedLogEntry('INFO', message, context, requestId); log.info(entry); } /** * Enhanced warning logging with sanitization and performance monitoring * * @param {string} message - Log message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logWarn(message, context = {}, requestId = null) { //enhanced warn logging with performance monitoring const log = await logger; const entry = createEnhancedLogEntry('WARN', message, context, requestId); log.warn(entry); } /** * Enhanced error logging with sanitization and performance monitoring * * @param {string} message - Log message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logError(message, context = {}, requestId = null) { //enhanced error logging with performance monitoring const log = await logger; const entry = createEnhancedLogEntry('ERROR', message, context, requestId); log.error(entry); } /** * Enhanced fatal logging with sanitization and performance monitoring * * @param {string} message - Log message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logFatal(message, context = {}, requestId = null) { //enhanced fatal logging with performance monitoring const log = await logger; const entry = createEnhancedLogEntry('FATAL', message, context, requestId); log.error(entry); //winston doesn't have fatal level, use error with enhanced metadata } /** * Enhanced audit logging for compliance and security events * * @param {string} message - Audit message * @param {Object} context - Additional context data * @param {string} requestId - Optional request correlation ID */ async function logAudit(message, context = {}, requestId = null) { //enhanced audit logging for compliance const log = await logger; const entry = createEnhancedLogEntry('AUDIT', message, context, requestId); log.info(entry); //use info level for audit logs with enhanced metadata } /** * Performance Timer Utility * * Purpose: Provides easy performance monitoring for operations * Returns a function that can be called to log the elapsed time. */ function createPerformanceTimer(operation, requestId = null) { //create performance timer for operation monitoring const startTime = process.hrtime.bigint(); const startMemory = process.memoryUsage(); return async function logPerformance(success = true, additionalContext = {}) { const endTime = process.hrtime.bigint(); const endMemory = process.memoryUsage(); const duration = Number(endTime - startTime) / 1000000; //convert to milliseconds const context = { operation, duration_ms: Math.round(duration * 100) / 100, //round to 2 decimal places memory_delta: { heapUsed: Math.round((endMemory.heapUsed - startMemory.heapUsed) / 1024), //KB change external: Math.round((endMemory.external - startMemory.external) / 1024) //KB change }, success, ...additionalContext }; const message = `${operation} completed in ${context.duration_ms}ms (${success ? 'success' : 'failure'})`; if (success) { await logInfo(message, context, requestId); } else { await logWarn(message, context, requestId); } return context; //return performance data for further processing }; } module.exports = logger; //(export promise that resolves to winston logger instance for async initialization) module.exports.logStart = logStart; //(export start logging helper for function entry tracking) module.exports.logReturn = logReturn; //(export return logging helper for function exit tracking) // Enhanced logging functions with security and performance monitoring module.exports.logDebug = logDebug; //(export enhanced debug logging) module.exports.logInfo = logInfo; //(export enhanced info logging) module.exports.logWarn = logWarn; //(export enhanced warn logging) module.exports.logError = logError; //(export enhanced error logging) module.exports.logFatal = logFatal; //(export enhanced fatal logging) module.exports.logAudit = logAudit; //(export enhanced audit logging) // Utility functions for enhanced logging capabilities module.exports.createPerformanceTimer = createPerformanceTimer; //(export performance timer utility) module.exports.sanitizeMessage = sanitizeMessage; //(export message sanitization utility) module.exports.sanitizeContext = sanitizeContext; //(export context sanitization utility) module.exports.createEnhancedLogEntry = createEnhancedLogEntry; //(export enhanced log entry creator) module.exports.LOG_LEVELS = LOG_LEVELS; //(export log level constants) /** * Simple Winston Logger - Basic Configuration Pattern * * Purpose: Provides a straightforward Winston logger configuration similar to the * requested pattern, offering developers a familiar simple logging interface * alongside the enhanced qerrors logging capabilities. */ const createSimpleWinstonLogger = () => { //factory for basic Winston logger return createLogger({ level: 'info', format: format.json(), transports: [ new transports.Console({ format: format.simple() }) ] }); }; // Simple Winston logger instance for basic logging needs const simpleLogger = createSimpleWinstonLogger(); module.exports.simpleLogger = simpleLogger; //(export basic Winston logger instance) module.exports.createSimpleWinstonLogger = createSimpleWinstonLogger; //(export simple logger factory)