import { Injectable } from '../decorators/core/injectable.decorator'; import { Optional } from '../decorators/core/optional.decorator'; import { clc, yellow } from '../utils/cli-colors.util'; import { isObject, isPlainObject } from '../utils/shared.utils'; declare const process: any; export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose'; export interface LoggerService { log(message: any, context?: string); error(message: any, trace?: string, context?: string); warn(message: any, context?: string); debug?(message: any, context?: string); verbose?(message: any, context?: string); } @Injectable() export class Logger implements LoggerService { private static logLevels: LogLevel[] = [ 'log', 'error', 'warn', 'debug', 'verbose', ]; private static lastTimestamp?: number; protected static instance?: typeof Logger | LoggerService = Logger; constructor( @Optional() protected context?: string, @Optional() private readonly isTimestampEnabled = false, ) {} error(message: any, trace = '', context?: string) { const instance = this.getInstance(); if (!this.isLogLevelEnabled('error')) { return; } instance && instance.error.call(instance, message, trace, context || this.context); } log(message: any, context?: string) { this.callFunction('log', message, context); } warn(message: any, context?: string) { this.callFunction('warn', message, context); } debug(message: any, context?: string) { this.callFunction('debug', message, context); } verbose(message: any, context?: string) { this.callFunction('verbose', message, context); } setContext(context: string) { this.context = context; } getTimestamp() { return Logger.getTimestamp(); } static overrideLogger(logger: LoggerService | LogLevel[] | boolean) { if (Array.isArray(logger)) { this.logLevels = logger; return; } this.instance = isObject(logger) ? (logger as LoggerService) : undefined; } static log(message: any, context = '', isTimeDiffEnabled = true) { this.printMessage(message, clc.green, context, isTimeDiffEnabled); } static error( message: any, trace = '', context = '', isTimeDiffEnabled = true, ) { this.printMessage(message, clc.red, context, isTimeDiffEnabled, 'stderr'); this.printStackTrace(trace); } static warn(message: any, context = '', isTimeDiffEnabled = true) { this.printMessage(message, clc.yellow, context, isTimeDiffEnabled); } static debug(message: any, context = '', isTimeDiffEnabled = true) { this.printMessage(message, clc.magentaBright, context, isTimeDiffEnabled); } static verbose(message: any, context = '', isTimeDiffEnabled = true) { this.printMessage(message, clc.cyanBright, context, isTimeDiffEnabled); } static getTimestamp() { const localeStringOptions = { year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', day: '2-digit', month: '2-digit', }; return new Date(Date.now()).toLocaleString(undefined, localeStringOptions); } private callFunction( name: 'log' | 'warn' | 'debug' | 'verbose', message: any, context?: string, ) { if (!this.isLogLevelEnabled(name)) { return; } const instance = this.getInstance(); const func = instance && (instance as typeof Logger)[name]; func && func.call( instance, message, context || this.context, this.isTimestampEnabled, ); } protected getInstance(): typeof Logger | LoggerService { const { instance } = Logger; return instance === this ? Logger : instance; } private isLogLevelEnabled(level: LogLevel): boolean { return Logger.logLevels.includes(level); } private static printMessage( message: any, color: (message: string) => string, context = '', isTimeDiffEnabled?: boolean, writeStreamType?: 'stdout' | 'stderr', ) { const output = isPlainObject(message) ? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n` : color(message); const pidMessage = color(`[Nest] ${process.pid} - `); const contextMessage = context ? yellow(`[${context}] `) : ''; const timestampDiff = this.updateAndGetTimestampDiff(isTimeDiffEnabled); const instance = (this.instance as typeof Logger) ?? Logger; const timestamp = instance.getTimestamp ? instance.getTimestamp() : Logger.getTimestamp?.(); const computedMessage = `${pidMessage}${timestamp} ${contextMessage}${output}${timestampDiff}\n`; process[writeStreamType ?? 'stdout'].write(computedMessage); } private static updateAndGetTimestampDiff( isTimeDiffEnabled?: boolean, ): string { const includeTimestamp = Logger.lastTimestamp && isTimeDiffEnabled; const result = includeTimestamp ? yellow(` +${Date.now() - Logger.lastTimestamp}ms`) : ''; Logger.lastTimestamp = Date.now(); return result; } private static printStackTrace(trace: string) { if (!trace) { return; } process.stderr.write(`${trace}\n`); } }