UNPKG

loglayer

Version:

A modern logging library with a fluent API for specifying log messages, metadata and errors

1,373 lines (1,361 loc) 42 kB
import { PluginCallbackType, PluginCallbackType as PluginCallbackType$1 } from "@loglayer/plugin"; import { LogLevel, LogLevel as LogLevel$1 } from "@loglayer/shared"; import { DefaultContextManager, MockContextManager } from "@loglayer/context-manager"; import { DefaultLogLevelManager, MockLogLevelManager } from "@loglayer/log-level-manager"; import { BaseTransport, LogLevelPriority, LoggerlessTransport } from "@loglayer/transport"; //#region src/PluginManager.ts const CALLBACK_LIST = [ PluginCallbackType$1.onBeforeDataOut, PluginCallbackType$1.onMetadataCalled, PluginCallbackType$1.onBeforeMessageOut, PluginCallbackType$1.transformLogLevel, PluginCallbackType$1.shouldSendToLogger, PluginCallbackType$1.onContextCalled ]; /** * A class that manages plugins and runs their callbacks. * Used by LogLayer to run plugins at various stages of the logging process. */ var PluginManager = class { idToPlugin; transformLogLevel = []; onBeforeDataOut = []; shouldSendToLogger = []; onMetadataCalled = []; onBeforeMessageOut = []; onContextCalled = []; constructor(plugins) { this.idToPlugin = {}; this.mapPlugins(plugins); this.indexPlugins(); } mapPlugins(plugins) { for (const plugin of plugins) { if (!plugin.id) plugin.id = Date.now().toString() + Math.random().toString(); if (this.idToPlugin[plugin.id]) throw new Error(`[LogLayer] Plugin with id ${plugin.id} already exists.`); plugin["registeredAt"] = Date.now(); this.idToPlugin[plugin.id] = plugin; } } indexPlugins() { this.transformLogLevel = []; this.onBeforeDataOut = []; this.shouldSendToLogger = []; this.onMetadataCalled = []; this.onBeforeMessageOut = []; this.onContextCalled = []; const pluginList = Object.values(this.idToPlugin).sort((a, b) => a.registeredAt - b.registeredAt); for (const plugin of pluginList) { if (plugin.disabled) return; for (const callback of CALLBACK_LIST) if (plugin[callback] && plugin.id) this[callback].push(plugin.id); } } hasPlugins(callbackType) { return this[callbackType].length > 0; } countPlugins(callbackType) { if (callbackType) return this[callbackType].length; return Object.keys(this.idToPlugin).length; } addPlugins(plugins) { this.mapPlugins(plugins); this.indexPlugins(); } enablePlugin(id) { const plugin = this.idToPlugin[id]; if (plugin) plugin.disabled = false; this.indexPlugins(); } disablePlugin(id) { const plugin = this.idToPlugin[id]; if (plugin) plugin.disabled = true; this.indexPlugins(); } removePlugin(id) { delete this.idToPlugin[id]; this.indexPlugins(); } /** * Runs plugins that define transformLogLevel. Returns the transformed log level or the original if no transformation is applied. * If multiple plugins transform the log level, the last one wins. */ runTransformLogLevel(params, loglayer) { let transformedLevel = null; for (const pluginId of this.transformLogLevel) { const plugin = this.idToPlugin[pluginId]; if (plugin.transformLogLevel) { const result = plugin.transformLogLevel({ data: params.data, logLevel: params.logLevel, messages: params.messages, error: params.error, metadata: params.metadata, context: params.context }, loglayer); if (result !== null && result !== void 0 && result !== false) transformedLevel = result; } } return transformedLevel !== null && transformedLevel !== void 0 && transformedLevel !== false ? transformedLevel : params.logLevel; } /** * Runs plugins that defines onBeforeDataOut. */ runOnBeforeDataOut(params, loglayer) { const initialData = { ...params }; for (const pluginId of this.onBeforeDataOut) { const plugin = this.idToPlugin[pluginId]; if (plugin.onBeforeDataOut) { const result = plugin.onBeforeDataOut({ data: initialData.data, logLevel: initialData.logLevel, error: initialData.error, metadata: initialData.metadata, context: initialData.context }, loglayer); if (result) { if (!initialData.data) initialData.data = {}; Object.assign(initialData.data, result); } } } return initialData.data; } /** * Runs plugins that define shouldSendToLogger. Any plugin that returns false will prevent the message from being sent to the transport. */ runShouldSendToLogger(params, loglayer) { return !this.shouldSendToLogger.some((pluginId) => { return !this.idToPlugin[pluginId].shouldSendToLogger?.(params, loglayer); }); } /** * Runs plugins that define onMetadataCalled. */ runOnMetadataCalled(metadata, loglayer) { let data = { ...metadata }; for (const pluginId of this.onMetadataCalled) { const result = this.idToPlugin[pluginId].onMetadataCalled?.(data, loglayer); if (result) data = result; else return null; } return data; } runOnBeforeMessageOut(params, loglayer) { let messages = [...params.messages]; for (const pluginId of this.onBeforeMessageOut) { const result = this.idToPlugin[pluginId].onBeforeMessageOut?.({ messages, logLevel: params.logLevel }, loglayer); if (result) messages = result; } return messages; } /** * Runs plugins that define onContextCalled. */ runOnContextCalled(context, loglayer) { let data = { ...context }; for (const pluginId of this.onContextCalled) { const result = this.idToPlugin[pluginId].onContextCalled?.(data, loglayer); if (result) data = result; else return null; } return data; } }; //#endregion //#region src/LogLayer.ts /** * Wraps around a logging framework to provide convenience methods that allow * developers to programmatically specify their errors and metadata along with * a message in a consistent fashion. */ var LogLayer = class LogLayer { pluginManager; idToTransport; hasMultipleTransports; singleTransport; contextManager; logLevelManager; /** * The configuration object used to initialize the logger. * This is for internal use only and should not be modified directly. */ _config; constructor(config) { this._config = { ...config, enabled: config.enabled ?? true }; this.contextManager = new DefaultContextManager(); this.logLevelManager = new DefaultLogLevelManager(); if (!this._config.enabled) this.disableLogging(); this.pluginManager = new PluginManager([...config.plugins || [], ...mixinRegistry.pluginsToInit]); if (!this._config.errorFieldName) this._config.errorFieldName = "err"; if (!this._config.copyMsgOnOnlyError) this._config.copyMsgOnOnlyError = false; this._initializeTransports(this._config.transport); if (mixinRegistry.logLayerHandlers.length > 0) mixinRegistry.logLayerHandlers.forEach((mixin) => { if (mixin.onConstruct) mixin.onConstruct(this, config); }); } /** * Sets the context manager to use for managing context data. */ withContextManager(contextManager) { if (this.contextManager && typeof this.contextManager[Symbol.dispose] === "function") this.contextManager[Symbol.dispose](); this.contextManager = contextManager; return this; } /** * Returns the context manager instance being used. */ getContextManager() { return this.contextManager; } /** * Sets the log level manager to use for managing log levels. */ withLogLevelManager(logLevelManager) { if (this.logLevelManager && typeof this.logLevelManager[Symbol.dispose] === "function") this.logLevelManager[Symbol.dispose](); this.logLevelManager = logLevelManager; return this; } /** * Returns the log level manager instance being used. */ getLogLevelManager() { return this.logLevelManager; } /** * Returns the configuration object used to initialize the logger. */ getConfig() { return this._config; } _initializeTransports(transports) { if (this.idToTransport) { for (const id in this.idToTransport) if (this.idToTransport[id] && typeof this.idToTransport[id][Symbol.dispose] === "function") this.idToTransport[id][Symbol.dispose](); } this.hasMultipleTransports = Array.isArray(transports) && transports.length > 1; this.singleTransport = this.hasMultipleTransports ? null : Array.isArray(transports) ? transports[0] : transports; if (Array.isArray(transports)) this.idToTransport = transports.reduce((acc, transport) => { acc[transport.id] = transport; return acc; }, {}); else this.idToTransport = { [transports.id]: transports }; } /** * Calls child() and sets the prefix to be included with every log message. * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#message-prefixing | Message Prefixing Docs} */ withPrefix(prefix) { const logger = this.child(); logger._config.prefix = prefix; return logger; } /** * Appends context data which will be included with * every log entry. * * Passing in an empty value / object will *not* clear the context. * * To clear the context, use {@link https://loglayer.dev/logging-api/context.html#clearing-context | clearContext()}. * * @see {@link https://loglayer.dev/logging-api/context.html | Context Docs} */ withContext(context) { let updatedContext = context; if (!context) { if (this._config.consoleDebug) console.debug("[LogLayer] withContext was called with no context; dropping."); return this; } if (this.pluginManager.hasPlugins(PluginCallbackType$1.onContextCalled)) { updatedContext = this.pluginManager.runOnContextCalled(context, this); if (!updatedContext) { if (this._config.consoleDebug) console.debug("[LogLayer] Context was dropped due to plugin returning falsy value."); return this; } } this.contextManager.appendContext(updatedContext); return this; } /** * Clears the context data. If keys are provided, only those keys will be removed. * If no keys are provided, all context data will be cleared. */ clearContext(keys) { this.contextManager.clearContext(keys); return this; } getContext() { return this.contextManager.getContext(); } /** * Add additional plugins. * * @see {@link https://loglayer.dev/plugins/ | Plugins Docs} */ addPlugins(plugins) { this.pluginManager.addPlugins(plugins); } /** * Enables a plugin by id. * * @see {@link https://loglayer.dev/plugins/ | Plugins Docs} */ enablePlugin(id) { this.pluginManager.enablePlugin(id); } /** * Disables a plugin by id. * * @see {@link https://loglayer.dev/plugins/ | Plugins Docs} */ disablePlugin(id) { this.pluginManager.disablePlugin(id); } /** * Removes a plugin by id. * * @see {@link https://loglayer.dev/plugins/ | Plugins Docs} */ removePlugin(id) { this.pluginManager.removePlugin(id); } /** * Specifies metadata to include with the log message * * @see {@link https://loglayer.dev/logging-api/metadata.html | Metadata Docs} */ withMetadata(metadata) { return new LogBuilder(this).withMetadata(metadata); } /** * Specifies an Error to include with the log message * * @see {@link https://loglayer.dev/logging-api/error-handling.html | Error Handling Docs} */ withError(error) { return new LogBuilder(this).withError(error); } /** * Creates a new instance of LogLayer but with the initialization * configuration and context copied over. * * @see {@link https://loglayer.dev/logging-api/child-loggers.html | Child Logging Docs} */ child() { const childLogger = new LogLayer({ ...this._config, transport: Array.isArray(this._config.transport) ? [...this._config.transport] : this._config.transport }).withPluginManager(this.pluginManager).withContextManager(this.contextManager.clone()).withLogLevelManager(this.logLevelManager.clone()); this.contextManager.onChildLoggerCreated({ parentContextManager: this.contextManager, childContextManager: childLogger.contextManager, parentLogger: this, childLogger }); this.logLevelManager.onChildLoggerCreated({ parentLogLevelManager: this.logLevelManager, childLogLevelManager: childLogger.logLevelManager, parentLogger: this, childLogger }); return childLogger; } /** * Replaces all existing transports with new ones. * * Transport changes only affect the current logger instance. Child loggers * created before the change will retain their original transports, and * parent loggers are not affected when a child modifies its transports. * * @see {@link https://loglayer.dev/logging-api/transport-management.html | Transport Management Docs} */ withFreshTransports(transports) { this._config.transport = transports; this._initializeTransports(transports); return this; } /** * Adds one or more transports to the existing transports. * If a transport with the same ID already exists, it will be replaced. * * Transport changes only affect the current logger instance. Child loggers * created before the change will retain their original transports, and * parent loggers are not affected when a child modifies its transports. * * @see {@link https://loglayer.dev/logging-api/transport-management.html | Transport Management Docs} */ addTransport(transports) { const newTransports = Array.isArray(transports) ? transports : [transports]; const existingTransports = Array.isArray(this._config.transport) ? this._config.transport : [this._config.transport]; const newTransportIds = new Set(newTransports.map((t) => t.id)); for (const transport of newTransports) { const existingTransport = this.idToTransport[transport.id]; if (existingTransport && typeof existingTransport[Symbol.dispose] === "function") existingTransport[Symbol.dispose](); } const allTransports = [...existingTransports.filter((t) => !newTransportIds.has(t.id)), ...newTransports]; this._config.transport = allTransports; for (const transport of newTransports) this.idToTransport[transport.id] = transport; this.hasMultipleTransports = allTransports.length > 1; this.singleTransport = this.hasMultipleTransports ? null : allTransports[0]; return this; } /** * Removes a transport by its ID. * * Transport changes only affect the current logger instance. Child loggers * created before the change will retain their original transports, and * parent loggers are not affected when a child modifies its transports. * * @returns true if the transport was found and removed, false otherwise. * * @see {@link https://loglayer.dev/logging-api/transport-management.html | Transport Management Docs} */ removeTransport(id) { const transport = this.idToTransport[id]; if (!transport) return false; if (typeof transport[Symbol.dispose] === "function") transport[Symbol.dispose](); delete this.idToTransport[id]; const remainingTransports = (Array.isArray(this._config.transport) ? this._config.transport : [this._config.transport]).filter((t) => t.id !== id); this._config.transport = remainingTransports.length === 1 ? remainingTransports[0] : remainingTransports; this.hasMultipleTransports = remainingTransports.length > 1; this.singleTransport = this.hasMultipleTransports ? null : remainingTransports[0] || null; return true; } /** * Replaces all existing plugins with new ones. * * When used with child loggers, it only affects the current logger instance * and does not modify the parent's plugins. * * @see {@link https://loglayer.dev/plugins/ | Plugins Docs} */ withFreshPlugins(plugins) { this._config.plugins = plugins; this.pluginManager = new PluginManager(plugins); return this; } withPluginManager(pluginManager) { this.pluginManager = pluginManager; return this; } /** * Logs only the error object without a log message * * @see {@link https://loglayer.dev/logging-api/error-handling.html | Error Handling Docs} */ errorOnly(error, opts) { const logLevel = opts?.logLevel || LogLevel$1.error; if (!this.isLevelEnabled(logLevel)) return; const { copyMsgOnOnlyError } = this._config; const formatLogConf = { logLevel, err: error }; if ((copyMsgOnOnlyError && opts?.copyMsg !== false || opts?.copyMsg === true) && error?.message) formatLogConf.params = [error.message]; this._formatLog(formatLogConf); } /** * Logs only metadata without a log message * * @see {@link https://loglayer.dev/logging-api/metadata.html | Metadata Docs} */ metadataOnly(metadata, logLevel = LogLevel$1.info) { if (!this.isLevelEnabled(logLevel)) return; const { muteMetadata, consoleDebug } = this._config; if (muteMetadata) return; if (!metadata) { if (consoleDebug) console.debug("[LogLayer] metadataOnly was called with no metadata; dropping."); return; } let data = metadata; if (this.pluginManager.hasPlugins(PluginCallbackType$1.onMetadataCalled)) { data = this.pluginManager.runOnMetadataCalled(metadata, this); if (!data) { if (consoleDebug) console.debug("[LogLayer] Metadata was dropped due to plugin returning falsy value."); return; } } const config = { logLevel, metadata: data }; this._formatLog(config); } /** * Sends a log message to the logging library under an info log level. * * The logging library may or may not support multiple message parameters and only * the first parameter would be used. * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ info(...messages) { if (!this.isLevelEnabled(LogLevel$1.info)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.info, params: messages }); } /** * Sends a log message to the logging library under the warn log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ warn(...messages) { if (!this.isLevelEnabled(LogLevel$1.warn)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.warn, params: messages }); } /** * Sends a log message to the logging library under the error log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ error(...messages) { if (!this.isLevelEnabled(LogLevel$1.error)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.error, params: messages }); } /** * Sends a log message to the logging library under the debug log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ debug(...messages) { if (!this.isLevelEnabled(LogLevel$1.debug)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.debug, params: messages }); } /** * Sends a log message to the logging library under the trace log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ trace(...messages) { if (!this.isLevelEnabled(LogLevel$1.trace)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.trace, params: messages }); } /** * Sends a log message to the logging library under the fatal log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ fatal(...messages) { if (!this.isLevelEnabled(LogLevel$1.fatal)) return; this._formatMessage(messages); this._formatLog({ logLevel: LogLevel$1.fatal, params: messages }); } /** * Logs a raw log entry with complete control over all log parameters. * * This method allows you to bypass the normal LogLayer API and directly specify * all aspects of a log entry including log level, messages, metadata, and error. * It's useful for scenarios where you need to log structured data that doesn't * fit the standard LogLayer patterns, or when integrating with external logging * systems that provide pre-formatted log entries. * * The raw entry will still go through all LogLayer processing. * * @see {@link https://loglayer.dev/logging-api/basic-logging.html | Basic Logging Docs} */ raw(logEntry) { if (!this.isLevelEnabled(logEntry.logLevel)) return; const formatLogConf = { logLevel: logEntry.logLevel, params: logEntry.messages, metadata: logEntry.metadata, err: logEntry.error, context: logEntry.context }; this._formatMessage(logEntry.messages); this._formatLog(formatLogConf); } /** * All logging inputs are dropped and stops sending logs to the logging library. * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#enabling-disabling-logging | Enabling/Disabling Logging Docs} */ disableLogging() { this.logLevelManager.disableLogging(); return this; } /** * Enable sending logs to the logging library. * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#enabling-disabling-logging | Enabling/Disabling Logging Docs} */ enableLogging() { this.logLevelManager.enableLogging(); return this; } /** * Disables inclusion of context data in the print * * @see {@link https://loglayer.dev/logging-api/context.html#managing-context | Managing Context Docs} */ muteContext() { this._config.muteContext = true; return this; } /** * Enables inclusion of context data in the print * * @see {@link https://loglayer.dev/logging-api/context.html#managing-context | Managing Context Docs} */ unMuteContext() { this._config.muteContext = false; return this; } /** * Disables inclusion of metadata in the print * * @see {@link https://loglayer.dev/logging-api/metadata.html#controlling-metadata-output | Controlling Metadata Output Docs} */ muteMetadata() { this._config.muteMetadata = true; return this; } /** * Enables inclusion of metadata in the print * * @see {@link https://loglayer.dev/logging-api/metadata.html#controlling-metadata-output | Controlling Metadata Output Docs} */ unMuteMetadata() { this._config.muteMetadata = false; return this; } /** * Enables a specific log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#enabling-disabling-logging | Enabling/Disabling Logging Docs} */ enableIndividualLevel(logLevel) { this.logLevelManager.enableIndividualLevel(logLevel); return this; } /** * Disables a specific log level * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#enabling-disabling-logging | Enabling/Disabling Logging Docs} */ disableIndividualLevel(logLevel) { this.logLevelManager.disableIndividualLevel(logLevel); return this; } /** * Sets the minimum log level to be used by the logger. Only messages with * this level or higher severity will be logged. * * For example, if you setLevel(LogLevel.warn), this will: * Enable: * - warn * - error * - fatal * Disable: * - info * - debug * - trace * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#enabling-disabling-logging | Enabling/Disabling Logging Docs} */ setLevel(logLevel) { this.logLevelManager.setLevel(logLevel); return this; } /** * Checks if a specific log level is enabled * * @see {@link https://loglayer.dev/logging-api/basic-logging.html#checking-if-a-log-level-is-enabled | Checking if a Log Level is Enabled Docs} */ isLevelEnabled(logLevel) { return this.logLevelManager.isLevelEnabled(logLevel); } formatContext(context) { const { contextFieldName, muteContext } = this._config; if (context && Object.keys(context).length > 0 && !muteContext) { if (contextFieldName) return { [contextFieldName]: { ...context } }; return { ...context }; } return {}; } formatMetadata(data = null) { const { metadataFieldName, muteMetadata } = this._config; if (data && !muteMetadata) { if (metadataFieldName) return { [metadataFieldName]: { ...data } }; return { ...data }; } return {}; } /** * Returns a logger instance for a specific transport * * @see {@link https://loglayer.dev/logging-api/transport-management.html | Transport Management Docs} */ getLoggerInstance(id) { const transport = this.idToTransport[id]; if (!transport) return; return transport.getLoggerInstance(); } _formatMessage(messages = []) { const { prefix } = this._config; if (prefix && typeof messages[0] === "string") messages[0] = `${prefix} ${messages[0]}`; } _formatLog({ logLevel, params = [], metadata = null, err, context = null }) { const { errorSerializer, errorFieldInMetadata, muteContext, contextFieldName, metadataFieldName, errorFieldName } = this._config; const contextData = context !== null ? context : this.contextManager.getContext(); let hasObjData = !!metadata || (muteContext ? false : context !== null ? Object.keys(context).length > 0 : this.contextManager.hasContextData()); let d = {}; if (hasObjData) if (contextFieldName && contextFieldName === metadataFieldName) { const formattedContextData = this.formatContext(contextData)[contextFieldName]; const updatedMetadata = this.formatMetadata(metadata)[metadataFieldName]; d = { [contextFieldName]: { ...formattedContextData, ...updatedMetadata } }; } else d = { ...this.formatContext(contextData), ...this.formatMetadata(metadata) }; if (err) { const serializedError = errorSerializer ? errorSerializer(err) : err; if (errorFieldInMetadata && metadata && metadataFieldName) if (d?.[metadataFieldName]) d[metadataFieldName][errorFieldName] = serializedError; else d = { ...d, [metadataFieldName]: { [errorFieldName]: serializedError } }; else if (errorFieldInMetadata && !metadata && metadataFieldName) d = { ...d, [metadataFieldName]: { [errorFieldName]: serializedError } }; else d = { ...d, [errorFieldName]: serializedError }; hasObjData = true; } if (this.pluginManager.hasPlugins(PluginCallbackType$1.onBeforeDataOut)) { d = this.pluginManager.runOnBeforeDataOut({ data: hasObjData ? d : void 0, logLevel, error: err, metadata, context: contextData }, this); if (d && !hasObjData) hasObjData = true; } if (this.pluginManager.hasPlugins(PluginCallbackType$1.onBeforeMessageOut)) params = this.pluginManager.runOnBeforeMessageOut({ messages: [...params], logLevel }, this); if (this.pluginManager.hasPlugins(PluginCallbackType$1.transformLogLevel)) logLevel = this.pluginManager.runTransformLogLevel({ data: hasObjData ? d : void 0, logLevel, messages: [...params], error: err, metadata, context: contextData }, this); if (this.hasMultipleTransports) { const transportPromises = this._config.transport.filter((transport) => transport.enabled).map(async (transport) => { const currentLogLevel = logLevel; if (this.pluginManager.hasPlugins(PluginCallbackType$1.shouldSendToLogger)) { if (!this.pluginManager.runShouldSendToLogger({ messages: [...params], data: hasObjData ? d : void 0, logLevel: currentLogLevel, transportId: transport.id, error: err, metadata, context: contextData }, this)) return; } return transport._sendToLogger({ logLevel: currentLogLevel, messages: [...params], data: hasObjData ? d : void 0, hasData: hasObjData, error: err, metadata, context: contextData }); }); Promise.all(transportPromises).catch((err$1) => { if (this._config.consoleDebug) console.error("[LogLayer] Error executing transports:", err$1); }); } else { if (!this.singleTransport?.enabled) return; if (this.pluginManager.hasPlugins(PluginCallbackType$1.shouldSendToLogger)) { if (!this.pluginManager.runShouldSendToLogger({ messages: [...params], data: hasObjData ? d : void 0, logLevel, transportId: this.singleTransport.id, error: err, metadata, context: contextData }, this)) return; } this.singleTransport._sendToLogger({ logLevel, messages: [...params], data: hasObjData ? d : void 0, hasData: hasObjData, error: err, metadata, context: contextData }); } } }; //#endregion //#region src/MockLogBuilder.ts /** * A mock implementation of the ILogBuilder interface that does nothing. * Useful for writing unit tests. */ var MockLogBuilder = class { debug(..._messages) {} error(..._messages) {} info(..._messages) {} trace(..._messages) {} warn(..._messages) {} fatal(..._messages) {} enableLogging() { return this; } disableLogging() { return this; } withMetadata(_metadata) { return this; } withError(_error) { return this; } }; //#endregion //#region src/MockLogLayer.ts /* istanbul ignore file */ /** * A mock implementation of the ILogLayer interface that does nothing. * Useful for writing unit tests. * MockLogLayer implements both ILogLayer and ILogBuilder for simplicity in testing. */ var MockLogLayer = class { mockLogBuilder = new MockLogBuilder(); mockContextManager = new MockContextManager(); mockLogLevelManager = new MockLogLevelManager(); info(..._messages) {} warn(..._messages) {} error(..._messages) {} debug(..._messages) {} trace(..._messages) {} fatal(..._messages) {} raw(_rawEntry) {} getLoggerInstance(_id) {} errorOnly(_error, _opts) {} metadataOnly(_metadata, _logLevel) {} addPlugins(_plugins) {} removePlugin(_id) {} enablePlugin(_id) {} disablePlugin(_id) {} withPrefix(_prefix) { return this; } withContext(_context) { return this; } withError(_error) { return this.mockLogBuilder.withError(_error); } withMetadata(_metadata) { return this.mockLogBuilder.withMetadata(_metadata); } getContext() { return {}; } clearContext(_keys) { return this; } enableLogging() { return this; } disableLogging() { return this; } child() { return this; } muteContext() { return this; } unMuteContext() { return this; } muteMetadata() { return this; } unMuteMetadata() { return this; } withFreshTransports(_transports) { return this; } addTransport(_transports) { return this; } removeTransport(_id) { return true; } withFreshPlugins(_plugins) { return this; } withContextManager(_contextManager) { return this; } getContextManager() { return this.mockContextManager; } withLogLevelManager(_logLevelManager) { return this; } getLogLevelManager() { return this.mockLogLevelManager; } getConfig() { return {}; } /** * Sets the mock log builder to use for testing. */ setMockLogBuilder(mockLogBuilder) { this.mockLogBuilder = mockLogBuilder; } enableIndividualLevel(_logLevel) { return this; } disableIndividualLevel(_logLevel) { return this; } setLevel(_logLevel) { return this; } isLevelEnabled(_logLevel) { return true; } /** * Returns the mock log builder used for testing. */ getMockLogBuilder() { return this.mockLogBuilder; } /** * Resets the mock log builder to a new instance of MockLogBuilder. */ resetMockLogBuilder() { this.mockLogBuilder = new MockLogBuilder(); } }; //#endregion //#region src/types/mixin.types.ts /** * The class that the mixin extends */ let LogLayerMixinAugmentType = /* @__PURE__ */ function(LogLayerMixinAugmentType$1) { /** * Mixin extends the LogBuilder prototype */ LogLayerMixinAugmentType$1["LogBuilder"] = "LogBuilder"; /** * Mixin extends the LogLayer prototype */ LogLayerMixinAugmentType$1["LogLayer"] = "LogLayer"; return LogLayerMixinAugmentType$1; }({}); //#endregion //#region src/mixins.ts const mixinRegistry = { logLayerHandlers: [], pluginsToInit: [], logBuilderHandlers: [] }; /** * Adds one or more mixins to LogLayer. * @param mixin - The mixin(s) to register. Can be a single mixin registration or an array of mixin registrations. */ function useLogLayerMixin(mixin) { const mixins = Array.isArray(mixin) ? mixin : [mixin]; for (const mixinRegistration of mixins) { if (mixinRegistration.pluginsToAdd) for (const plugin of mixinRegistration.pluginsToAdd) mixinRegistry.pluginsToInit.push(plugin); for (const mixinToAdd of mixinRegistration.mixinsToAdd) switch (mixinToAdd.augmentationType) { case LogLayerMixinAugmentType.LogLayer: if (mixinToAdd.onConstruct) mixinRegistry.logLayerHandlers.push({ onConstruct: mixinToAdd.onConstruct }); mixinToAdd.augment(LogLayer.prototype); mixinToAdd.augmentMock(MockLogLayer.prototype); break; case LogLayerMixinAugmentType.LogBuilder: if (mixinToAdd.onConstruct) mixinRegistry.logBuilderHandlers.push({ onConstruct: mixinToAdd.onConstruct }); mixinToAdd.augment(LogBuilder.prototype); mixinToAdd.augmentMock(MockLogBuilder.prototype); break; } } } //#endregion //#region src/LogBuilder.ts /** * A class that contains methods to specify log metadata and an error and assembles * it to form a data object that can be passed into the transport. */ var LogBuilder = class { err; metadata; structuredLogger; hasMetadata; pluginManager; constructor(structuredLogger) { this.err = null; this.metadata = {}; this.structuredLogger = structuredLogger; this.hasMetadata = false; this.pluginManager = structuredLogger["pluginManager"]; if (mixinRegistry.logBuilderHandlers.length > 0) mixinRegistry.logBuilderHandlers.forEach((mixin) => { if (mixin.onConstruct) mixin.onConstruct(this, structuredLogger); }); } /** * Specifies metadata to include with the log message * * @see {@link https://loglayer.dev/logging-api/metadata.html | Metadata Docs} */ withMetadata(metadata) { const { pluginManager, structuredLogger: { _config: { consoleDebug } } } = this; if (!metadata) { if (consoleDebug) console.debug("[LogLayer] withMetadata was called with no metadata; dropping."); return this; } let data = metadata; if (pluginManager.hasPlugins(PluginCallbackType$1.onMetadataCalled)) { data = pluginManager.runOnMetadataCalled(metadata, this.structuredLogger); if (!data) { if (consoleDebug) console.debug("[LogLayer] Metadata was dropped due to plugin returning falsy value."); return this; } } this.metadata = { ...this.metadata, ...data }; this.hasMetadata = true; return this; } /** * Specifies an Error to include with the log message * * @see {@link https://loglayer.dev/logging-api/error-handling.html | Error Handling Docs} */ withError(error) { this.err = error; return this; } /** * Sends a log message to the logging library under an info log level. */ info(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.info)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.info, messages); } /** * Sends a log message to the logging library under the warn log level */ warn(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.warn)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.warn, messages); } /** * Sends a log message to the logging library under the error log level */ error(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.error)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.error, messages); } /** * Sends a log message to the logging library under the debug log level * * The logging library may or may not support multiple message parameters and only * the first parameter would be used. */ debug(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.debug)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.debug, messages); } /** * Sends a log message to the logging library under the trace log level * * The logging library may or may not support multiple message parameters and only * the first parameter would be used. */ trace(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.trace)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.trace, messages); } /** * Sends a log message to the logging library under the fatal log level * * The logging library may or may not support multiple message parameters and only * the first parameter would be used. */ fatal(...messages) { if (!this.structuredLogger.isLevelEnabled(LogLevel$1.fatal)) return; this.structuredLogger._formatMessage(messages); this.formatLog(LogLevel$1.fatal, messages); } /** * All logging inputs are dropped and stops sending logs to the logging library. */ disableLogging() { this.structuredLogger.disableLogging(); return this; } /** * Enable sending logs to the logging library. */ enableLogging() { this.structuredLogger.enableLogging(); return this; } formatLog(logLevel, params) { const { muteMetadata } = this.structuredLogger._config; const hasData = muteMetadata ? false : this.hasMetadata; this.structuredLogger._formatLog({ logLevel, params, metadata: hasData ? this.metadata : null, err: this.err }); } }; //#endregion //#region src/TestLoggingLibrary.ts /** * A test logging library that can be used to test LogLayer plugins and transports. * It is meant to be used with the TestTransport. */ var TestLoggingLibrary = class { /** * An array of log lines that have been logged. */ lines; constructor() { this.lines = []; } info(...params) { this.addLine(LogLevel$1.info, params); } warn(...params) { this.addLine(LogLevel$1.warn, params); } error(...params) { this.addLine(LogLevel$1.error, params); } debug(...params) { this.addLine(LogLevel$1.debug, params); } trace(...params) { this.addLine(LogLevel$1.trace, params); } fatal(...params) { this.addLine(LogLevel$1.fatal, params); } addLine(logLevel, params) { this.lines.push({ level: logLevel, data: params }); } /** * Get the last line that was logged. Returns null if no lines have been logged. */ getLastLine() { if (!this.lines.length) return null; return this.lines[this.lines.length - 1]; } /** * Pops the last line that was logged. Returns null if no lines have been logged. */ popLine() { return this.lines.pop(); } /** * Clears all lines that have been logged. */ clearLines() { this.lines = []; } }; //#endregion //#region src/transports/BlankTransport.ts /** * A transport that allows users to quickly create custom transports by providing their own `shipToLogger` function. * * This is useful for creating simple custom transports without having to create a completely new transport class from scratch. * * @example * ```typescript * import { LogLayer, BlankTransport } from 'loglayer'; * * const log = new LogLayer({ * transport: new BlankTransport({ * shipToLogger: ({ logLevel, messages, data, hasData }) => { * // Your custom logging logic here * console.log(`[${logLevel}]`, ...messages, data && hasData ? data : ''); * return messages; * } * }) * }); * ``` */ var BlankTransport = class extends LoggerlessTransport { shipToLoggerFn; constructor(config) { super(config); this.shipToLoggerFn = config.shipToLogger; } shipToLogger(params) { return this.shipToLoggerFn(params); } }; //#endregion //#region src/transports/ConsoleTransport.ts /** * Transport for use with a console logger. */ var ConsoleTransport = class extends BaseTransport { appendObjectData; logLevel; messageField; dateField; levelField; dateFn; levelFn; stringify; messageFn; constructor(params) { super(params); this.appendObjectData = params.appendObjectData || false; this.logLevel = params.level ?? "trace"; this.messageField = params.messageField; this.dateField = params.dateField; this.levelField = params.levelField; this.dateFn = params.dateFn; this.levelFn = params.levelFn; this.stringify = params.stringify || false; this.messageFn = params.messageFn; } shipToLogger(params) { const { logLevel, data, hasData } = params; let { messages } = params; if (LogLevelPriority[logLevel] < LogLevelPriority[this.logLevel]) return; if (this.messageFn) messages = [this.messageFn(params)]; if (this.messageField) { const messageText = messages.join(" "); const logObject = { ...data || {}, [this.messageField]: messageText, ...this.dateField && { [this.dateField]: this.dateFn ? this.dateFn() : (/* @__PURE__ */ new Date()).toISOString() }, ...this.levelField && { [this.levelField]: this.levelFn ? this.levelFn(logLevel) : logLevel } }; messages = [this.stringify ? JSON.stringify(logObject) : logObject]; } else if (this.dateField || this.levelField) { const logObject = { ...data || {}, ...this.dateField && { [this.dateField]: this.dateFn ? this.dateFn() : (/* @__PURE__ */ new Date()).toISOString() }, ...this.levelField && { [this.levelField]: this.levelFn ? this.levelFn(logLevel) : logLevel } }; if (this.stringify) messages.push(JSON.stringify(logObject)); else messages.push(logObject); } else if (data && hasData) if (this.appendObjectData) messages.push(data); else messages.unshift(data); switch (logLevel) { case LogLevel$1.info: this.logger.info(...messages); break; case LogLevel$1.warn: this.logger.warn(...messages); break; case LogLevel$1.error: this.logger.error(...messages); break; case LogLevel$1.trace: this.logger.trace(...messages); break; case LogLevel$1.debug: this.logger.debug(...messages); break; case LogLevel$1.fatal: this.logger.error(...messages); break; } return messages; } }; //#endregion //#region src/transports/TestTransport.ts /** * Transport used for testing purposes. */ var TestTransport = class extends BaseTransport { shipToLogger({ logLevel, messages, data, hasData }) { if (data && hasData) messages.unshift(data); switch (logLevel) { case LogLevel$1.info: this.logger.info(...messages); break; case LogLevel$1.warn: this.logger.warn(...messages); break; case LogLevel$1.error: this.logger.error(...messages); break; case LogLevel$1.trace: this.logger.trace(...messages); break; case LogLevel$1.debug: this.logger.debug(...messages); break; case LogLevel$1.fatal: this.logger.fatal(...messages); break; } return messages; } }; //#endregion export { BlankTransport, ConsoleTransport, LogBuilder, LogLayer, LogLayerMixinAugmentType, LogLevel, MockLogBuilder, MockLogLayer, PluginCallbackType, TestLoggingLibrary, TestTransport, useLogLayerMixin };