Log drivers
Earlier, we briefly introduced the term log driver. This section explains in depth how to use and configure them and how to create custom log drivers.
A log driver is the conduit used by the Lumberjack to output or persist application logs.
Lumberjack offers basic log drivers out-of-the-box, namely the LumberjackConsoleDriver
and the LumberjackHttpDriver
.
Every log driver implements the LumberjackLogDriver
interface.
export interface LumberjackLogDriver<TPayload extends LumberjackLogPayload | void = void> {
readonly config: LumberjackLogDriverConfig;
logCritical(driverLog: LumberjackLogDriverLog<TPayload>): void;
logDebug(driverLog: LumberjackLogDriverLog<TPayload>): void;
logError(driverLog: LumberjackLogDriverLog<TPayload>): void;
logInfo(driverLog: LumberjackLogDriverLog<TPayload>): void;
logTrace(driverLog: LumberjackLogDriverLog<TPayload>): void;
logWarning(driverLog: LumberjackLogDriverLog<TPayload>): void;
}
The LumberjackLogDriverLog
holds a formatted string representation of the LumberjackLog
and the LumberjackLog
itself.
export interface LumberjackLogDriverLog<TPayload extends LumberjackLogPayload | void = void> {
readonly formattedLog: string;
readonly log: LumberjackLog<TPayload>;
}
Log levels
Log drivers should make it possible to configure the logging levels on a per driver basis.
For example, we could use the default logging levels for the console driver, but only enable the critical and error levels for the HTTP driver as seen in the following example.
import { bootstrapApplication } from '@angular/platform-browser';
import { Level as LumberjackLevel, provideLumberjack } from '@ngworker/lumberjack';
import { provideLumberjackConsoleDriver } from '@ngworker/lumberjack/console-driver';
import { provideLumberjackHttpDriver, withHttpConfig } from '@ngworker/lumberjack/http-driver';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideLumberjack(),
provideLumberjackConsoleDriver(),
provideLumberjackHttpDriver(
withHttpConfig({
levels: ['critical', 'error'],
origin: 'ForestApp',
storeUrl: '/api/logs',
retryOptions: { maxRetries: 5, delayMs: 250 },
})
),
],
});
Creating a custom log driver
Let's create a simple log driver for the browser console.
import { inject, Injectable } from '@angular/core';
import { LumberjackLogDriver, LumberjackLogDriverConfig, LumberjackLogDriverLog } from '@ngworker/lumberjack';
import { consoleDriverConfigToken } from './console-driver-config.token';
@Injectable()
export class ConsoleDriver implements LumberjackLogDriver {
readonly config = inject(consoleDriverConfigToken);
logCritical({ formattedLog }: LumberjackLogDriverLog): void {
console.error(formattedLog);
}
logDebug({ formattedLog }: LumberjackLogDriverLog): void {
console.debug(formattedLog);
}
logError({ formattedLog }: LumberjackLogDriverLog): void {
console.error(formattedLog);
}
logInfo({ formattedLog }: LumberjackLogDriverLog): void {
console.info(formattedLog);
}
logTrace({ formattedLog }: LumberjackLogDriverLog): void {
console.trace(formattedLog);
}
logWarning({ formattedLog }: LumberjackLogDriverLog): void {
console.warn(formattedLog);
}
}
In the above snippet, the config is injected and assigned to the public config
property. Lumberjack uses this
configuration to determine which logs the log driver should handle.
Using a LumberjackLogPayload
We might want to add some extra data not present in the LumberjackLog
to our log driver.
For such cases, Lumberjack exposes the LumberjackLog#payload
property.
/**
* A Lumberjack log entry
*/
export interface LumberjackLog<TPayload extends LumberjackLogPayload | void = void> {
/**
* Scope, for example domain, application, component, or service.
*/
readonly scope?: string;
/**
* Unix epoch ticks (milliseconds) timestamp when log entry was created.
*/
readonly createdAt: number;
/**
* Level of severity.
*/
readonly level: LumberjackLogLevel;
/**
* Log message, for example describing an event that happened.
*/
readonly message: string;
/**
* Holds any payload info
*/
readonly payload?: TPayload;
}
We can modify the ConsoleDriver
to handle such payload information
import { inject, Injectable } from '@angular/core';
import {
LumberjackLogDriver,
LumberjackLogDriverConfig,
LumberjackLogDriverLog,
LumberjackLogPayload,
} from '@ngworker/lumberjack';
import { consoleDriverConfigToken } from './console-driver-config.token';
export interface AnalyticsPayload extends LumberjackLogPayload {
angularVersion: string;
}
@Injectable()
export class ConsoleDriver implements LumberjackLogDriver<AnalyticsPayload> {
readonly config = inject(consoleDriverConfigToken);
logCritical({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.error(formattedLog, log.payload);
}
logDebug({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.debug(formattedLog, log.payload);
}
logError({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.error(formattedLog, log.payload);
}
logInfo({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.info(formattedLog, log.payload);
}
logTrace({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.trace(formattedLog, log.payload);
}
logWarning({ formattedLog, log }: LumberjackLogDriverLog<AnalyticsPayload>): void {
console.warn(formattedLog, log.payload);
}
}
Creating a custom log driver provider functions
The provide functions provides configuration and other dependencies to a log driver. It also provides the log driver, making it available to Lumberjack.
import { Provider } from '@angular/core';
import {
LumberjackLogDriverConfig,
lumberjackLogDriverConfigToken,
lumberjackLogDriverToken,
} from '@ngworker/lumberjack';
import { LumberjackConsoleDriver } from '../log-drivers/lumberjack-console.driver';
import { lumberjackConsoleDriverConfigToken } from './lumberjack-console-driver-config.token';
import { LumberjackConsoleDriverConfig } from './lumberjack-console-driver.config';
export function provideLumberjackConsoleDriver(config: Partial<LumberjackConsoleDriverConfig> = {}): Provider[] {
return [
{
provide: lumberjackConsoleDriverConfigToken,
deps: [lumberjackLogDriverConfigToken],
useFactory: (logDriverConfig: LumberjackLogDriverConfig): LumberjackConsoleDriverConfig => ({
...logDriverConfig,
identifier: LumberjackConsoleDriver.driverIdentifier,
...config,
}),
},
{
provide: lumberjackLogDriverToken,
useClass: LumberjackConsoleDriver,
multi: true,
},
];
}
If no configuration is passed, then the root LogDriverConfig
is used.
import { InjectionToken } from '@angular/core';
import { LumberjackLogDriverConfig, lumberjackLogDriverConfigToken } from '@ngworker/lumberjack';
export const consoleDriverConfigToken = new InjectionToken<LumberjackLogDriverConfig>('__CONSOLE_DRIVER_CONFIG__', {
factory: () => inject({ ...lumberjackLogDriverConfigToken, identifier: 'ConsoleDriver' }),
});
This is possible because the ConsoleDriver
has the same configuration options as the LumberjackLogDriverConfig
. We
only have to include the driver identifier since it cannot be predefined.
For adding custom settings, see LumberjackHttpDriver.
The most important thing about the provideLumberjackConsoleDriver
function is that it provides the LumberjackConsoleDriver
using the lumberjackLogDriverToken
with the multi
flag on. This allows us to provide multiple log drivers for
Lumberjack at the same time.
Using a custom log driver
The last step is to provide this driver at the bootstrapApplication
function, as seen in the first Usage
section.
import { bootstrapApplication } from '@angular/platform-browser';
import { LumberjackLog, LumberjackOptions, provideLumberjack } from '@ngworker/lumberjack';
import { provideLumberjackConsoleDriver } from '@ngworker/lumberjack/console-driver';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideLumberjack(),
provideLumberjackConsoleDriver(),
// (...)
],
});
HTTP driver
For a more advanced log driver implementation, see LumberjackHttpDriver