UNPKG

cross-log

Version:

A universal logging package that works in both browser and Node.js environments with environment variable configuration

1,389 lines (1,378 loc) 93.9 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.UniversalLogger = {})); })(this, (function (exports) { 'use strict'; /** * Core types and interfaces for the universal logger */ // Log level definitions exports.LogLevel = void 0; (function (LogLevel) { LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["WARN"] = 2] = "WARN"; LogLevel[LogLevel["ERROR"] = 3] = "ERROR"; LogLevel[LogLevel["SILENT"] = 4] = "SILENT"; // No logging })(exports.LogLevel || (exports.LogLevel = {})); /** * Type guard to check if a value is a valid LogLevel */ function isLogLevel(value) { return typeof value === 'number' && value >= exports.LogLevel.DEBUG && value <= exports.LogLevel.SILENT; } /** * Type guard to check if a value is a valid LogLevel string */ function isLogLevelString(value) { return typeof value === 'string' && value in exports.LogLevel; } /** * Deep merge configuration objects */ function mergeConfig(base, partial) { var _a, _b, _c, _d, _e, _f, _g; const result = { ...base }; if (partial.enabled !== undefined) result.enabled = partial.enabled; if (partial.minLevel !== undefined) result.minLevel = partial.minLevel; if (partial.showTimestamp !== undefined) result.showTimestamp = partial.showTimestamp; if (partial.includeStackTrace !== undefined) result.includeStackTrace = partial.includeStackTrace; if (partial.categories) { result.categories = { ...base.categories }; // Merge partial category configs for (const [key, value] of Object.entries(partial.categories)) { if (value) { result.categories[key] = { enabled: (_c = (_a = value.enabled) !== null && _a !== void 0 ? _a : (_b = base.categories[key]) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : true, minLevel: (_f = (_d = value.minLevel) !== null && _d !== void 0 ? _d : (_e = base.categories[key]) === null || _e === void 0 ? void 0 : _e.minLevel) !== null && _f !== void 0 ? _f : exports.LogLevel.DEBUG }; } } } if (partial.colors) { result.colors = { enabled: (_g = partial.colors.enabled) !== null && _g !== void 0 ? _g : base.colors.enabled, browser: partial.colors.browser ? { ...base.colors.browser, ...partial.colors.browser } : base.colors.browser, ansi: partial.colors.ansi ? { ...base.colors.ansi, ...partial.colors.ansi } : base.colors.ansi }; } if (partial.storage) { result.storage = { ...base.storage, ...partial.storage }; } if (partial.browserControls) { result.browserControls = { ...base.browserControls, ...partial.browserControls }; } return result; } /** * Environment abstraction layer for cross-platform compatibility * Handles differences between Node.js, Browser, and Edge Runtime environments */ /** * Runtime type detection */ const RuntimeType = { BROWSER: 'browser', NODE: 'node', EDGE: 'edge', DENO: 'deno', BUN: 'bun', UNKNOWN: 'unknown' }; /** * Detect the current runtime type */ function detectRuntimeType() { // Browser detection if (typeof window !== 'undefined' && typeof window.document !== 'undefined') { return RuntimeType.BROWSER; } // Deno detection if (typeof globalThis.Deno !== 'undefined') { return RuntimeType.DENO; } // Bun detection if (typeof globalThis.Bun !== 'undefined') { return RuntimeType.BUN; } // Node.js detection (check this before Edge Runtime) if (typeof process !== 'undefined' && process.versions && process.versions.node) { return RuntimeType.NODE; } // Edge Runtime detection (Vercel Edge, Cloudflare Workers, etc.) // Edge runtimes have specific globals and no process.versions // Check for Edge-specific APIs or runtime indicators if (typeof globalThis.process === 'undefined') { // Vercel Edge Runtime if (typeof globalThis.EdgeRuntime !== 'undefined') { return RuntimeType.EDGE; } // Generic Edge detection - has fetch but no process if (typeof globalThis.fetch !== 'undefined') { return RuntimeType.EDGE; } } // Additional Edge Runtime detection for Cloudflare Workers if (typeof globalThis.caches !== 'undefined' && typeof globalThis.process === 'undefined') { return RuntimeType.EDGE; } return RuntimeType.UNKNOWN; } /** * Environment variable abstraction * Works across different runtime environments */ function getEnvironmentVariable(key, defaultValue) { var _a; const runtime = detectRuntimeType(); switch (runtime) { case RuntimeType.NODE: // Node.js environment if (typeof process !== 'undefined' && process.env) { return process.env[key] || defaultValue; } break; case RuntimeType.DENO: // Deno environment try { const denoEnv = (_a = globalThis.Deno) === null || _a === void 0 ? void 0 : _a.env; if (denoEnv && typeof denoEnv.get === 'function') { return denoEnv.get(key) || defaultValue; } } catch (_b) { // Deno may require permissions } break; case RuntimeType.BUN: // Bun environment if (typeof process !== 'undefined' && process.env) { return process.env[key] || defaultValue; } break; case RuntimeType.EDGE: // Edge Runtime (Vercel/Cloudflare Workers) // Try to access environment variables through globalThis if (typeof globalThis[key] !== 'undefined') { return String(globalThis[key]); } break; case RuntimeType.BROWSER: // Browser environment - no direct env var access // Could potentially read from window object if exposed if (typeof window.__ENV__ === 'object' && window.__ENV__[key]) { return window.__ENV__[key]; } break; } return defaultValue; } /** * Detect if running in production mode * Works across different environments */ function isProductionEnvironment() { // Check various production indicators const nodeEnv = getEnvironmentVariable('NODE_ENV'); if (nodeEnv === 'production') return true; const environment = getEnvironmentVariable('ENVIRONMENT'); if (environment === 'production' || environment === 'prod') return true; const vercelEnv = getEnvironmentVariable('VERCEL_ENV'); if (vercelEnv === 'production') return true; const cfPages = getEnvironmentVariable('CF_PAGES'); const cfPagesBranch = getEnvironmentVariable('CF_PAGES_BRANCH'); if (cfPages === '1' && cfPagesBranch === 'main') return true; return false; } /** * Enhanced environment detection that works across all runtimes */ function detectEnvironment$1() { const runtime = detectRuntimeType(); const isProduction = isProductionEnvironment(); return { isBrowser: runtime === RuntimeType.BROWSER, isNode: runtime === RuntimeType.NODE, isDevelopment: !isProduction, isProduction, // Additional runtime info runtime }; } /** * Utility functions for the universal logger */ /** * Detect the current environment * Re-exported from environment module for backward compatibility */ function detectEnvironment() { return detectEnvironment$1(); } /** * Parse log level from string */ function parseLogLevel(level) { if (!level) return null; const upperLevel = level.toUpperCase(); switch (upperLevel) { case 'DEBUG': return exports.LogLevel.DEBUG; case 'INFO': return exports.LogLevel.INFO; case 'WARN': return exports.LogLevel.WARN; case 'ERROR': return exports.LogLevel.ERROR; case 'SILENT': return exports.LogLevel.SILENT; default: return null; } } /** * Parse boolean from environment variable */ function parseEnvBoolean(value, defaultValue = false) { if (value === undefined) return defaultValue; return value.toLowerCase() === 'true'; } /** * Parse integer from environment variable */ function parseEnvInt(value, defaultValue) { if (value === undefined) return defaultValue; const parsed = parseInt(value, 10); return isNaN(parsed) ? defaultValue : parsed; } /** * Get environment variable with fallback * Now uses the environment abstraction for cross-platform compatibility */ function getEnvVar(key, defaultValue) { return getEnvironmentVariable(key, defaultValue); } /** * Format timestamp for logging */ function formatTimestamp(date = new Date()) { return date.toISOString(); } /** * Check if logging should be enabled for the specified level */ function isLoggingEnabled(currentLevel, messageLevel, globalEnabled) { return globalEnabled && messageLevel >= currentLevel && currentLevel < exports.LogLevel.SILENT && messageLevel < exports.LogLevel.SILENT; } /** * Format message with category and timestamp */ function formatMessage(message, category, showTimestamp = true) { const parts = []; if (showTimestamp) { parts.push(`[${formatTimestamp()}]`); } if (category) { parts.push(`[${category}]`); } parts.push(message); return parts.join(' '); } /** * Get console method for log level */ function getConsoleMethod(level) { switch (level) { case exports.LogLevel.DEBUG: return console.debug; case exports.LogLevel.INFO: return console.info; case exports.LogLevel.WARN: return console.warn; case exports.LogLevel.ERROR: return console.error; default: return console.log; } } /** * Create ANSI color code for terminal output */ function createAnsiColor(colorCode) { return `\x1b[${colorCode}m`; } /** * Reset ANSI color */ function resetAnsiColor() { return '\x1b[0m'; } /** * Configuration manager with environment-based defaults */ class ConfigManager { constructor(initialConfig) { this.environment = detectEnvironment(); this.config = this.mergeWithDefaults(initialConfig); } /** * Get the current configuration */ getConfig() { return { ...this.config }; } /** * Update configuration */ updateConfig(newConfig) { this.config = mergeConfig(this.config, newConfig); } /** * Merge user config with smart defaults */ mergeWithDefaults(userConfig) { const envConfig = this.getEnvConfig(); // Base defaults without environment variables const baseDefaults = { enabled: true, minLevel: this.environment.isDevelopment ? exports.LogLevel.DEBUG : exports.LogLevel.WARN, showTimestamp: true, includeStackTrace: true, categories: {}, colors: { enabled: this.getDefaultColorsEnabled(), browser: { debug: '#6EC1E4', info: '#4A9FCA', warn: '#FBC02D', error: '#D67C2A' }, ansi: { debug: 36, info: 36, warn: 33, error: 31 } }, storage: { enabled: this.environment.isBrowser, keyPrefix: 'universal_logger' }, browserControls: { enabled: this.environment.isBrowser && this.environment.isDevelopment, windowNamespace: '__universalLogger' } }; // Environment variable overrides const envOverrides = {}; if (envConfig.LOGGER_ENABLED !== undefined) { envOverrides.enabled = parseEnvBoolean(envConfig.LOGGER_ENABLED, true); } if (envConfig.LOG_LEVEL !== undefined) { const envLevel = parseLogLevel(envConfig.LOG_LEVEL); if (envLevel !== null) { envOverrides.minLevel = envLevel; } } if (envConfig.LOGGER_TIMESTAMPS !== undefined) { envOverrides.showTimestamp = parseEnvBoolean(envConfig.LOGGER_TIMESTAMPS, true); } if (envConfig.LOGGER_STACK_TRACES !== undefined) { envOverrides.includeStackTrace = parseEnvBoolean(envConfig.LOGGER_STACK_TRACES, true); } // Handle colors configuration const colorsOverride = {}; let hasColorOverrides = false; if (envConfig.LOGGER_COLORS !== undefined) { colorsOverride.enabled = parseEnvBoolean(envConfig.LOGGER_COLORS, this.getDefaultColorsEnabled()); hasColorOverrides = true; } // Browser color overrides const browserColors = {}; let hasBrowserColorOverrides = false; if (envConfig.LOGGER_COLOR_DEBUG) { browserColors.debug = envConfig.LOGGER_COLOR_DEBUG; hasBrowserColorOverrides = true; } if (envConfig.LOGGER_COLOR_INFO) { browserColors.info = envConfig.LOGGER_COLOR_INFO; hasBrowserColorOverrides = true; } if (envConfig.LOGGER_COLOR_WARN) { browserColors.warn = envConfig.LOGGER_COLOR_WARN; hasBrowserColorOverrides = true; } if (envConfig.LOGGER_COLOR_ERROR) { browserColors.error = envConfig.LOGGER_COLOR_ERROR; hasBrowserColorOverrides = true; } // ANSI color overrides const ansiColors = {}; let hasAnsiColorOverrides = false; if (envConfig.LOGGER_ANSI_DEBUG) { const parsed = parseInt(envConfig.LOGGER_ANSI_DEBUG, 10); if (!isNaN(parsed)) { ansiColors.debug = parsed; hasAnsiColorOverrides = true; } } if (envConfig.LOGGER_ANSI_INFO) { const parsed = parseInt(envConfig.LOGGER_ANSI_INFO, 10); if (!isNaN(parsed)) { ansiColors.info = parsed; hasAnsiColorOverrides = true; } } if (envConfig.LOGGER_ANSI_WARN) { const parsed = parseInt(envConfig.LOGGER_ANSI_WARN, 10); if (!isNaN(parsed)) { ansiColors.warn = parsed; hasAnsiColorOverrides = true; } } if (envConfig.LOGGER_ANSI_ERROR) { const parsed = parseInt(envConfig.LOGGER_ANSI_ERROR, 10); if (!isNaN(parsed)) { ansiColors.error = parsed; hasAnsiColorOverrides = true; } } if (hasColorOverrides || hasBrowserColorOverrides || hasAnsiColorOverrides) { envOverrides.colors = { ...baseDefaults.colors, ...colorsOverride, browser: hasBrowserColorOverrides ? { ...baseDefaults.colors.browser, ...browserColors } : baseDefaults.colors.browser, ansi: hasAnsiColorOverrides ? { ...baseDefaults.colors.ansi, ...ansiColors } : baseDefaults.colors.ansi }; } // Storage overrides const storageOverride = {}; let hasStorageOverrides = false; if (envConfig.LOGGER_STORAGE_ENABLED !== undefined) { storageOverride.enabled = parseEnvBoolean(envConfig.LOGGER_STORAGE_ENABLED, this.environment.isBrowser); hasStorageOverrides = true; } if (envConfig.LOGGER_STORAGE_KEY_PREFIX) { storageOverride.keyPrefix = envConfig.LOGGER_STORAGE_KEY_PREFIX; hasStorageOverrides = true; } if (hasStorageOverrides) { envOverrides.storage = { ...baseDefaults.storage, ...storageOverride }; } // Browser controls overrides const browserControlsOverride = {}; let hasBrowserControlsOverrides = false; if (envConfig.LOGGER_BROWSER_CONTROLS !== undefined) { browserControlsOverride.enabled = parseEnvBoolean(envConfig.LOGGER_BROWSER_CONTROLS, this.environment.isBrowser && this.environment.isDevelopment); hasBrowserControlsOverrides = true; } if (envConfig.LOGGER_WINDOW_NAMESPACE) { browserControlsOverride.windowNamespace = envConfig.LOGGER_WINDOW_NAMESPACE; hasBrowserControlsOverrides = true; } if (hasBrowserControlsOverrides) { envOverrides.browserControls = { ...baseDefaults.browserControls, ...browserControlsOverride }; } // Merge: base defaults < user config < environment variables let result = baseDefaults; if (userConfig) { result = mergeConfig(result, userConfig); } if (Object.keys(envOverrides).length > 0) { result = mergeConfig(result, envOverrides); } return result; } /** * Get environment configuration */ getEnvConfig() { return { LOG_LEVEL: getEnvVar('LOG_LEVEL'), LOGGER_ENABLED: getEnvVar('LOGGER_ENABLED'), LOGGER_TIMESTAMPS: getEnvVar('LOGGER_TIMESTAMPS'), LOGGER_STACK_TRACES: getEnvVar('LOGGER_STACK_TRACES'), LOGGER_COLORS: getEnvVar('LOGGER_COLORS'), LOGGER_STORAGE_ENABLED: getEnvVar('LOGGER_STORAGE_ENABLED'), LOGGER_STORAGE_KEY_PREFIX: getEnvVar('LOGGER_STORAGE_KEY_PREFIX'), LOGGER_BROWSER_CONTROLS: getEnvVar('LOGGER_BROWSER_CONTROLS'), LOGGER_WINDOW_NAMESPACE: getEnvVar('LOGGER_WINDOW_NAMESPACE'), LOGGER_COLOR_DEBUG: getEnvVar('LOGGER_COLOR_DEBUG'), LOGGER_COLOR_INFO: getEnvVar('LOGGER_COLOR_INFO'), LOGGER_COLOR_WARN: getEnvVar('LOGGER_COLOR_WARN'), LOGGER_COLOR_ERROR: getEnvVar('LOGGER_COLOR_ERROR'), LOGGER_ANSI_DEBUG: getEnvVar('LOGGER_ANSI_DEBUG'), LOGGER_ANSI_INFO: getEnvVar('LOGGER_ANSI_INFO'), LOGGER_ANSI_WARN: getEnvVar('LOGGER_ANSI_WARN'), LOGGER_ANSI_ERROR: getEnvVar('LOGGER_ANSI_ERROR') }; } /** * Get default colors enabled setting */ getDefaultColorsEnabled() { // Colors enabled in browser, or Node.js development return this.environment.isBrowser || this.environment.isDevelopment; } /** * Get environment information */ getEnvironment() { return { ...this.environment }; } } class PluginManager { constructor(logger) { this.plugins = new Map(); this.logger = logger; } use(plugin) { if (this.plugins.has(plugin.name)) { this.logger.warn(`Plugin "${plugin.name}" is already registered`, 'PluginManager'); return; } const context = { logger: this.logger, config: plugin.config || { enabled: true } }; const instance = { plugin, context }; try { plugin.init(context); // Get methods from context after init if (context.methods) { instance.methods = context.methods; } this.plugins.set(plugin.name, instance); this.attachPluginMethods(plugin.name, instance); this.logger.debug(`Plugin "${plugin.name}" initialized successfully`, 'PluginManager'); } catch (error) { this.logger.error(`Failed to initialize plugin "${plugin.name}"`, 'PluginManager', error); throw error; } } attachPluginMethods(name, instance) { const loggerWithPlugins = this.logger; switch (name) { case 'api': loggerWithPlugins.api = instance.methods; break; case 'database': loggerWithPlugins.database = instance.methods; break; case 'analytics': loggerWithPlugins.analytics = instance.methods; break; case 'performance': loggerWithPlugins.performance = instance.methods; break; case 'security': loggerWithPlugins.security = instance.methods; break; } } remove(name) { const instance = this.plugins.get(name); if (!instance) { return false; } try { if (instance.plugin.destroy) { instance.plugin.destroy(); } this.detachPluginMethods(name); this.plugins.delete(name); this.logger.debug(`Plugin "${name}" removed successfully`, 'PluginManager'); return true; } catch (error) { this.logger.error(`Failed to remove plugin "${name}"`, 'PluginManager', error); return false; } } detachPluginMethods(name) { const loggerWithPlugins = this.logger; switch (name) { case 'api': delete loggerWithPlugins.api; break; case 'database': delete loggerWithPlugins.database; break; case 'analytics': delete loggerWithPlugins.analytics; break; case 'performance': delete loggerWithPlugins.performance; break; case 'security': delete loggerWithPlugins.security; break; } } getPlugin(name) { return this.plugins.get(name); } getAllPlugins() { return Array.from(this.plugins.values()); } hasPlugin(name) { return this.plugins.has(name); } clear() { for (const [name] of this.plugins) { this.remove(name); } } } /** * Base logger implementation with shared functionality */ class BaseLogger { constructor(initialConfig) { this.recentLogs = new Map(); this.duplicateThreshold = 1000; // ms this.configManager = new ConfigManager(initialConfig); this.pluginManager = new PluginManager(this); } /** * Log debug message */ debug(message, category, ...args) { this.logWithLevel(exports.LogLevel.DEBUG, message, category, ...args); } /** * Log informational message */ info(message, category, ...args) { this.logWithLevel(exports.LogLevel.INFO, message, category, ...args); } /** * Log warning message */ warn(message, category, ...args) { this.logWithLevel(exports.LogLevel.WARN, message, category, ...args); } /** * Log error message or Error object */ error(message, category, ...args) { this.logWithLevel(exports.LogLevel.ERROR, message, category, ...args); } /** * Set the minimum log level */ setLevel(level) { const config = this.configManager.getConfig(); this.configManager.updateConfig({ ...config, minLevel: level }); } /** * Configure the logger */ configure(newConfig) { this.configManager.updateConfig(newConfig); } /** * Enable logging for a specific category */ enableCategory(category, minLevel = exports.LogLevel.DEBUG) { const config = this.configManager.getConfig(); config.categories[category] = { enabled: true, minLevel }; this.configManager.updateConfig(config); } /** * Disable logging for a specific category */ disableCategory(category) { const config = this.configManager.getConfig(); if (config.categories[category]) { config.categories[category].enabled = false; } else { config.categories[category] = { enabled: false, minLevel: exports.LogLevel.DEBUG }; } this.configManager.updateConfig(config); } /** * Enable all logging */ enableAll() { const config = this.configManager.getConfig(); config.enabled = true; config.minLevel = exports.LogLevel.DEBUG; // Enable all existing categories Object.keys(config.categories).forEach(category => { config.categories[category].enabled = true; }); this.configManager.updateConfig(config); } /** * Disable all logging */ disableAll() { const config = this.configManager.getConfig(); config.enabled = false; this.configManager.updateConfig(config); } /** * Get the current logger configuration */ getConfig() { return this.configManager.getConfig(); } /** * Check if logging is currently enabled */ isEnabled() { return this.configManager.getConfig().enabled; } /** * Log level constants */ get Level() { return exports.LogLevel; } /** * Use a plugin with the logger */ use(plugin) { this.pluginManager.use(plugin); const instance = this.pluginManager.getPlugin(plugin.name); if (instance === null || instance === void 0 ? void 0 : instance.methods) { this[plugin.name] = instance.methods; } return this; } /** * Get a plugin instance by name */ getPlugin(name) { const instance = this.pluginManager.getPlugin(name); return instance === null || instance === void 0 ? void 0 : instance.plugin; } /** * Check if a plugin is loaded */ hasPlugin(name) { return this.pluginManager.hasPlugin(name); } /** * Remove a plugin */ removePlugin(name) { this.pluginManager.remove(name); delete this[name]; } /** * Core logging method with level checking */ logWithLevel(level, message, category, ...args) { const config = this.configManager.getConfig(); // Check if logging is globally enabled if (!config.enabled) return; // Check category-specific settings first if (category && config.categories[category]) { const categoryConfig = config.categories[category]; if (!categoryConfig.enabled) return; if (!isLoggingEnabled(categoryConfig.minLevel, level, true)) return; } else { // Check global log level only if no category-specific settings if (!isLoggingEnabled(config.minLevel, level, true)) return; } // Check for duplicate logs if (this.isDuplicateLog(message, category)) return; // Create log entry const logEntry = { level, message: typeof message === 'string' ? message : message.message, category, timestamp: new Date(), args }; // Format message const formattedMessage = formatMessage(logEntry.message, category, config.showTimestamp); // Output the log this.outputLog(level, formattedMessage, logEntry, ...args); // Handle error stack traces if (level === exports.LogLevel.ERROR && config.includeStackTrace) { // Check if message is an Error if (message instanceof Error) { this.outputStackTrace(message); } else { // Check if any of the arguments is an Error for (const arg of args) { if (arg instanceof Error) { this.outputStackTrace(arg); break; // Only output the first error's stack trace } } } } } /** * Check if this is a duplicate log message */ isDuplicateLog(message, category) { const key = `${typeof message === 'string' ? message : message.message}:${category || ''}`; const now = Date.now(); const lastTime = this.recentLogs.get(key); if (lastTime && (now - lastTime) < this.duplicateThreshold) { return true; } this.recentLogs.set(key, now); // Clean up old entries if (this.recentLogs.size > 100) { const cutoff = now - this.duplicateThreshold * 2; for (const [k, time] of this.recentLogs.entries()) { if (time < cutoff) { this.recentLogs.delete(k); } } } return false; } } /** * Browser-specific logger implementation */ class BrowserLogger extends BaseLogger { constructor(initialConfig) { super(initialConfig); // Store references to original console methods to prevent infinite recursion this.originalConsole = { debug: console.debug.bind(console), info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), log: console.log.bind(console) }; this.storageAvailable = this.checkStorageAvailability(); this.loadConfigFromStorage(); this.setupBrowserControls(); } /** * Output log with browser-specific styling */ outputLog(level, formattedMessage, _logEntry, ...args) { const config = this.configManager.getConfig(); // Get the original console method to avoid infinite recursion // when logger methods are used to replace console methods const originalConsole = this.getOriginalConsoleMethod(level); if (config.colors.enabled) { const color = this.getColorForLevel(level); const style = `color: ${color}; font-weight: bold`; originalConsole(`%c${formattedMessage}`, style, ...args); } else { originalConsole(formattedMessage, ...args); } } /** * Output stack trace for errors */ outputStackTrace(error) { if (error.stack) { // Use original console.error to avoid infinite recursion const originalError = this.getOriginalConsoleMethod(exports.LogLevel.ERROR); originalError(error.stack); } } /** * Override configure to save to storage */ configure(newConfig) { super.configure(newConfig); this.saveConfigToStorage(); } /** * Override setLevel to save to storage */ setLevel(level) { super.setLevel(level); this.saveConfigToStorage(); } /** * Override enableCategory to save to storage */ enableCategory(category, minLevel = exports.LogLevel.DEBUG) { super.enableCategory(category, minLevel); this.saveConfigToStorage(); } /** * Override disableCategory to save to storage */ disableCategory(category) { super.disableCategory(category); this.saveConfigToStorage(); } /** * Override enableAll to save to storage */ enableAll() { super.enableAll(); this.saveConfigToStorage(); } /** * Override disableAll to save to storage */ disableAll() { super.disableAll(); this.saveConfigToStorage(); } /** * Get original console method to avoid infinite recursion */ getOriginalConsoleMethod(level) { switch (level) { case exports.LogLevel.DEBUG: return this.originalConsole.debug; case exports.LogLevel.INFO: return this.originalConsole.info; case exports.LogLevel.WARN: return this.originalConsole.warn; case exports.LogLevel.ERROR: return this.originalConsole.error; default: return this.originalConsole.log; } } /** * Get color for log level */ getColorForLevel(level) { const config = this.configManager.getConfig(); const colors = config.colors.browser; switch (level) { case exports.LogLevel.DEBUG: return colors.debug; case exports.LogLevel.INFO: return colors.info; case exports.LogLevel.WARN: return colors.warn; case exports.LogLevel.ERROR: return colors.error; default: return colors.info; } } /** * Check if localStorage is available */ checkStorageAvailability() { try { if (typeof window === 'undefined' || !window.localStorage) { return false; } const testKey = '__logger_test__'; window.localStorage.setItem(testKey, 'test'); window.localStorage.removeItem(testKey); return true; } catch (_a) { return false; } } /** * Load configuration from localStorage */ loadConfigFromStorage() { if (!this.storageAvailable) return; const config = this.configManager.getConfig(); if (!config.storage.enabled) return; try { const configKey = `${config.storage.keyPrefix}_config`; const enabledKey = `${config.storage.keyPrefix}_enabled`; const savedConfig = localStorage.getItem(configKey); const savedEnabled = localStorage.getItem(enabledKey); if (savedConfig) { const parsedConfig = JSON.parse(savedConfig); this.configManager.updateConfig(parsedConfig); } if (savedEnabled !== null) { const enabled = savedEnabled === 'true'; this.configManager.updateConfig({ enabled }); } } catch (error) { console.error('Error loading logger config from localStorage:', error); } } /** * Save configuration to localStorage */ saveConfigToStorage() { if (!this.storageAvailable) return; const config = this.configManager.getConfig(); if (!config.storage.enabled) return; try { const configKey = `${config.storage.keyPrefix}_config`; const enabledKey = `${config.storage.keyPrefix}_enabled`; localStorage.setItem(configKey, JSON.stringify(config)); localStorage.setItem(enabledKey, config.enabled.toString()); } catch (error) { console.error('Error saving logger config to localStorage:', error); } } /** * Setup browser console controls */ setupBrowserControls() { const config = this.configManager.getConfig(); if (!config.browserControls.enabled || typeof window === 'undefined') { return; } const win = window; const namespace = config.browserControls.windowNamespace; // Expose the logger instance win[namespace] = this; // Helper functions const enableFuncName = `enable${this.capitalizeFirst(namespace.replace('__', ''))}Logging`; const disableFuncName = `disable${this.capitalizeFirst(namespace.replace('__', ''))}Logging`; const statusFuncName = `${namespace.replace('__', '')}LoggingStatus`; win[enableFuncName] = () => { this.enableAll(); console.log(`%cLogging enabled!`, `color: ${config.colors.browser.info}; font-size: 14px; font-weight: bold`); return `Logging enabled. Use ${namespace} to access the logger API.`; }; win[disableFuncName] = () => { this.disableAll(); console.log(`%cLogging disabled!`, `color: ${config.colors.browser.warn}; font-size: 14px; font-weight: bold`); return 'Logging disabled.'; }; win[statusFuncName] = () => { const currentConfig = this.getConfig(); const status = currentConfig.enabled ? 'enabled' : 'disabled'; console.log(`%cLogging is currently ${status}`, `color: ${config.colors.browser.info}; font-size: 14px;`); if (currentConfig.enabled) { console.log('Current configuration:', currentConfig); } return { enabled: currentConfig.enabled, config: currentConfig }; }; } /** * Capitalize first letter of string */ capitalizeFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } } /** * Node.js-specific logger implementation */ class NodeLogger extends BaseLogger { constructor(initialConfig) { super(initialConfig); // Store references to original console methods to prevent infinite recursion this.originalConsole = { debug: console.debug.bind(console), info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), log: console.log.bind(console) }; } /** * Output log with Node.js-specific ANSI coloring */ outputLog(level, formattedMessage, _logEntry, ...args) { const config = this.configManager.getConfig(); // Get the original console method to avoid infinite recursion // when logger methods are used to replace console methods const originalConsole = this.getOriginalConsoleMethod(level); if (config.colors.enabled) { const colorCode = this.getAnsiColorForLevel(level); const colorStart = createAnsiColor(colorCode); const colorEnd = resetAnsiColor(); originalConsole(`${colorStart}${formattedMessage}${colorEnd}`, ...args); } else { originalConsole(formattedMessage, ...args); } } /** * Output stack trace for errors */ outputStackTrace(error) { if (error.stack) { // Use original console.error to avoid infinite recursion const originalError = this.getOriginalConsoleMethod(exports.LogLevel.ERROR); originalError(error.stack); } } /** * Get original console method to avoid infinite recursion */ getOriginalConsoleMethod(level) { switch (level) { case exports.LogLevel.DEBUG: return this.originalConsole.debug; case exports.LogLevel.INFO: return this.originalConsole.info; case exports.LogLevel.WARN: return this.originalConsole.warn; case exports.LogLevel.ERROR: return this.originalConsole.error; default: return this.originalConsole.log; } } /** * Get ANSI color code for log level */ getAnsiColorForLevel(level) { const config = this.configManager.getConfig(); const colors = config.colors.ansi; switch (level) { case exports.LogLevel.DEBUG: return colors.debug; case exports.LogLevel.INFO: return colors.info; case exports.LogLevel.WARN: return colors.warn; case exports.LogLevel.ERROR: return colors.error; default: return colors.info; } } } /** * Edge Runtime logger implementation * Compatible with Vercel Edge Functions, Cloudflare Workers, and other Edge Runtimes */ /** * Edge-specific logger that works in Edge Runtime environments * Uses only Web APIs and avoids Node.js-specific features */ class EdgeLogger extends BaseLogger { constructor(config) { var _a, _b; // Apply Edge-specific defaults before passing to parent const edgeConfig = config ? { ...config, storage: { enabled: false, keyPrefix: ((_a = config.storage) === null || _a === void 0 ? void 0 : _a.keyPrefix) || 'logger_' }, browserControls: { enabled: false, windowNamespace: ((_b = config.browserControls) === null || _b === void 0 ? void 0 : _b.windowNamespace) || 'logger' } } : undefined; super(edgeConfig); } /** * Output log implementation for Edge Runtime * Uses console methods which are available in Edge environments */ outputLog(level, formattedMessage, _logEntry, ...args) { const consoleMethod = getConsoleMethod(level); // Edge runtimes support console methods if (args.length > 0) { consoleMethod(formattedMessage, ...args); } else { consoleMethod(formattedMessage); } } /** * Output stack trace for Edge Runtime * Limited stack trace support in Edge environments */ outputStackTrace(error) { if (error.stack) { console.error(error.stack); } } /** * Edge-specific configuration * Disables features not available in Edge Runtime */ configure(config) { var _a, _b; // Disable features not available in Edge Runtime const edgeConfig = { ...config, // Storage is not available in Edge Runtime storage: { enabled: false, keyPrefix: ((_a = config.storage) === null || _a === void 0 ? void 0 : _a.keyPrefix) || 'logger_' }, // Browser controls are not applicable browserControls: { enabled: false, windowNamespace: ((_b = config.browserControls) === null || _b === void 0 ? void 0 : _b.windowNamespace) || 'logger' } }; super.configure(edgeConfig); } } /** * Builder pattern implementation for creating loggers with type-safe plugin support */ /** * Implementation of the LoggerBuilder with type-safe plugin chaining */ class LoggerBuilderImpl { constructor(config) { this.loadedPlugins = []; this.logger = createLogger(config); this.pluginManager = new PluginManager(this.logger); } withPlugin(plugin) { this.pluginManager.use(plugin); this.loadedPlugins.push(plugin.name); // Attach methods to logger const loggerWithPlugins = this.logger; const instance = this.pluginManager.getPlugin(plugin.name); if (instance === null || instance === void 0 ? void 0 : instance.methods) { loggerWithPlugins[plugin.name] = instance.methods; } return this; } build() { const loggerWithPlugins = this.logger; // Add plugin management methods loggerWithPlugins.use = (plugin) => { this.pluginManager.use(plugin); const instance = this.pluginManager.getPlugin(plugin.name); if (instance === null || instance === void 0 ? void 0 : instance.methods) { loggerWithPlugins[plugin.name] = instance.methods; } return loggerWithPlugins; }; loggerWithPlugins.getPlugin = (name) => { const instance = this.pluginManager.getPlugin(name); return instance ? instance.plugin : undefined; }; loggerWithPlugins.hasPlugin = (name) => { return this.pluginManager.hasPlugin(name); }; loggerWithPlugins.removePlugin = (name) => { this.pluginManager.remove(name); delete loggerWithPlugins[name]; }; return loggerWithPlugins; } } /** * Create a new logger builder with optional configuration */ function createLoggerBuilder(config) { return new LoggerBuilderImpl(config); } /** * Create a logger with plugins using a configuration object * Provides full type safety for all configured plugins */ function createLoggerWithPlugins(loggerConfig, pluginConfigs) { const builder = createLoggerBuilder(loggerConfig); let currentBuilder = builder; if (pluginConfigs) { // Import plugins dynamically to avoid circular dependencies const { plugins } = require('./index'); // Add each configured plugin for (const [pluginName, config] of Object.entries(pluginConfigs)) { if (config && pluginName in plugins) { const pluginFactory = plugins[pluginName]; if (pluginFactory) { currentBuilder = currentBuilder.withPlugin(pluginFactory(config)); } } } } return currentBuilder.build(); } /** * Type-safe wrapper for existing logger instance * Adds type information for already loaded plugins */ function wrapLogger(logger, // eslint-disable-next-line @typescript-eslint/no-unused-vars _pluginNames) { return logger; } /** * Type guard to check if logger has a specific plugin */ function hasPlugin(logger, pluginName) { return pluginName in logger && typeof logger[pluginName] === 'object'; } /** * Type guard to check if logger has multiple plugins */ function hasPlugins(logger, pluginNames) { return pluginNames.every(name => hasPlugin(logger, name)); } class ApiRequestPlugin { constructor(config) { this.name = 'api'; this.version = '1.0.0'; this.config = { enabled: true, includeSessionId: true, includeHeaders: false, truncateUrl: 100, ...config }; } init(context) { this.context = context; if (this.config.includeSessionId) { this.sessionId = this.generateSessionId(); } const methods = { request: this.request.bind(this), response: this.response.bind(this), error: this.error.bind(this) }; this.methods = methods; context.methods = methods; const instance = context; instance.methods = methods; } generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } truncateUrl(url) { if (!this.config.truncateUrl || url.length <= this.config.truncateUrl) { return url; } return url.substring(0, this.config.truncateUrl) + '...'; } formatDuration(duration) { if (duration === undefined) return ''; if (duration < 1000) return `${duration}ms`; return `${(duration / 1000).toFixed(2)}s`; } request(method, url, duration, status, data) { if (!this.context || !this.config.enab