@ngworker/lumberjack
Version:
Lumberjack is a versatile Angular logging library, specifically designed to be extended and customized
764 lines (734 loc) • 27.2 kB
JavaScript
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