cross-log
Version:
A universal logging package that works in both browser and Node.js environments with environment variable configuration
1,075 lines (1,066 loc) • 36.6 kB
JavaScript
/**
* Core types and interfaces for the universal logger
*/
// Log level definitions
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["SILENT"] = 4] = "SILENT"; // No logging
})(LogLevel || (LogLevel = {}));
/**
* Type guard to check if a value is a valid LogLevel
*/
function isLogLevel(value) {
return typeof value === 'number' && value >= LogLevel.DEBUG && value <= LogLevel.SILENT;
}
/**
* Type guard to check if a value is a valid LogLevel string
*/
function isLogLevelString(value) {
return typeof value === 'string' && value in 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 : 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 LogLevel.DEBUG;
case 'INFO': return LogLevel.INFO;
case 'WARN': return LogLevel.WARN;
case 'ERROR': return LogLevel.ERROR;
case 'SILENT': return LogLevel.SILENT;
default: return null;
}
}
/**
* Parse boolean from environment variable
*/
function parseEnvBoolean(value, defaultValue = false) {
if (value === undefined)
return defaultValue;
return value.toLowerCase() === 'true';
}
/**
* 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 < LogLevel.SILENT && messageLevel < 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(' ');
}
/**
* 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 ? LogLevel.DEBUG : 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 };
}
}
/**
* Load configuration from environment variables
* Utility function for creating loggers with env-based config
*/
function loadConfigFromEnv() {
const configManager = new ConfigManager();
return configManager.getConfig();
}
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(LogLevel.DEBUG, message, category, ...args);
}
/**
* Log informational message
*/
info(message, category, ...args) {
this.logWithLevel(LogLevel.INFO, message, category, ...args);
}
/**
* Log warning message
*/
warn(message, category, ...args) {
this.logWithLevel(LogLevel.WARN, message, category, ...args);
}
/**
* Log error message or Error object
*/
error(message, category, ...args) {
this.logWithLevel(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 = 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: LogLevel.DEBUG };
}
this.configManager.updateConfig(config);
}
/**
* Enable all logging
*/
enableAll() {
const config = this.configManager.getConfig();
config.enabled = true;
config.minLevel = 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 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 === 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(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 = 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 LogLevel.DEBUG:
return this.originalConsole.debug;
case LogLevel.INFO:
return this.originalConsole.info;
case LogLevel.WARN:
return this.originalConsole.warn;
case 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 LogLevel.DEBUG:
return colors.debug;
case LogLevel.INFO:
return colors.info;
case LogLevel.WARN:
return colors.warn;
case 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);
}
}
/**
* Factory function to create a browser logger instance
*/
function createLogger(config) {
return new BrowserLogger(config);
}
/**
* Browser adapter for universal-logger
* Entry point: universal-logger/browser
*
* Optimized for browser environments with localStorage support
*/
// Create a singleton logger instance for browsers
const browserLogger = createLogger(loadConfigFromEnv());
export { LogLevel, createLogger as createBrowserLogger, browserLogger as default, detectEnvironment$1 as detectEnvironment, getEnvironmentVariable, isLogLevel, isLogLevelString, isProductionEnvironment, browserLogger as logger };
//# sourceMappingURL=browser.esm.js.map