You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/service/Logging.ts

177 lines
5.5 KiB

import {Singleton} from '../di/decorator/injection'
import {DebuggingTraceIsNotAnError} from '../util/error/DebuggingTraceIsNotAnError'
import {LoggingLevel, LogMessage} from '../util/logging/types'
import {Logger} from '../util/logging/Logger'
/**
* A singleton service that manages loggers registered in the application, and
* can be used to log output to all of them based on the configured logging level.
*
* This should be used in place of `console.log` as it also supports logging to
* external locations.
*
* @example
* ```typescript
* logging.info('Info level!')
* logging.debug('Some debugging information...')
* logging.warn('A warning!', true) // true, to force it to show, regardless of logging level.
* ```
*/
@Singleton()
export class Logging {
/** Array of Logger implementations that should be logged to. */
protected registeredLoggers: Logger[] = []
/** The currently configured logging level. */
protected currentLevel: LoggingLevel = LoggingLevel.Warning
/** Register a Logger implementation with this service. */
public registerLogger(logger: Logger): this {
if ( !this.registeredLoggers.includes(logger) ) {
this.registeredLoggers.push(logger)
}
return this
}
/**
* Remove a Logger implementation from this service, if it is registered.
* @param logger
*/
public unregisterLogger(logger: Logger): this {
this.registeredLoggers = this.registeredLoggers.filter(x => x !== logger)
return this
}
/**
* Get the current logging level.
*/
public get level(): LoggingLevel {
return this.currentLevel
}
/**
* Set the current logging level.
* @param level
*/
public set level(level: LoggingLevel) {
this.currentLevel = level
}
/**
* Write a success-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public success(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Success, output, force)
}
/**
* Write an error-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public error(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Error, output, force)
}
/**
* Write a warning-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public warn(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Warning, output, force)
}
/**
* Write an info-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public info(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Info, output, force)
}
/**
* Write a debugging-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public debug(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Debug, output, force)
}
/**
* Write a verbose-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
public verbose(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Verbose, output, force)
}
public trace(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Trace, output, force)
}
/**
* Helper function to write the given output, at the given logging level, to
* all of the registered loggers.
* @param level
* @param output
* @param force - if true, output even if outside the current logging level
* @protected
*/
protected writeLog(level: LoggingLevel, output: unknown, force = false): void {
const message = this.buildMessage(level, output)
const trace = DebuggingTraceIsNotAnError.getTrace()
const traceMessage = this.buildMessage(LoggingLevel.Trace, trace)
if ( this.currentLevel >= level || force ) {
for ( const logger of this.registeredLoggers ) {
try {
logger.write(message)
if ( this.currentLevel >= LoggingLevel.Trace ) {
logger.write(traceMessage)
}
} catch (e) {
console.error('logging error', e) // eslint-disable-line no-console
}
}
}
}
/**
* Given a level and output item, build a formatted LogMessage with date and caller.
* @param level
* @param output
* @protected
*/
protected buildMessage(level: LoggingLevel, output: unknown): LogMessage {
return {
level,
output,
date: new Date(),
callerName: this.getCallerInfo(),
}
}
/**
* Get the name of the object that called the log method using error traces.
* @param level
* @protected
*/
protected getCallerInfo(level = 5): string {
const e = new Error()
if ( !e.stack ) {
return 'Unknown'
}
return e.stack.split(/\s+at\s+/)
.slice(level)
.map((x: string): string => x.trim().split(' (')[0].split('.')[0].split(':')[0])[0]
.split('/')
.reverse()[0]
}
}