UNPKG

@sphereon/ssi-types

Version:

SSI Common Types

228 lines (195 loc) • 6.56 kB
import createDebug from 'debug' import { EventEmitter } from 'events' export enum LogLevel { TRACE = 0, DEBUG, INFO, WARNING, ERROR, } export enum LoggingEventType { AUDIT = 'audit', ACTIVITY = 'activity', GENERAL = 'general', } export interface SimpleLogEvent { type: LoggingEventType.GENERAL level: LogLevel correlationId?: string timestamp: Date data: string diagnosticData?: any } export enum LogMethod { DEBUG_PKG, CONSOLE, EVENT, } export interface SimpleLogOptions { namespace?: string eventName?: string defaultLogLevel?: LogLevel methods?: LogMethod[] } export function logOptions(opts?: SimpleLogOptions): Required<SimpleLogOptions> { return { namespace: opts?.namespace ?? 'sphereon', eventName: opts?.eventName ?? 'sphereon:default', defaultLogLevel: opts?.defaultLogLevel ?? LogLevel.INFO, methods: opts?.methods ?? [LogMethod.DEBUG_PKG, LogMethod.EVENT], } } export class Loggers { private static readonly DEFAULT_KEY = '__DEFAULT__' public static readonly DEFAULT: Loggers = new Loggers({ defaultLogLevel: LogLevel.INFO, methods: [LogMethod.DEBUG_PKG, LogMethod.EVENT], }) private readonly namespaceOptions: Map<string, Required<SimpleLogOptions>> = new Map() private readonly loggers: WeakMap<Required<SimpleLogOptions>, ISimpleLogger<any>> = new WeakMap() constructor(defaultOptions?: Omit<SimpleLogOptions, 'namespace'>) { this.defaultOptions(logOptions(defaultOptions)) } public options(namespace: string, options: Omit<SimpleLogOptions, 'namespace'>): this { this.namespaceOptions.set(namespace, logOptions({ ...options, namespace })) return this } public defaultOptions(options: Omit<SimpleLogOptions, 'namespace'>): this { this.options(Loggers.DEFAULT_KEY, options) return this } register<T>(namespace: string, logger: ISimpleLogger<T>): ISimpleLogger<T> { return this.get(namespace, logger) } get<T>(namespace: string, registerLogger?: ISimpleLogger<T>): ISimpleLogger<T> { const options = this.namespaceOptions.get(namespace) ?? registerLogger?.options ?? this.namespaceOptions.get(Loggers.DEFAULT_KEY) if (!options) { throw Error(`No logging options found for namespace ${namespace}`) } this.namespaceOptions.set(namespace, options) let logger = this.loggers.get(options) if (!logger) { logger = registerLogger ?? new SimpleLogger(options) this.loggers.set(options, logger) } return logger } } export type ISimpleLogger<LogType> = { options: Required<SimpleLogOptions> log(value: LogType, ...args: any[]): void info(value: LogType, ...args: any[]): void debug(value: LogType, ...args: any[]): void trace(value: LogType, ...args: any[]): void warning(value: LogType, ...args: any[]): void error(value: LogType, ...args: any[]): void logl(level: LogLevel, value: LogType, ...argsW: any[]): void } export class SimpleLogger implements ISimpleLogger<any> { private _eventEmitter = new EventEmitter({ captureRejections: true }) private readonly _options: Required<SimpleLogOptions> constructor(opts?: SimpleLogOptions) { this._options = logOptions(opts) } get eventEmitter(): EventEmitter { return this._eventEmitter } get options(): Required<SimpleLogOptions> { return this._options } trace(value: any, ...args: any[]) { this.logImpl(LogLevel.TRACE, value, ...args) } debug(value: any, ...args: any[]) { this.logImpl(LogLevel.DEBUG, value, ...args) } info(value: any, ...args: any[]) { this.logImpl(LogLevel.INFO, value, ...args) } warning(value: any, ...args: any[]) { this.logImpl(LogLevel.WARNING, value, ...args) } error(value: any, ...args: any[]) { this.logImpl(LogLevel.ERROR, value, ...args) } logl(level: LogLevel, value: any, ...args: any[]) { this.logImpl(level, value, ...args) } private logImpl(level: LogLevel, value: any, ...args: any[]) { const date = new Date().toISOString() const filteredArgs = args?.filter((v) => v!!) ?? [] const arg = filteredArgs.length === 0 || filteredArgs[0] == undefined ? undefined : filteredArgs function toLogValue(options: SimpleLogOptions): any { if (typeof value === 'string') { return `${date}-(${options.namespace}) ${value}` } else if (typeof value === 'object') { value['namespace'] = options.namespace value['time'] = date } return value } const logValue = toLogValue(this.options) const logArgs = [logValue] if (arg) { logArgs.push(args) } // FIXME: !!!!!!!!!!!!!!!!!!!!!! let debugPkgEnabled = false && this.options.methods.includes(LogMethod.DEBUG_PKG) if (debugPkgEnabled) { const debugPkgDebugger = createDebug.default(this._options.namespace) // It was enabled at the options level in code, but could be disabled at runtime using env vars debugPkgEnabled = debugPkgDebugger.enabled if (debugPkgEnabled) { if (arg) { debugPkgDebugger(`${date}- ${value},`, ...arg) } else { debugPkgDebugger(`${date}- ${value}`) } } } // We do not perform console.logs in case the debug package is enabled in code and used at runtime if (this.options.methods.includes(LogMethod.CONSOLE) && !debugPkgEnabled) { const [value, args] = logArgs let logMethod = console.info switch (level) { case LogLevel.TRACE: logMethod = console.trace break case LogLevel.DEBUG: logMethod = console.debug break case LogLevel.INFO: logMethod = console.info break case LogLevel.WARNING: logMethod = console.warn break case LogLevel.ERROR: logMethod = console.error break } if (args) { logMethod(value + ',', ...args) } else { logMethod(value) } } if (this.options.methods.includes(LogMethod.EVENT)) { this._eventEmitter.emit(this.options.eventName, { data: value.toString(), timestamp: new Date(date), level, type: LoggingEventType.GENERAL, diagnosticData: logArgs, } satisfies SimpleLogEvent) } } log(value: any, ...args: any[]) { this.logImpl(this.options.defaultLogLevel, value, ...args) } } export class SimpleRecordLogger extends SimpleLogger implements ISimpleLogger<Record<string, any>> { constructor(opts?: SimpleLogOptions) { super(opts) } }