UNPKG

smyld-lib-common

Version:

SMYLD Javascript Core Library, contains several core APIs that benefit the developers working on SPA applications

487 lines 17.7 kB
import { LogMessage, Type } from './LogMessage'; import { LogLevel } from './LogSettings'; import { LogManager } from "./LogManager"; /** * @class * Core logging utility that provides methods for logging messages with different severity levels. * Supports log caching, custom formatting, and integration with browser console. */ export class Logger { /** Date format string for log timestamps */ dateFormat = 'y-MM-dd_HH:mm:ss'; /** Date formatting options */ options = { month: "long", day: "numeric", year: "numeric" }; /** Locale for date formatting */ locale = "de-DE"; /** Array to store cached log messages */ logs = []; /** Reference to the standard log function */ stdlog; /** Array to store console logs */ clogs = []; /** Source identifier for this logger */ source; /** Current log level */ _logLevel = LogLevel.ALL; /** Abbriviated Source identifier for this logger */ sourceLog; /** * Creates a new Logger instance and registers it with the LogManager. * * @param {LoggerConfig} [params={ source: 'MainLogger', logLevel: LogLevel.DEBUG }] - Configuration options * * @example * // Create the default MainLogger * const mainLogger = new Logger(); * * @example * // Create a custom logger for a specific component * const authLogger = new Logger({ * source: 'AuthService', * logLevel: LogLevel.ERROR * }); */ constructor(params = { source: 'MainLogger', logLevel: LogLevel.DEBUG }) { const { source, logLevel } = params; const logManager = LogManager.getInstance(); if (logManager.hasLogger(source) && logManager.logSettings?.pooledLoggers) { // If a logger with the same source already exists, return that instance return logManager.getLogger(source); } if (source !== undefined) { this.source = source; this.initSourceLog(); } if (logLevel !== undefined) { this._logLevel = logLevel; } else { // Set different default log levels for MainLogger and non-MainLogger instances if (this.source === 'MainLogger') { this._logLevel = LogLevel.DEBUG; } else if (logManager.logLevel !== undefined) { this._logLevel = logManager.logLevel; // Fallback to DEFAULT log level if not set } else { this._logLevel = LogLevel.DEFAULT; } } logManager.registerLogger(this.source, this); // Enable log caching by default for MainLogger this.handleLogsCache(); } /** * Gets the current log level. * * @returns {LogLevel} The current log level */ get logLevel() { return this._logLevel; } /** * Sets the log level. * * @param {LogLevel} value - The new log level */ set logLevel(value) { this._logLevel = value; } /** * Creates a formatted date string for log timestamps. * * @private * @returns {string} Formatted date string */ createDate() { return new Date().toLocaleString(); // Alternative date formatting options (currently commented out): // return new Date().toLocaleString(this.locale, this.options); // return new Intl.DateTimeFormat(this.locale, this.options).format(); } /** * Initializes the source log format by abbreviating namespace parts. * For namespaced sources (e.g. 'app.service.component'), abbreviates all parts except the last one. * Example: 'app.service.component' becomes 'a.s.component' */ initSourceLog() { const NAMESPACE_SEPARATOR = '.'; if (!this.source.includes(NAMESPACE_SEPARATOR)) { this.sourceLog = this.source; return; } const sourceTokens = this.source.split(NAMESPACE_SEPARATOR); const lastToken = sourceTokens[sourceTokens.length - 1]; this.sourceLog = this.formatNamespacedSource(sourceTokens, lastToken); console.debug(`Logging Source value for '${this.source}' : ${this.sourceLog}`); } /** * Formats a namespaced source by abbreviating all parts except the last one. * @param tokens - Array of namespace tokens * @param lastToken - The last token to keep unchanged * @returns Formatted source string */ formatNamespacedSource(tokens, lastToken) { const abbreviatedParts = tokens .slice(0, -1) .map(token => token !== lastToken ? `${token.charAt(0)}.` : token) .join(''); return `${abbreviatedParts}${lastToken}`; } /** * Applies settings to the logger. * * @param {LogSettings} logSettings - The settings to apply * * @example * logger.setLogSettings({ * cacheLogs: true, * logLevel: LogLevel.DEBUG * }); */ setLogSettings(logSettings) { if (logSettings !== undefined) { if (logSettings.cacheLogs) { this.handleLogsCache(); } if (logSettings.logLevel !== undefined) { this._logLevel = logSettings.logLevel; } } } /** * Saves a console message to the logs cache. * This method is used internally by the console method overrides. * * @param {string} type - The type of log message (Log, Debug, Info, Error, Warn) * @param {any} args - The arguments passed to the console method * @param {Function} origFun - The original console function to call after caching */ doSaveMessage(type, args, origFun) { try { if (args && args !== undefined && Object.keys(args)) { let msg = ''; for (const [key, value] of Object.entries(args)) { if (typeof value === 'string') { msg = msg + `${key}: ${value} `; } } // Cache the log message if it's not empty and is not formatted by the logger itself if ((msg !== '') && (msg.indexOf('%c') === -1)) { this.logs.push(type + "- " + msg); } } } catch (error) { if (error instanceof TypeError) { console.log('-------- ' + typeof args); this.logs.push(type + "- " + JSON.stringify(args)); } else { console.error("Error upon trying to save the message with the following args ", error); } } finally { // Always call the original console function origFun.apply(console, args); } } /** * Sets up log caching by overriding console methods. * When enabled, all console logs will be captured in the logs array. * * @private */ handleLogsCache() { const settings = LogManager.getInstance().logSettings; if (this.source !== 'MainLogger' || (settings !== undefined && !settings.cacheLogs)) return; const instance = this; // Override console.log const origFunLog = console.log; console.log = function () { instance.doSaveMessage("Log ", arguments, origFunLog); }; // Override console.debug const origFunDbg = console.debug; console.debug = function () { instance.doSaveMessage("Debug ", arguments, origFunDbg); }; // Override console.info const origFunInf = console.info; console.info = function () { instance.doSaveMessage("Info ", arguments, origFunInf); }; // Override console.error const origFunErr = console.error; console.error = function () { instance.doSaveMessage("Error ", arguments, origFunErr); }; // Override console.warn const origFunWarn = console.warn; console.warn = function () { instance.doSaveMessage("Warn ", arguments, origFunWarn); }; } /** * Retrieves all cached log messages. * * @returns {any[]} Array of cached log messages * * @example * const logs = logger.getCachedLogs(); * console.log(`Collected ${logs.length} log entries`); */ getCachedLogs() { return this.logs; } /** * Deletes all cached logs. * This will clear the logs array and remove all stored log messages. * * @example * logger.deleteCachedLogs(); */ deleteCachedLogs() { this.logs = []; } /** * Retrieves cached logs as a Blob object for downloading or storage. * Works in both browser and Node.js environments. * * @returns {Blob} A Blob containing all cached logs * * @example * // In browser: Create a download link for logs * const blob = logger.getCachedLogsAsBlob(); * const url = URL.createObjectURL(blob); * const a = document.createElement('a'); * a.href = url; * a.download = 'application-logs.txt'; * a.click(); */ getCachedLogsAsBlob() { // Check if Blob is available (browser environment) if (typeof Blob !== 'undefined') { return new Blob([this.logs.join("\n")], { type: "text/plain" }); } // In Node.js environment, create a global Blob polyfill if it doesn't exist else { if (typeof global !== 'undefined' && !global.Blob) { // Simple Blob polyfill for Node.js environment class NodeBlob { /** MIME type of the blob */ type; /** Size of the blob in bytes */ size; /** Content of the blob */ content; /** * Creates a NodeBlob instance. * * @param {any[]} parts - Array of parts to concatenate * @param {object} [options={}] - Blob options */ constructor(parts, options = {}) { this.type = options.type || ''; this.content = parts.join(''); this.size = this.content.length; } /** * Returns the blob content as text. * * @returns {Promise<string>} Promise resolving to the blob content */ text() { return Promise.resolve(this.content); } /** * Returns the blob content as an ArrayBuffer. * * @returns {Promise<ArrayBuffer>} Promise resolving to the blob content as ArrayBuffer */ arrayBuffer() { return Promise.resolve(new TextEncoder().encode(this.content).buffer); } } // Add the Blob to the global object global.Blob = NodeBlob; } // Now we can use the global Blob return new global.Blob([this.logs.join("\n")], { type: "text/plain" }); } } /** * Logs a message to the console with basic formatting. * * @param {any} text - The message to log */ log(text) { console.log('%c[' + this.createDate() + '] ' + (this.sourceLog ? this.sourceLog : '') + ' : %c' + text, 'color:blue;', 'color:black;'); } /** * Logs an informational message. * Only displayed if the current log level is INFO or higher. * * @param {any} text - The message or object to log * @param {boolean} [compact=false] - Whether to format objects in compact mode * * @example * logger.info("Operation completed successfully"); * * @example * // Log an object with pretty-printing * logger.info({ user: "john", status: "active" }); * * @example * // Log an object in compact format * logger.info({ user: "john", status: "active" }, true); */ info(text, compact = false) { this.logMessage(new LogMessage(text, Type.Info, compact)); } /** * Logs an error message. * Only displayed if the current log level is ERROR or higher. * * @param {any} text - The error message or object to log * @param {boolean} [compact=false] - Whether to format objects in compact mode * * @example * logger.error("Failed to connect to the server"); * * @example * // Log an error object * try { * // Some code that might throw * } catch (error) { * logger.error(error); * } */ error(text, compact = false) { this.logMessage(new LogMessage(text, Type.Error, compact)); } /** * Logs a warning message. * Only displayed if the current log level is WARN or higher. * * @param {any} text - The warning message or object to log * @param {boolean} [compact=false] - Whether to format objects in compact mode * * @example * logger.warn("API rate limit approaching"); */ warn(text, compact = false) { this.logMessage(new LogMessage(text, Type.Warning, compact)); } /** * Logs a debug message. * Only displayed if the current log level is DEBUG or higher. * * @param {any} text - The debug message or object to log * @param {boolean} [compact=false] - Whether to format objects in compact mode * * @example * logger.debug("Variable state:", { count: 5, items: [...] }); */ debug(text, compact = false) { this.logMessage(new LogMessage(text, Type.Debug, compact)); } /** * Legacy debug method. * @deprecated Use debug() instead * @private */ debugOld(text) { console.debug('%c[' + this.createDate() + '] : %c' + text, 'color:blue;', 'color:black;'); } /** * Processes and displays a log message based on its type and the current log level. * * @param {LogMessage} msg - The log message to process */ logMessage(msg) { // Don't log anything if logging is turned off if (this._logLevel === LogLevel.OFF) return; switch (msg.type) { case Type.Info: if (this._logLevel >= LogLevel.INFO) { console.info(this.composeLogMessage(msg), 'color:blue;', 'color:' + this.getMsgLogColor(msg) + ';', 'color:blue;', 'color:black;'); } break; case Type.Error: if (this._logLevel >= LogLevel.ERROR) { console.error(this.composeLogMessage(msg), 'color:blue;', 'color:' + this.getMsgLogColor(msg) + ';', 'color:blue;', this.getMsgLogColor(msg)); } break; case Type.Warning: if (this._logLevel >= LogLevel.WARN) { console.warn(this.composeLogMessage(msg), 'color:blue;', 'color:' + this.getMsgLogColor(msg) + ';', 'color:blue;', 'color:black;'); } break; case Type.Debug: if (this._logLevel >= LogLevel.DEBUG) { console.debug(this.composeLogMessage(msg), 'color:blue;', 'color:' + this.getMsgLogColor(msg) + ';', 'color:blue;', 'color:black;'); } break; default: if (this._logLevel >= LogLevel.DEFAULT) { console.log(this.composeLogMessage(msg), 'color:blue;', 'color:' + this.getMsgLogColor(msg) + ';', 'color:blue;', 'color:black;'); } break; } } /** * Determines the color to use for a log message based on its type. * * @private * @param {LogMessage} msg - The log message * @returns {string} The CSS color value */ getMsgLogColor(msg) { switch (msg.type) { case Type.Info: return 'green'; case Type.Error: return 'red'; case Type.Warning: return 'orange'; // Fixed typo: 'orang' -> 'orange' case Type.Debug: return 'purple'; default: return 'black'; } } /** * Composes a formatted log message string with styling placeholders. * * @private * @param {LogMessage} msg - The log message to format * @returns {string} Formatted message string with CSS style placeholders */ composeLogMessage(msg) { let newMessage = '%c[' + this.createDate() + '] ' + (this.sourceLog ? this.sourceLog : '') + ' - %c' + msg.type + ' %c:: %c' + msg.text; // Store a clean version of the message (without style placeholders) in the logs array // Using replace with global regex instead of replaceAll for better compatibility MainLogger.logs.push(newMessage.replace(/%c/g, '')); return newMessage; } } /** * Default logger instance for the application. * This is the main entry point for logging and is exported as the default export. * * @example * import MainLogger from 'smyld-lib-common'; * * MainLogger.info('Application started'); * MainLogger.debug('Debug information', { version: '1.0.0' }); */ const MainLogger = new Logger(); export default MainLogger; //# sourceMappingURL=Logger.js.map