@sphereon/ssi-types
Version:
SSI Common Types
228 lines (195 loc) • 6.56 kB
text/typescript
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)
}
}