packages/kernel/src/logger.ts
import { createInterface, IContainer, IRegistry } from './di';
import { instanceRegistration, singletonRegistration } from './di.registration';
import { bound, toLookup } from './functions';
import { Class, Constructable } from './interfaces';
import { IPlatform } from './platform';
import { getAnnotationKeyFor } from './resource';
import { createLookup, defineMetadata, getMetadata, isFunction, objectFreeze } from './utilities';
import { resolve } from './di.container';
import { all, optional } from './di.resolvers';
/** @internal */ export const trace = 0;
/** @internal */ export const debug = 1;
/** @internal */ export const info = 2;
/** @internal */ export const warn = 3;
/** @internal */ export const error = 4;
/** @internal */ export const fatal = 5;
/** @internal */ export const none = 6;
export const LogLevel = objectFreeze({
/**
* The most detailed information about internal app state.
*
* Disabled by default and should never be enabled in a production environment.
*/
trace,
/**
* Information that is useful for debugging during development and has no long-term value.
*/
debug,
/**
* Information about the general flow of the application that has long-term value.
*/
info,
/**
* Unexpected circumstances that require attention but do not otherwise cause the current flow of execution to stop.
*/
warn,
/**
* Unexpected circumstances that cause the flow of execution in the current activity to stop but do not cause an app-wide failure.
*/
error,
/**
* Unexpected circumstances that cause an app-wide failure or otherwise require immediate attention.
*/
fatal,
/**
* No messages should be written.
*/
none,
} as const);
export type LogLevel = typeof LogLevel[keyof typeof LogLevel];
/**
* Flags to enable/disable color usage in the logging output.
* - `no-colors`: Do not use ASCII color codes in logging output.
* - `colors`: Use ASCII color codes in logging output. By default, timestamps and the TRC and DBG prefix are colored grey. INF white, WRN yellow, and ERR and FTL red.
*/
export type ColorOptions = 'no-colors' | 'colors';
/**
* The global logger configuration.
*
* Properties on this object can be changed during runtime and will affect logging of all components that are housed under the same root DI container as the logger.
*/
export interface ILogConfig {
/**
* The global color options.
*/
colorOptions: ColorOptions;
/**
* The global log level. Only log calls with the same level or higher are emitted.
*/
level: LogLevel;
}
interface ILoggingConfigurationOptions extends ILogConfig {
$console: IConsoleLike;
sinks: (Class<ISink> | IRegistry)[];
}
/**
* Component that creates log event objects based on raw inputs sent to `ILogger`.
*
* To customize what data is sent to the sinks, replace the implementation for this interface with your own.
*
* @example
*
* ```ts
* export class MyLogEventFactory {
* public createLogEvent(logger: ILogger, logLevel: LogLevel, message: string, optionalParams: unknown[]): ILogEvent {
* return {
* logLevel,
* optionalParams,
* toString() {
* return `[${logger.scope.join('.')}] ${message} ${optionalParams.join(', ')}`;
* }
* };
* }
* }
*
* container.register(Registration.singleton(ILogEventFactory, MyLogEventFactory));
* ```
*/
export interface ILogEventFactory {
/**
* Create a log event object based on the input parameters sent to `ILogger`.
*
* @param logger - The `ILogger` that received the message.
* @param logLevel - The `LogLevel` associated with the `ILogger` method that the message was passed into. E.g. `logger.debug` will result in `LogLevel.debug`
* @param message - The message (first parameter) that was passed into the logger. If a function was passed into the logger, this will be the return value of that function.
* @param optionalParams - Additional optional parameters there were passed into the logger, if any.
*
* @returns An `ILogEvent` object that, by default, only has a `.toString()` method.
*
* This is called by the default console sink to get the message to emit to the console.
* It could be any object of any shape, as long as the registered sinks understand that shape.
*/
createLogEvent(logger: ILogger, logLevel: LogLevel, message: string | Error, optionalParams: unknown[]): ILogEvent;
}
/**
* A logging sink that emits `ILogEvent` objects to any kind of output. This can be the console, a database, a web api, a file, etc.
*
* Multiple sinks can be registered, and all events will be emitted to all of them.
*
* @example
* // A buffered file sink that writes once per second:
*
* ```ts
* export class BufferedFileSink {
* private readonly buffer: ILogEvent[] = [];
*
* constructor() {
* setInterval(() => {
* const events = this.buffer.splice(0);
* if (events.length > 0) {
* fs.appendFileSync('my-log.txt', events.map(e => e.toString()).join('\n'));
* }
* }, 1000);
* }
*
* public emit(event: ILogEvent): void {
* this.buffer.push(event);
* }
* }
*
* container.register(Registration.singleton(ISink, BufferedFileSink));
* ```
*/
export interface ISink {
/**
* Handle the provided `ILogEvent` to the output interface wrapped by this sink.
*
* @param event - The event object to emit. Built-in sinks will call `.toString()` on the event object but custom sinks can do anything they like with the event.
*/
handleEvent(event: ILogEvent): void;
}
/**
* The main interface to the logging API.
*
* Inject this as a dependency in your components to add centralized, configurable logging capabilities to your application.
*/
export interface ILogger extends DefaultLogger {}
export const ILogConfig = /*@__PURE__*/createInterface<ILogConfig>('ILogConfig', x => x.instance(new LogConfig('no-colors', warn)));
export const ISink = /*@__PURE__*/createInterface<ISink>('ISink');
export const ILogEventFactory = /*@__PURE__*/createInterface<ILogEventFactory>('ILogEventFactory', x => x.singleton(DefaultLogEventFactory));
export const ILogger = /*@__PURE__*/createInterface<ILogger>('ILogger', x => x.singleton(DefaultLogger));
export const ILogScopes = /*@__PURE__*/createInterface<string[]>('ILogScope');
interface SinkDefinition {
handles: Exclude<LogLevel, typeof none>[];
}
export const LoggerSink = /*@__PURE__*/objectFreeze({
key: getAnnotationKeyFor('logger-sink-handles'),
define<TSink extends Constructable<ISink>>(target: TSink, definition: SinkDefinition) {
defineMetadata(definition.handles, target, this.key);
},
getHandles<TSink extends ISink>(target: TSink): Exclude<LogLevel, typeof none>[] | undefined {
return getMetadata(this.key, target.constructor);
},
});
export const sink = (definition: SinkDefinition) => {
return <TSink extends Constructable<ISink>>(_target: TSink, context: ClassDecoratorContext<TSink>): void =>
context.addInitializer(function (this) {
LoggerSink.define(this, definition);
});
};
export interface IConsoleLike {
debug(message: string, ...optionalParams: unknown[]): void;
info(message: string, ...optionalParams: unknown[]): void;
warn(message: string, ...optionalParams: unknown[]): void;
error(message: string, ...optionalParams: unknown[]): void;
}
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
export const format = toLookup({
red<T extends string>(str: T): T {
return `\u001b[31m${str}\u001b[39m` as T;
},
green<T extends string>(str: T): T {
return `\u001b[32m${str}\u001b[39m` as T;
},
yellow<T extends string>(str: T): T {
return `\u001b[33m${str}\u001b[39m` as T;
},
blue<T extends string>(str: T): T {
return `\u001b[34m${str}\u001b[39m` as T;
},
magenta<T extends string>(str: T): T {
return `\u001b[35m${str}\u001b[39m` as T;
},
cyan<T extends string>(str: T): T {
return `\u001b[36m${str}\u001b[39m` as T;
},
white<T extends string>(str: T): T {
return `\u001b[37m${str}\u001b[39m` as T;
},
grey<T extends string>(str: T): T {
return `\u001b[90m${str}\u001b[39m` as T;
},
} as const);
export interface ILogEvent {
readonly severity: LogLevel;
readonly message: string | Error;
readonly optionalParams?: readonly unknown[];
readonly scope: readonly string[];
readonly colorOptions: ColorOptions;
readonly timestamp: number;
toString(): string;
getFormattedLogInfo(forConsole?: boolean): [string, ...unknown[]];
}
export class LogConfig implements ILogConfig {
public constructor(
public readonly colorOptions: ColorOptions,
public readonly level: LogLevel,
) {}
}
const getLogLevelString = (function () {
const logLevelString = {
'no-colors': toLookup({
TRC: 'TRC',
DBG: 'DBG',
INF: 'INF',
WRN: 'WRN',
ERR: 'ERR',
FTL: 'FTL',
QQQ: '???',
} as const),
'colors': toLookup({
TRC: format.grey('TRC'),
DBG: format.grey('DBG'),
INF: format.white('INF'),
WRN: format.yellow('WRN'),
ERR: format.red('ERR'),
FTL: format.red('FTL'),
QQQ: format.grey('???'),
} as const),
} as const;
return (level: LogLevel, colorOptions: ColorOptions): string => {
if (level <= trace) {
return logLevelString[colorOptions].TRC;
}
if (level <= debug) {
return logLevelString[colorOptions].DBG;
}
if (level <= info) {
return logLevelString[colorOptions].INF;
}
if (level <= warn) {
return logLevelString[colorOptions].WRN;
}
if (level <= error) {
return logLevelString[colorOptions].ERR;
}
if (level <= fatal) {
return logLevelString[colorOptions].FTL;
}
return logLevelString[colorOptions].QQQ;
};
})();
const getScopeString = (scope: readonly string[], colorOptions: ColorOptions): string => {
if (colorOptions === 'no-colors') {
return scope.join('.');
}
return scope.map(format.cyan).join('.');
};
const getIsoString = (timestamp: number, colorOptions: ColorOptions): string => {
if (colorOptions === 'no-colors') {
return new Date(timestamp).toISOString();
}
return format.grey(new Date(timestamp).toISOString());
};
export class DefaultLogEvent implements ILogEvent {
public constructor(
public readonly severity: LogLevel,
public readonly message: string | Error,
public readonly optionalParams: unknown[],
public readonly scope: readonly string[],
public readonly colorOptions: ColorOptions,
public readonly timestamp: number,
) {}
public toString(): string {
const { severity, message, scope, colorOptions, timestamp } = this;
if (scope.length === 0) {
return `${getIsoString(timestamp, colorOptions)} [${getLogLevelString(severity, colorOptions)}] ${message}`;
}
return `${getIsoString(timestamp, colorOptions)} [${getLogLevelString(severity, colorOptions)} ${getScopeString(scope, colorOptions)}] ${message}`;
}
public getFormattedLogInfo(forConsole: boolean = false): [string, ...unknown[]] {
const { severity, message: messageOrError, scope, colorOptions, timestamp, optionalParams } = this;
let error: Error|null = null;
let message: string = '';
if (forConsole && messageOrError instanceof Error) {
error = messageOrError;
} else {
message = messageOrError as string;
}
const scopeInfo = scope.length === 0 ? '' : ` ${getScopeString(scope, colorOptions)}`;
let msg = `${getIsoString(timestamp, colorOptions)} [${getLogLevelString(severity, colorOptions)}${scopeInfo}] ${message}`;
if (optionalParams === void 0 || optionalParams.length === 0) {
return error === null ? [msg] : [msg, error];
}
let offset = 0;
while (msg.includes('%s')) {
msg = msg.replace('%s', String(optionalParams[offset++]));
}
return error !== null ? [msg, error, ...optionalParams.slice(offset)] : [msg, ...optionalParams.slice(offset)];
}
}
export class DefaultLogEventFactory implements ILogEventFactory {
public readonly config = resolve(ILogConfig);
public createLogEvent(logger: ILogger, level: LogLevel, message: string | Error, optionalParams: unknown[]): ILogEvent {
return new DefaultLogEvent(level, message, optionalParams, logger.scope, this.config.colorOptions, Date.now());
}
}
export class ConsoleSink implements ISink {
public static register(container: IContainer) {
singletonRegistration(ISink, ConsoleSink).register(container);
}
public readonly handleEvent: (event: ILogEvent) => void;
public constructor(
p = resolve(IPlatform),
) {
const $console = p.console as {
debug(...args: unknown[]): void;
info(...args: unknown[]): void;
warn(...args: unknown[]): void;
error(...args: unknown[]): void;
};
this.handleEvent = function emit(event: ILogEvent): void {
const _info = event.getFormattedLogInfo(true);
switch (event.severity) {
case trace:
case debug:
return $console.debug(..._info);
case info:
return $console.info(..._info);
case warn:
return $console.warn(..._info);
case error:
case fatal:
return $console.error(..._info);
}
};
}
}
export class DefaultLogger {
/**
* The root `ILogger` instance. On the root logger itself, this property circularly references the root. It is never null.
*
* When using `.scopeTo`, a new `ILogger` is created. That new logger will have the `root` property set to the global (non-scoped) logger.
*/
public readonly root: ILogger;
public readonly config: ILogConfig;
public readonly sinks: readonly ISink[];
/**
* The parent `ILogger` instance. On the root logger itself, this property circularly references the root. It is never null.
*
* When using `.scopeTo`, a new `ILogger` is created. That new logger will have the `parent` property set to the logger that it was created from.
*/
private readonly parent: ILogger;
/** @internal */
private readonly _traceSinks: ISink[];
/** @internal */
private readonly _debugSinks: ISink[];
/** @internal */
private readonly _infoSinks: ISink[];
/** @internal */
private readonly _warnSinks: ISink[];
/** @internal */
private readonly _errorSinks: ISink[];
/** @internal */
private readonly _fatalSinks: ISink[];
/** @internal */
private readonly _scopedLoggers = createLookup<ILogger | undefined>();
/** @internal */
private readonly _factory: ILogEventFactory;
/* eslint-disable default-param-last */
public constructor(
/**
* The global logger configuration.
*/
config = resolve(ILogConfig),
factory = resolve(ILogEventFactory),
sinks = resolve(all(ISink)),
/**
* The scopes that this logger was created for, if any.
*/
public readonly scope: string[] = resolve(optional(ILogScopes)) ?? [],
parent: DefaultLogger | null = null,
) {
/* eslint-enable default-param-last */
let traceSinks: ISink[];
let debugSinks: ISink[];
let infoSinks: ISink[];
let warnSinks: ISink[];
let errorSinks: ISink[];
let fatalSinks: ISink[];
this.config = config;
this._factory = factory;
this.sinks = sinks;
if (parent === null) {
this.root = this;
this.parent = this;
traceSinks = this._traceSinks = [];
debugSinks = this._debugSinks = [];
infoSinks = this._infoSinks = [];
warnSinks = this._warnSinks = [];
errorSinks = this._errorSinks = [];
fatalSinks = this._fatalSinks = [];
for (const $sink of sinks) {
const handles = LoggerSink.getHandles($sink);
if (handles?.includes(trace) ?? true) {
traceSinks.push($sink);
}
if (handles?.includes(debug) ?? true) {
debugSinks.push($sink);
}
if (handles?.includes(info) ?? true) {
infoSinks.push($sink);
}
if (handles?.includes(warn) ?? true) {
warnSinks.push($sink);
}
if (handles?.includes(error) ?? true) {
errorSinks.push($sink);
}
if (handles?.includes(fatal) ?? true) {
fatalSinks.push($sink);
}
}
} else {
this.root = parent.root;
this.parent = parent;
traceSinks = this._traceSinks = parent._traceSinks;
debugSinks = this._debugSinks = parent._debugSinks;
infoSinks = this._infoSinks = parent._infoSinks;
warnSinks = this._warnSinks = parent._warnSinks;
errorSinks = this._errorSinks = parent._errorSinks;
fatalSinks = this._fatalSinks = parent._fatalSinks;
}
}
/**
* Write to TRC output, if the configured `LogLevel` is set to `trace`.
*
* Intended for the most detailed information about internal app state.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public trace(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to TRC output, if the configured `LogLevel` is set to `trace`.
*
* Intended for the most detailed information about internal app state.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public trace(message: unknown, ...optionalParams: unknown[]): void;
@bound
public trace(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= trace) {
this._emit(this._traceSinks, trace, messageOrGetMessage, optionalParams);
}
}
/**
* Write to DBG output, if the configured `LogLevel` is set to `debug` or lower.
*
* Intended for information that is useful for debugging during development and has no long-term value.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public debug(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to DBG output, if the configured `LogLevel` is set to `debug` or lower.
*
* Intended for information that is useful for debugging during development and has no long-term value.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public debug(message: unknown, ...optionalParams: unknown[]): void;
@bound
public debug(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= debug) {
this._emit(this._debugSinks, debug, messageOrGetMessage, optionalParams);
}
}
/**
* Write to trace UBF, if the configured `LogLevel` is set to `info` or lower.
*
* Intended for information about the general flow of the application that has long-term value.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public info(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to trace UBF, if the configured `LogLevel` is set to `info` or lower.
*
* Intended for information about the general flow of the application that has long-term value.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public info(message: unknown, ...optionalParams: unknown[]): void;
@bound
public info(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= info) {
this._emit(this._infoSinks, info, messageOrGetMessage, optionalParams);
}
}
/**
* Write to WRN output, if the configured `LogLevel` is set to `warn` or lower.
*
* Intended for unexpected circumstances that require attention but do not otherwise cause the current flow of execution to stop.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public warn(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to WRN output, if the configured `LogLevel` is set to `warn` or lower.
*
* Intended for unexpected circumstances that require attention but do not otherwise cause the current flow of execution to stop.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public warn(message: unknown, ...optionalParams: unknown[]): void;
@bound
public warn(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= warn) {
this._emit(this._warnSinks, warn, messageOrGetMessage, optionalParams);
}
}
/**
* Write to ERR output, if the configured `LogLevel` is set to `error` or lower.
*
* Intended for unexpected circumstances that cause the flow of execution in the current activity to stop but do not cause an app-wide failure.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public error(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to ERR output, if the configured `LogLevel` is set to `error` or lower.
*
* Intended for unexpected circumstances that cause the flow of execution in the current activity to stop but do not cause an app-wide failure.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public error(message: unknown, ...optionalParams: unknown[]): void;
@bound
public error(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= error) {
this._emit(this._errorSinks, error, messageOrGetMessage, optionalParams);
}
}
/**
* Write to FTL output, if the configured `LogLevel` is set to `fatal` or lower.
*
* Intended for unexpected circumstances that cause an app-wide failure or otherwise require immediate attention.
*
* @param getMessage - A function to build the message to pass to the `ILogEventFactory`.
* Only called if the configured `LogLevel` dictates that these messages be emitted.
* Use this when creating the log message is potentially expensive and should only be done if the log is actually emitted.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public fatal(getMessage: () => unknown, ...optionalParams: unknown[]): void;
/**
* Write to FTL output, if the configured `LogLevel` is set to `fatal` or lower.
*
* Intended for unexpected circumstances that cause an app-wide failure or otherwise require immediate attention.
*
* @param message - The message to pass to the `ILogEventFactory`.
* @param optionalParams - Any additional, optional params that should be passed to the `ILogEventFactory`
*/
public fatal(message: unknown, ...optionalParams: unknown[]): void;
@bound
public fatal(messageOrGetMessage: unknown, ...optionalParams: unknown[]): void {
if (this.config.level <= fatal) {
this._emit(this._fatalSinks, fatal, messageOrGetMessage, optionalParams);
}
}
/**
* Create a new logger with an additional permanent prefix added to the logging outputs.
* When chained, multiple scopes are separated by a dot.
*
* This is preliminary API and subject to change before alpha release.
*
* @example
*
* ```ts
* export class MyComponent {
* constructor(@ILogger private logger: ILogger) {
* this.logger.debug('before scoping');
* // console output: '[DBG] before scoping'
* this.logger = logger.scopeTo('MyComponent');
* this.logger.debug('after scoping');
* // console output: '[DBG MyComponent] after scoping'
* }
*
* public doStuff(): void {
* const logger = this.logger.scopeTo('doStuff()');
* logger.debug('doing stuff');
* // console output: '[DBG MyComponent.doStuff()] doing stuff'
* }
* }
* ```
*/
public scopeTo(name: string): ILogger {
const scopedLoggers = this._scopedLoggers;
let scopedLogger = scopedLoggers[name];
if (scopedLogger === void 0) {
scopedLogger = scopedLoggers[name] = new DefaultLogger(this.config, this._factory, null!, this.scope.concat(name), this);
}
return scopedLogger;
}
/** @internal */
private _emit(sinks: ISink[], level: LogLevel, msgOrGetMsg: unknown, optionalParams: unknown[]): void {
const message = (isFunction(msgOrGetMsg) ? msgOrGetMsg() : msgOrGetMsg) as string;
const event = this._factory.createLogEvent(this, level, message, optionalParams);
for (let i = 0, ii = sinks.length; i < ii; ++i) {
sinks[i].handleEvent(event);
}
}
}
/**
* A basic `ILogger` configuration that configures a single `console` sink based on provided options.
*
* NOTE: You *must* register the return value of `.create` with the container / au instance, not this `LoggerConfiguration` object itself.
*
* @example
* ```ts
* container.register(LoggerConfiguration.create());
*
* container.register(LoggerConfiguration.create({sinks: [ConsoleSink]}))
*
* container.register(LoggerConfiguration.create({sinks: [ConsoleSink], level: LogLevel.debug}))
*
* ```
*/
export const LoggerConfiguration = /*@__PURE__*/ toLookup({
/**
* @param $console - The `console` object to use. Can be the native `window.console` / `global.console`, but can also be a wrapper or mock that implements the same interface.
* @param level - The global `LogLevel` to configure. Defaults to `warn` or higher.
* @param colorOptions - Whether to use colors or not. Defaults to `noColors`. Colors are especially nice in nodejs environments but don't necessarily work (well) in all environments, such as browsers.
*/
create(
{
level = warn,
colorOptions = 'no-colors',
sinks = [],
}: Partial<ILoggingConfigurationOptions> = {}
): IRegistry {
return toLookup({
register(container: IContainer): IContainer {
container.register(
instanceRegistration(ILogConfig, new LogConfig(colorOptions, level)),
);
for (const $sink of sinks) {
if (isFunction($sink)) {
container.register(singletonRegistration(ISink, $sink));
} else {
container.register($sink);
}
}
return container;
},
});
},
});