UNPKG

@ngworker/lumberjack

Version:

Lumberjack is a versatile Angular logging library, specifically designed to be extended and customized

764 lines (734 loc) 27.2 kB
import * as i0 from '@angular/core'; import { InjectionToken, isDevMode, Injectable, inject } from '@angular/core'; /** * The Lumberjack configuration token is used internally by `provideLumberjack` function. * and various Lumberjack services. */ const lumberjackConfigToken = new InjectionToken('__LUMBERJACK_CONFIG__'); /** * The Lumberjack log driver configuration token is used internally by * `provideLumberjack` function, various internal Lumberjack services, and log drivers. */ const lumberjackLogDriverConfigToken = new InjectionToken('__LUMBERJACK_LOG_DRIVER_CONFIG__'); const isProductionEnvironmentToken = new InjectionToken('__PRODUCTION_MODE_DETECTION__', { factory: () => !isDevMode(), providedIn: 'root', }); function utcTimestampFor(unixEpochTicks) { return new Date(unixEpochTicks).toISOString(); } function lumberjackFormatLog({ scope, createdAt: timestamp, level, message, }) { const formattedScope = scope ? ` [${scope}]` : ''; return `${level} ${utcTimestampFor(timestamp)}${formattedScope} ${message}`; } /** * Used by Lumberjack to timestamp logs at their creation time. * * Can be overridden for testing purposes. */ class LumberjackTimeService { /** * Return the current date-time as Unix epoch ticks in milliseconds. */ getUnixEpochTicks() { return Date.now(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackTimeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackTimeService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackTimeService, decorators: [{ type: Injectable }] }); class LumberjackLogFormatter { #config = inject(lumberjackConfigToken); #time = inject(LumberjackTimeService); formatLog(log) { const { format } = this.#config; let result; try { result = { log, formattedLog: format(log), }; } catch (formattingError) { const formattingErrorLog = this.#createFormattingErrorLog(formattingError, log); const formattedFormattingError = this.#formatFormattingError(formattingErrorLog); result = { log: formattingErrorLog, formattedLog: formattedFormattingError, }; } return result; } #createFormattingErrorLog(formatError, log) { const formattingErrorMessage = formatError.message ?? String(formatError); return { scope: 'LumberjackLogFormattingError', createdAt: this.#time.getUnixEpochTicks(), level: 'error', message: `Could not format message "${log.message}". Error: "${formattingErrorMessage}"`, payload: undefined, }; } #formatFormattingError(errorEntry) { const { format } = this.#config; let errorMessage = ''; try { errorMessage = format(errorEntry); } catch { errorMessage = lumberjackFormatLog(errorEntry); } return errorMessage; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFormatter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFormatter }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFormatter, decorators: [{ type: Injectable }] }); /** * Critical logging strategy for a log driver. * * @param driver The log driver. * @param driverLog A critical log driver log. */ function criticalLogDriverLoggingStrategy(driver, driverLog) { driver.logCritical(driverLog); } /** * Debug logging strategy for a log driver. * * @param driver The log driver. * @param driverLog A debug log driver log. */ function debugLogDriverLoggingStrategy(driver, driverLog) { driver.logDebug(driverLog); } /** * Error logging strategy for a log driver. * * @param driver The log driver. * @param driverLog An error log driver log. */ function errorLogDriverLoggingStrategy(driver, driverLog) { driver.logError(driverLog); } /** * Info logging strategy for a log driver. * * @param driver The log driver. * @param driverLog An info log driver log. */ function infoLogDriverLoggingStrategy(driver, driverLog) { driver.logInfo(driverLog); } /** * Trace logging strategy for a log driver. * * @param driver The log driver. * @param driverLog A trace log driver log. */ function traceLogDriverLoggingStrategy(driver, driverLog) { driver.logTrace(driverLog); } /** * Warning logging strategy for a log driver. * * @param driver The log driver. * @param driverLog A warning log driver log. */ function warningLogDriverLoggingStrategy(driver, driverLog) { driver.logWarning(driverLog); } /** * A context for a log driver logging strategy. */ class LumberjackLogDriverLogger { /** * A record of logging strategies for each log level. */ #loggingStrategies = { ['critical']: criticalLogDriverLoggingStrategy, ['debug']: debugLogDriverLoggingStrategy, ['error']: errorLogDriverLoggingStrategy, ['info']: infoLogDriverLoggingStrategy, ['trace']: traceLogDriverLoggingStrategy, ['warn']: warningLogDriverLoggingStrategy, }; /** * Log the specified log to the log driver. * * Forwards control to the log driver logging strategy corresponding to the * level of the specified log driver log. * * @param driver The log driver. * @param driverLog A log driver log. */ log(driver, driverLog) { this.#loggingStrategies[driverLog.log.level](driver, driverLog); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogDriverLogger, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogDriverLogger }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogDriverLogger, decorators: [{ type: Injectable }] }); /** * Builder for a log with the specified log level and message. * * Use this to create a log before passing it to `LumberjackService`. */ class LumberjackLogBuilder { #payload; #scope; #time; #level; #message; /** * Create a log builder with the specified log level and message. * * @param time Pass the `LumberjackTimeService`. Used for timestamping the log. * @param level The log level. * @param message The log message. */ constructor(time, level, message) { this.#time = time; this.#level = level; this.#message = message; } /** * Create a log with the specified properties and timestamp it. * * @param payloadArg Optional dynamic payload. */ build(...payloadArg) { return { level: this.#level, message: this.#message, scope: this.#scope, createdAt: this.#time.getUnixEpochTicks(), payload: (payloadArg[0] ?? this.#payload), }; } /** * Optionally add a static payload to the log. */ withPayload(...payloadArg) { this.#payload = payloadArg[0]; return this; } /** * Add a scope to the log. */ withScope(scope) { this.#scope = scope; return this; } } /** * Factory for a log builder with the specified log level and message. * * Use this to create a log before passing it to `LumberjackService`. * * Wraps `LumberjackLogBuilder`, supports dependency injection, and allows reuse * so that we don't have to new up log builders and pass `LumberjackTimeService` * to them. */ class LumberjackLogFactory { #time = inject(LumberjackTimeService); /** * Create a log builder for a critical log with the specified message. */ createCriticalLog(message) { return new LumberjackLogBuilder(this.#time, 'critical', message); } /** * Create a log builder for a debug log with the specified message. */ createDebugLog(message) { return new LumberjackLogBuilder(this.#time, 'debug', message); } /** * Create a log builder for an error log with the specified message. */ createErrorLog(message) { return new LumberjackLogBuilder(this.#time, 'error', message); } /** * Create a log builder for an info log with the specified message. */ createInfoLog(message) { return new LumberjackLogBuilder(this.#time, 'info', message); } /** * Create a log builder for a trace log with the specified message. */ createTraceLog(message) { return new LumberjackLogBuilder(this.#time, 'trace', message); } /** * Create a log builder for a warning log with the specified message. */ createWarningLog(message) { return new LumberjackLogBuilder(this.#time, 'warn', message); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFactory }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogFactory, decorators: [{ type: Injectable }] }); /** * Create a text representation of the specified log driver error. */ function formatLogDriverError({ error, formattedLog, log, logDriver, }) { const thrownErrorMessage = error.message ?? String(error); const payloadMessage = log.payload ? ` with payload "${JSON.stringify(log.payload)}"` : ''; return `Could not log message "${formattedLog}"${payloadMessage} to ${logDriver.config.identifier}.\n Error: "${thrownErrorMessage}"`; } /** * A multi-provider token which log drivers use to register with Lumberjack. */ const lumberjackLogDriverToken = new InjectionToken('__LUMBERJACK_LOG_DRIVER_TOKEN__'); const noReportedLogDriverErrorIndex = -1; /** * Service with programmatic API to pass logs to Lumberjack. Optionally * supports a log payload. * * Lumberjack passes the logs to the registered log drivers based on their * configurations. * * NOTE! Consider extending the `LumberjackLogger` or `ScopedLumberjackLogger` * base classes to set up predefined loggers unless you need a programmatic * API. */ class LumberjackService { /** * The registered log drivers. */ #drivers = inject(lumberjackLogDriverToken, { optional: true }) ?? []; #driverLogger = inject(LumberjackLogDriverLogger); #logFormatter = inject(LumberjackLogFormatter); #time = inject(LumberjackTimeService); constructor() { this.#drivers = Array.isArray(this.#drivers) ? this.#drivers : [this.#drivers]; } /** * Pass a log to Lumberjack which will be forwarded to the registered log * drivers based on their configurations. * * NOTE! It's recommended to use `LumberjackLogBuilder` to create the log. * * @param lumberjackLog The Lumberjack log. Optionally supports a log payload. */ log(lumberjackLog) { const { log, formattedLog } = this.#logFormatter.formatLog(lumberjackLog); this.#logWithErrorHandling(log, formattedLog, this.#drivers); } /** * Pass a critical log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logCritical(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'critical', message, payload, scope, }); } /** * Pass an error log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logError(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'error', message, payload, scope, }); } /** * Pass a warning log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logWarning(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'warn', message, payload, scope, }); } /** * Pass an info log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logInfo(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'info', message, payload, scope, }); } /** * Pass a debug log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logDebug(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'debug', message, payload, scope, }); } /** * Pass a trace log to Lumberjack which will be forwarded to the registered * log drivers based on their configurations. * * @param message Log message, for example describing an event that happened. * @param payload An optional payload to add to the log. * @param scope An optional scope to add to the {@link LumberjackLog}. */ logTrace(message, payload, scope) { this.log({ createdAt: this.#time.getUnixEpochTicks(), level: 'trace', message, payload, scope, }); } /** * Determine whether a log driver is configured to accept a log with the * specified log level. * * @param driver The configured log driver. * @param logLevel The log's log level. */ #canDriveLog(driver, logLevel) { const hasVerboseLogging = driver.config.levels.length === 1 && driver.config.levels[0] === 'verbose'; const acceptsLogLevel = driver.config.levels.includes(logLevel); return hasVerboseLogging || acceptsLogLevel; } /** * Create a log based on the specified log driver error. * * @param driverError The reported log driver error. */ #createDriverErrorLog(driverError) { return { createdAt: this.#time.getUnixEpochTicks(), level: 'error', message: formatLogDriverError(driverError), scope: 'LumberjackLogDriverError', }; } /** * If any stable log drivers are specified, the log driver errors will be * forward to them. Otherwise, they will be output to the browser console. * * @param stableDrivers The log drivers that haven't failed. * @param driverErrors The reported log driver errors. */ #handleDriverErrors(stableDrivers, driverErrors) { if (stableDrivers.length > 0) { this.#logDriverErrorsToStableDrivers(driverErrors, stableDrivers); } else { this.#outputUnhandledDriverErrors(driverErrors); } } /** * Pass the log to all the specified log drivers filtered by their log driver * configuration. If the driver throws an error while handling a log, an error * log with the caught error message will be created and passed to the stable * log drivers. * * @param log The log with an optional log payload. * @param formattedLog The text representation of the specified log. * @param drivers The log drivers. * @param driverErrors The reported log driver errors. * @param driverErrorIndex Index of the reported driver error to handle. */ #logWithErrorHandling(log, formattedLog, drivers, driverErrors = [], driverErrorIndex = noReportedLogDriverErrorIndex) { const stableDrivers = []; drivers .filter((driver) => this.#canDriveLog(driver, log.level)) .forEach((driver) => { try { this.#driverLogger.log(driver, { formattedLog, log }); stableDrivers.push(driver); if (driverErrorIndex !== noReportedLogDriverErrorIndex) { this.#removeHandledError(driverErrorIndex, driverErrors); driverErrorIndex = noReportedLogDriverErrorIndex; } } catch (error) { const caughtDriverError = { error, formattedLog, log, logDriver: driver }; driverErrors = [...driverErrors, caughtDriverError]; } }); this.#handleDriverErrors(stableDrivers, driverErrors); } /** * Pass the log driver errors to the specified stable log drivers. * * @param driverErrors The reported log driver errors. * @param stableDrivers The log drivers that haven't failed. */ #logDriverErrorsToStableDrivers(driverErrors, stableDrivers) { driverErrors.forEach((error, errorIndex) => { const driverErrorLog = this.#createDriverErrorLog(error); this.#logWithErrorHandling(driverErrorLog, driverErrorLog.message, stableDrivers, driverErrors, errorIndex); }); } /** * Output the specified log driver error to the browser console. * * @param driverError The reported log driver error. */ #outputDriverError(driverError) { const errorMessage = formatLogDriverError(driverError); console.error(errorMessage); } /** * Output the unhandled log driver errors to the browser console. * * @param driverErrors The reported log driver errors. */ #outputUnhandledDriverErrors(driverErrors) { driverErrors.forEach((error) => this.#outputDriverError(error)); } /** * Remove the log driver error with the specified index. * * @param driverErrorIndex The index of the log driver error to remove from * the specified log driver errors. * @param driverErrors The reported log driver errors. */ #removeHandledError(driverErrorIndex, driverErrors) { driverErrors.splice(driverErrorIndex, 1); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackService, decorators: [{ type: Injectable }], ctorParameters: () => [] }); const defaultDevelopmentLevels = ['verbose']; const defaultProductionLevels = ['critical', 'error', 'info', 'warn']; const lumberjackOptionsToken = new InjectionToken('__TEMP_LUMBERJACK_LOG_OPTIONS__'); function configFactory(isProductionEnvironment, options = {}) { return { format: lumberjackFormatLog, levels: isProductionEnvironment ? defaultProductionLevels : defaultDevelopmentLevels, ...options, }; } function logDriverConfigFactory({ levels }) { return { levels, }; } /** * Returns the [dependency-injection providers](https://angular.io/guide/glossary#provider) * for the `LumberjackOptions`, `LumberjackConfig` and `LumberjackLogDriverConfig`. * * @usageNotes * * The function is useful when you want to bootstrap an application using * the `bootstrapApplication` function and want to make available the `Lumberjack` providers. * * ```typescript * bootstrapApplication(RootComponent, { * providers: [ * provideLumberjack({...}) * ] * }); * ``` * * @publicApi */ function provideLumberjack(options) { return [ { provide: lumberjackOptionsToken, useValue: options }, { deps: [isProductionEnvironmentToken], provide: lumberjackConfigToken, useFactory: (isProductionEnvironment) => configFactory(isProductionEnvironment, options), }, { deps: [lumberjackConfigToken], provide: lumberjackLogDriverConfigToken, useFactory: logDriverConfigFactory, }, { provide: LumberjackService, useClass: LumberjackService }, LumberjackLogFormatter, LumberjackLogDriverLogger, LumberjackLogFactory, LumberjackTimeService, ]; } class LumberjackLoggerBuilder { #scope; #payload; #lumberjack; #time; #level; #message; constructor(lumberjack, time, level, message) { this.#lumberjack = lumberjack; this.#time = time; this.#level = level; this.#message = message; } build() { return (...payloadArg) => { this.#lumberjack.log({ level: this.#level, message: this.#message, scope: this.#scope, createdAt: this.#time.getUnixEpochTicks(), payload: payloadArg[0] ?? this.#payload, }); }; } /** * Add a scope to the `LumberjackLog` */ withScope(scope) { this.#scope = scope; return this; } /** * Add payload with custom data to the `LumberjackLog` */ withPayload(...payloadArg) { this.#payload = payloadArg[0]; return this; } } /** * A logger holds methods that log a predefined log. * * Implement application- and library-specific loggers by extending this base * class. Optionally supports a log payload. * * Each protected method on this base class returns a logger builder. */ class LumberjackLogger { lumberjack = inject(LumberjackService); time = inject(LumberjackTimeService); /** * Create a logger builder for a critical log with the specified message. */ createCriticalLogger(message) { return this.createLoggerBuilder('critical', message); } /** * Create a logger builder for a debug log with the specified message. */ createDebugLogger(message) { return this.createLoggerBuilder('debug', message); } /** * Create a logger builder for an error log with the specified message. */ createErrorLogger(message) { return this.createLoggerBuilder('error', message); } /** * Create a logger builder for an info log with the specified message. */ createInfoLogger(message) { return this.createLoggerBuilder('info', message); } /** * Create a logger builder for a trace log with the specified message. */ createTraceLogger(message) { return this.createLoggerBuilder('trace', message); } /** * Create a logger builder for a warning log with the specified message. */ createWarningLogger(message) { return this.createLoggerBuilder('warn', message); } /** * Create a logger builder for a log with the specified log level and message. */ createLoggerBuilder(level, message) { return new LumberjackLoggerBuilder(this.lumberjack, this.time, level, message); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogger, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogger }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LumberjackLogger, decorators: [{ type: Injectable }] }); /** * A scoped logger holds methods that log a predefined log sharing a scope. * * Implement application- and library-specific loggers by extending this base * class. Optionally supports a log payload. * * Each protected method on this base class returns a logger builder with a * predefined scope. */ class ScopedLumberjackLogger extends LumberjackLogger { lumberjack = inject(LumberjackService); time = inject(LumberjackTimeService); /** * Create a logger builder for a log with the shared scope as well as the * specified log level and message. */ createLoggerBuilder(level, message) { return new LumberjackLoggerBuilder(this.lumberjack, this.time, level, message).withScope(this.scope); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ScopedLumberjackLogger, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ScopedLumberjackLogger }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ScopedLumberjackLogger, decorators: [{ type: Injectable }] }); /* * Public API surface of @ngworker/lumberjack */ // Configuration /** * Generated bundle index. Do not edit. */ export { LumberjackLogBuilder, LumberjackLogFactory, LumberjackLogger, LumberjackService, LumberjackTimeService, ScopedLumberjackLogger, lumberjackConfigToken, lumberjackLogDriverConfigToken, lumberjackLogDriverToken, provideLumberjack }; //# sourceMappingURL=ngworker-lumberjack.mjs.map