lib/src/service/Logging.ts

166 lines
4.9 KiB
TypeScript
Raw Normal View History

2021-06-03 03:36:25 +00:00
import {Logger, LoggingLevel, LogMessage} from '../util'
import {Singleton} from '../di'
2021-03-03 00:57:41 +00:00
2021-03-25 13:50:13 +00:00
/**
* 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.
* ```
*/
2021-03-03 00:57:41 +00:00
@Singleton()
export class Logging {
2021-03-25 13:50:13 +00:00
/** Array of Logger implementations that should be logged to. */
2021-03-03 00:57:41 +00:00
protected registeredLoggers: Logger[] = []
2021-03-25 13:50:13 +00:00
/** The currently configured logging level. */
2021-03-03 00:57:41 +00:00
protected currentLevel: LoggingLevel = LoggingLevel.Warning
2021-03-25 13:50:13 +00:00
/** Register a Logger implementation with this service. */
2021-06-03 03:36:25 +00:00
public registerLogger(logger: Logger): this {
2021-03-03 00:57:41 +00:00
if ( !this.registeredLoggers.includes(logger) ) {
this.registeredLoggers.push(logger)
}
2021-06-03 03:36:25 +00:00
return this
2021-03-03 00:57:41 +00:00
}
2021-03-25 13:50:13 +00:00
/**
* Remove a Logger implementation from this service, if it is registered.
* @param logger
*/
2021-06-03 03:36:25 +00:00
public unregisterLogger(logger: Logger): this {
2021-03-03 00:57:41 +00:00
this.registeredLoggers = this.registeredLoggers.filter(x => x !== logger)
2021-06-03 03:36:25 +00:00
return this
2021-03-03 00:57:41 +00:00
}
2021-03-25 13:50:13 +00:00
/**
* Get the current logging level.
*/
2021-03-03 00:57:41 +00:00
public get level(): LoggingLevel {
return this.currentLevel
}
2021-03-25 13:50:13 +00:00
/**
* Set the current logging level.
* @param level
*/
2021-03-03 00:57:41 +00:00
public set level(level: LoggingLevel) {
this.currentLevel = level
}
2021-03-25 13:50:13 +00:00
/**
* Write a success-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public success(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Success, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* Write an error-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public error(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Error, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* Write a warning-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public warn(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Warning, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* Write an info-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public info(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Info, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* Write a debugging-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public debug(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Debug, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* Write a verbose-level output to the logs.
* @param output
* @param force - if true, output even if outside the current logging level
*/
2021-06-03 03:36:25 +00:00
public verbose(output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
this.writeLog(LoggingLevel.Verbose, output, force)
}
2021-03-25 13:50:13 +00:00
/**
* 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
*/
2021-06-03 03:36:25 +00:00
protected writeLog(level: LoggingLevel, output: unknown, force = false): void {
2021-03-03 00:57:41 +00:00
const message = this.buildMessage(level, output)
if ( this.currentLevel >= level || force ) {
for ( const logger of this.registeredLoggers ) {
try {
logger.write(message)
} catch (e) {
2021-06-03 03:36:25 +00:00
console.error('logging error', e) // eslint-disable-line no-console
2021-03-03 00:57:41 +00:00
}
}
}
}
2021-03-25 13:50:13 +00:00
/**
* Given a level and output item, build a formatted LogMessage with date and caller.
* @param level
* @param output
* @protected
*/
2021-06-03 03:36:25 +00:00
protected buildMessage(level: LoggingLevel, output: unknown): LogMessage {
2021-03-03 00:57:41 +00:00
return {
level,
output,
2021-06-03 03:36:25 +00:00
date: new Date(),
2021-03-03 00:57:41 +00:00
callerName: this.getCallerInfo(),
}
}
2021-03-25 13:50:13 +00:00
/**
* Get the name of the object that called the log method using error traces.
* @param level
* @protected
*/
2021-03-03 00:57:41 +00:00
protected getCallerInfo(level = 5): string {
const e = new Error()
2021-06-03 03:36:25 +00:00
if ( !e.stack ) {
return 'Unknown'
}
2021-03-03 00:57:41 +00:00
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]
2021-03-03 00:57:41 +00:00
}
}