UNPKG

weelog

Version:

Next-generation JavaScript logging library with performance tracking, memory monitoring, analytics, and advanced debugging features.

608 lines (603 loc) 23.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WeeLog = {})); })(this, (function (exports) { 'use strict'; /** * WeeLog - Tiny Logging Library for JavaScript * Zero dependencies, browser and Node.js compatible */ class Logger { constructor(options = {}) { this.levels = { debug: 0, info: 1, warn: 2, error: 3 }; this.colors = { debug: '#6b7280', info: '#2563eb', warn: '#f59e0b', error: '#ef4444' }; // Detect environment first this.detectedEnvironment = this.detectEnvironment(options); // Apply environment-specific configuration const finalOptions = this.applyEnvironmentConfig(options); this.level = finalOptions.level || 'info'; this.enabled = finalOptions.enabled !== false; this.useTimestamp = finalOptions.useTimestamp || false; this.useHumanReadableTimestamp = finalOptions.useHumanReadableTimestamp || false; this.enablePerformanceTracking = finalOptions.enablePerformanceTracking || false; this.enableMemoryTracking = finalOptions.enableMemoryTracking || false; this.logMemoryInline = finalOptions.logMemoryInline || false; this.maxLogHistory = finalOptions.maxLogHistory || 1000; this.enableLogAnalytics = finalOptions.enableLogAnalytics || false; this.interceptors = []; this.logHistory = []; this.sessionId = this.generateSessionId(); this.performanceMarks = new Map(); this.analytics = { totalLogs: 0, logsByLevel: { debug: 0, info: 0, warn: 0, error: 0 }, averageLogRate: 0, errorRate: 0, topContexts: [] }; } /** * Detect the current environment */ detectEnvironment(options) { // If explicitly set, use that if (options.environment) { return options.environment; } // If auto-detection is disabled, default to development if (options.autoDetectEnvironment === false) { return 'development'; } // Try to detect from WEELOG_ENV first (higher priority) if (typeof process !== 'undefined' && process.env && process.env.WEELOG_ENV) { const weelogEnv = process.env.WEELOG_ENV.toLowerCase(); if (weelogEnv === 'production' || weelogEnv === 'prod') return 'production'; if (weelogEnv === 'staging' || weelogEnv === 'stage') return 'staging'; if (weelogEnv === 'test' || weelogEnv === 'testing') return 'test'; if (weelogEnv === 'development' || weelogEnv === 'dev') return 'development'; } // Try to detect from NODE_ENV (Node.js) if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV) { const nodeEnv = process.env.NODE_ENV.toLowerCase(); if (nodeEnv === 'production' || nodeEnv === 'prod') return 'production'; if (nodeEnv === 'staging' || nodeEnv === 'stage') return 'staging'; if (nodeEnv === 'test' || nodeEnv === 'testing') return 'test'; if (nodeEnv === 'development' || nodeEnv === 'dev') return 'development'; } // Browser environment detection if (typeof window !== 'undefined') { // Check for common development indicators if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.includes('dev') || window.location.hostname.includes('test') || window.location.port !== '') { return 'development'; } // Check for staging indicators if (window.location.hostname.includes('staging') || window.location.hostname.includes('stage')) { return 'staging'; } // Otherwise assume production in browser return 'production'; } // Default to development if nothing else detected return 'development'; } /** * Apply environment-specific configuration */ applyEnvironmentConfig(options) { const env = this.detectedEnvironment; // Get environment-specific config let envConfig = {}; switch (env) { case 'development': envConfig = options.developmentConfig || { level: 'debug', useTimestamp: true, enablePerformanceTracking: true, enableMemoryTracking: true, enableLogAnalytics: true }; break; case 'production': envConfig = options.productionConfig || { level: 'warn', useTimestamp: true, enablePerformanceTracking: false, enableMemoryTracking: false, logMemoryInline: false, enableLogAnalytics: false }; break; case 'staging': envConfig = options.stagingConfig || { level: 'info', useTimestamp: true, enablePerformanceTracking: true, enableMemoryTracking: true, enableLogAnalytics: true }; break; case 'test': envConfig = options.testConfig || { level: 'error', enabled: false, useTimestamp: false, enablePerformanceTracking: false, enableMemoryTracking: false, enableLogAnalytics: false }; break; } // Merge configurations with explicit options taking precedence const finalConfig = { ...envConfig }; // Only override environment config if explicitly set in options Object.keys(options).forEach(key => { if (key !== 'autoDetectEnvironment' && key !== 'environment' && key !== 'developmentConfig' && key !== 'productionConfig' && key !== 'stagingConfig' && key !== 'testConfig' && options[key] !== undefined) { finalConfig[key] = options[key]; } }); return finalConfig; } /** * Get the detected environment */ getEnvironment() { return this.detectedEnvironment; } /** * Set the minimum log level */ setLevel(level) { this.level = level; return this; } /** * Enable or disable logging */ enable(enabled) { this.enabled = enabled; return this; } /** * Create a logger with a specific context */ withContext(context) { const newLogger = Object.create(this); newLogger.context = context; return newLogger; } /** * Add a log interceptor callback */ onLog(callback) { this.interceptors.push(callback); return this; } /** * Generate a unique session ID */ generateSessionId() { return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); } /** * Get memory usage information */ getMemoryInfo() { if (!this.enableMemoryTracking) return undefined; // Browser environment if (typeof window !== 'undefined' && performance.memory) { const memory = performance.memory; return { used: memory.usedJSHeapSize, total: memory.totalJSHeapSize, percentage: Math.round((memory.usedJSHeapSize / memory.totalJSHeapSize) * 100) }; } // Node.js environment if (typeof process !== 'undefined' && process.memoryUsage) { const memory = process.memoryUsage(); return { used: memory.heapUsed, total: memory.heapTotal, percentage: Math.round((memory.heapUsed / memory.heapTotal) * 100) }; } return undefined; } /** * Format memory usage for inline display */ formatMemoryUsage() { if (!this.logMemoryInline) { return ''; } const memoryInfo = this.getMemoryInfo(); if (!memoryInfo) { return ''; } const memoryMB = (memoryInfo.used / 1024 / 1024).toFixed(2); return ` (Memory: ${memoryMB} MB)`; } /** * Start performance tracking for a specific operation */ startPerformanceTimer(label) { if (this.enablePerformanceTracking) { this.performanceMarks.set(label, Date.now()); } return this; } /** * End performance tracking and log the duration */ endPerformanceTimer(label, message) { if (this.enablePerformanceTracking && this.performanceMarks.has(label)) { const startTime = this.performanceMarks.get(label); const duration = Date.now() - startTime; this.performanceMarks.delete(label); const perfMessage = message || `Performance: ${label} completed`; return this.info(perfMessage, { performanceTimer: label, duration: `${duration}ms`, timestamp: Date.now() }); } return null; } /** * Log with automatic stack trace capture */ trace(message, data) { const stackTrace = new Error().stack; return this.log('debug', message, data, stackTrace); } /** * Get current analytics data */ getAnalytics() { return { ...this.analytics }; } /** * Get log history */ getLogHistory() { return [...this.logHistory]; } /** * Clear log history */ clearHistory() { this.logHistory = []; return this; } /** * Export logs as JSON */ exportLogs() { return JSON.stringify({ sessionId: this.sessionId, exportedAt: new Date().toISOString(), analytics: this.analytics, logs: this.logHistory }, null, 2); } /** * Search logs by criteria */ searchLogs(criteria) { return this.logHistory.filter(entry => { if (criteria.level && entry.level !== criteria.level) return false; if (criteria.context && entry.context !== criteria.context) return false; if (criteria.message && !entry.message.includes(criteria.message)) return false; if (criteria.timeRange) { if (entry.timestamp < criteria.timeRange.start || entry.timestamp > criteria.timeRange.end) { return false; } } return true; }); } /** * Check if a log level should be output */ shouldLog(level) { return this.enabled && this.levels[level] >= this.levels[this.level]; } /** * Format a log message */ formatMessage(level, message, data) { let formatted = ''; if (this.useTimestamp) { const timestamp = new Date(); if (this.useHumanReadableTimestamp) { // Human readable format: "Dec 16, 2024 at 9:45:23 PM" formatted += `[${timestamp.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true })}] `; } else { formatted += `[${timestamp.toISOString()}] `; } } formatted += `[${level.toUpperCase()}]`; if (this.context) { formatted += ` [${this.context}]`; } formatted += ` ${message}`; // Add inline memory usage if enabled if (this.logMemoryInline) { formatted += this.formatMemoryUsage(); } if (data !== undefined && data !== null) { if (typeof data === 'object') { try { // Process the data to apply human readable timestamps if enabled const processedData = this.processDataTimestamps(data); formatted += ` ${JSON.stringify(processedData)}`; } catch (e) { formatted += ` [Object (circular)]`; } } else { formatted += ` ${data}`; } } return formatted; } /** * Process data object to apply human readable timestamps */ processDataTimestamps(data) { if (!this.useHumanReadableTimestamp) { return data; } if (data === null || data === undefined) { return data; } if (typeof data !== 'object') { return data; } if (Array.isArray(data)) { return data.map(item => this.processDataTimestamps(item)); } const processed = {}; for (const [key, value] of Object.entries(data)) { if (key === 'timestamp' && (typeof value === 'number' || value instanceof Date)) { // Convert timestamp to human readable format const date = typeof value === 'number' ? new Date(value) : value; processed[key] = date.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true }); } else if (typeof value === 'object') { processed[key] = this.processDataTimestamps(value); } else { processed[key] = value; } } return processed; } /** * Internal log method */ log(level, message, data, stackTrace) { var _a; if (!this.shouldLog(level)) { return null; } const timestamp = new Date(); const formatted = this.formatMessage(level, message, data); const logEntry = { level, message, context: this.context, data, timestamp, formatted, sessionId: this.sessionId, stackTrace: stackTrace }; // Add performance and memory tracking if (this.enablePerformanceTracking) { logEntry.performance = { timestamp: Date.now(), memoryUsage: this.enableMemoryTracking ? (_a = this.getMemoryInfo()) === null || _a === void 0 ? void 0 : _a.used : undefined }; } if (this.enableMemoryTracking) { logEntry.memory = this.getMemoryInfo(); } // Update analytics if (this.enableLogAnalytics) { this.updateAnalytics(level, this.context); } // Add to history this.logHistory.push(logEntry); if (this.logHistory.length > this.maxLogHistory) { this.logHistory.shift(); } // Call interceptors this.interceptors.forEach(interceptor => { try { interceptor(level, message, this.context, data); } catch (e) { // Avoid infinite loops by using plain console.error if (typeof console !== 'undefined' && console.error) { console.error('Logger interceptor error:', e); } } }); // Output to console this.outputToConsole(level, formatted); return logEntry; } /** * Update analytics data */ updateAnalytics(level, context) { this.analytics.totalLogs++; this.analytics.logsByLevel[level]++; if (level === 'error') { this.analytics.errorRate = (this.analytics.logsByLevel.error / this.analytics.totalLogs) * 100; } if (context) { const existingContext = this.analytics.topContexts.find(c => c.context === context); if (existingContext) { existingContext.count++; } else { this.analytics.topContexts.push({ context, count: 1 }); } // Keep only top 10 contexts this.analytics.topContexts.sort((a, b) => b.count - a.count); this.analytics.topContexts = this.analytics.topContexts.slice(0, 10); } } /** * Output formatted message to console with colors (browser only) */ outputToConsole(level, formatted) { if (typeof console === 'undefined') { return; } const color = this.colors[level]; // Detect browser environment more reliably const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; // Browser environment - use colored output with CSS styling if (isBrowser && console.log) { const weight = level === 'error' ? 'bold' : 'normal'; const styles = `color: ${color}; font-weight: ${weight}; font-family: monospace;`; try { console.log(`%c${formatted}`, styles); } catch (e) { // Fallback if styling fails console.log(formatted); } } // Node.js environment - use appropriate console method with ANSI colors else if (isNode) { const ansiColors = { debug: '\x1b[90m', // gray info: '\x1b[36m', // cyan warn: '\x1b[33m', // yellow error: '\x1b[31m' // red }; const reset = '\x1b[0m'; const coloredMessage = `${ansiColors[level]}${formatted}${reset}`; switch (level) { case 'debug': console.debug ? console.debug(coloredMessage) : console.log(coloredMessage); break; case 'info': console.info(coloredMessage); break; case 'warn': console.warn(coloredMessage); break; case 'error': console.error(coloredMessage); break; default: console.log(coloredMessage); } } // Fallback for other environments else { console.log(formatted); } } /** * Log a debug message */ debug(message, data) { return this.log('debug', message, data); } /** * Log an info message */ info(message, data) { return this.log('info', message, data); } /** * Log a warning message */ warn(message, data) { return this.log('warn', message, data); } /** * Log an error message */ error(message, data) { return this.log('error', message, data); } } // Create a default logger instance for convenience functions const defaultLogger = new Logger(); // Named exports for individual logging functions const log = (message, data) => defaultLogger.info(message, data); const info = (message, data) => defaultLogger.info(message, data); const warn = (message, data) => defaultLogger.warn(message, data); const error = (message, data) => defaultLogger.error(message, data); const debug = (message, data) => defaultLogger.debug(message, data); const success = (message, data) => defaultLogger.info(`✅ ${message}`, data); exports.Logger = Logger; exports.debug = debug; exports.default = Logger; exports.error = error; exports.info = info; exports.log = log; exports.success = success; exports.warn = warn; Object.defineProperty(exports, '__esModule', { value: true }); })); //# sourceMappingURL=weelog.umd.js.map