diff --git a/packages/common/src/logging/console.spec.ts b/packages/common/src/logging/console.spec.ts index 0fa6bef0..1fc774ef 100644 --- a/packages/common/src/logging/console.spec.ts +++ b/packages/common/src/logging/console.spec.ts @@ -52,6 +52,41 @@ describe('ConsoleLogger', () => { expect(debugSpy).not.toHaveBeenCalled(); }); + it('should use default options when process is undefined', () => { + const originalProcess = global.process; + global.process = undefined as any; + const logger = new ConsoleLogger('test'); + logger.info('This is an info message'); + expect(infoSpy).toHaveBeenCalledTimes(1); + + global.process = originalProcess; + }); + + it.each` + envLogLevel | optionLogLevel + ${'error'} | ${undefined} + ${'ERROR'} | ${'info'} + ${undefined} | ${'error'} + ${'bogus'} | ${'error'} + `( + 'should log only error when environment variable is $envLogLevel and option log level is $optionLogLevel', + ({ envLogLevel, optionLogLevel }) => { + if (envLogLevel) { + process.env.LOG_LEVEL = envLogLevel; + } + const logger = new ConsoleLogger('test', { level: optionLogLevel }); + logger.warn('This is a warning message'); + expect(warnSpy).not.toHaveBeenCalled(); + logger.info('This is an info message'); + expect(infoSpy).not.toHaveBeenCalled(); + logger.debug('This is a debug message'); + expect(debugSpy).not.toHaveBeenCalled(); + + logger.error('This is an error message'); + expect(errorSpy).toHaveBeenCalledTimes(1); + } + ); + it('should create child logger with prefix', () => { const logger = new ConsoleLogger('parent'); const childLogger = logger.child('child'); @@ -61,16 +96,26 @@ describe('ConsoleLogger', () => { expect((childLogger as any).level).toBe('info'); }); - it('should respect environment variable for logging pattern', () => { - process.env.LOG = 'test*'; - const logger = new ConsoleLogger('testLogger'); - logger.info('This should log'); - expect(infoSpy).toHaveBeenCalled(); - - const nonMatchingLogger = new ConsoleLogger('nonMatchingLogger'); - nonMatchingLogger.info('This should not log'); - expect(infoSpy).toHaveBeenCalledTimes(1); - }); + it.each` + envPattern | optionPattern + ${'test*'} | ${undefined} + ${undefined} | ${'test*'} + ${'test*'} | ${'nonMatching*'} + `( + `should respect logging pattern when environment variable is $envPattern and option pattern is $optionPattern`, + ({ envPattern, optionPattern }) => { + if (envPattern) { + process.env.LOG = envPattern; + } + const logger = new ConsoleLogger('testLogger', { pattern: optionPattern }); + logger.info('This should log'); + expect(infoSpy).toHaveBeenCalled(); + + const nonMatchingLogger = new ConsoleLogger('nonMatchingLogger', { pattern: optionPattern }); + nonMatchingLogger.info('This should not log'); + expect(infoSpy).toHaveBeenCalledTimes(1); + } + ); it('should format object messages properly', () => { const logger = new ConsoleLogger('test'); diff --git a/packages/common/src/logging/console.ts b/packages/common/src/logging/console.ts index de4f9846..66038755 100644 --- a/packages/common/src/logging/console.ts +++ b/packages/common/src/logging/console.ts @@ -1,11 +1,23 @@ import { ANSI } from './ansi'; import { ILogger, ILoggerOptions, LogLevel } from './logger'; +const parseLogLevel = (level?: string): LogLevel | undefined => { + const value = level?.toLowerCase(); + switch (value) { + case 'error': + case 'warn': + case 'info': + case 'debug': + return value; + default: + return undefined; + } +}; export class ConsoleLogger implements ILogger { protected readonly name: string; protected readonly level: LogLevel; - private readonly _pattern: RegExp; + private readonly _enabled: boolean; private readonly _levels = { error: 100, warn: 200, @@ -22,8 +34,11 @@ export class ConsoleLogger implements ILogger { constructor(name: string, options?: ILoggerOptions) { this.name = name; - this.level = options?.level || 'info'; - this._pattern = parseMagicExpr(process.env.LOG || '*'); + + const env = typeof process === 'undefined' ? undefined : process.env; + const logNamePattern = env?.LOG || options?.pattern || '*'; + this._enabled = parseMagicExpr(logNamePattern).test(name); + this.level = parseLogLevel(env?.LOG_LEVEL) || options?.level || 'info'; } error(...msg: any[]) { @@ -43,11 +58,11 @@ export class ConsoleLogger implements ILogger { } log(level: LogLevel, ...msg: any[]) { - if (this._levels[level] > this._levels[this.level]) { + if (!this._enabled) { return; } - if (!this._pattern.test(this.name)) { + if (this._levels[level] > this._levels[this.level]) { return; } diff --git a/packages/common/src/logging/logger.ts b/packages/common/src/logging/logger.ts index eb21e210..eaf52d50 100644 --- a/packages/common/src/logging/logger.ts +++ b/packages/common/src/logging/logger.ts @@ -1,7 +1,15 @@ export type LogLevel = 'error' | 'warn' | 'info' | 'debug'; export interface ILoggerOptions { + /** + * Minimum severity level to log. When provided, log entries with lower severity are ignored. + */ readonly level?: LogLevel; + /** + * Logger name filter pattern. May contain wild cards. + * When provided, the logger is only enabled if its name matches the pattern. + */ + readonly pattern?: string; } export interface ILogger {