From 2bc77634b43b7e5eec0d39af97aa47f3c180dc30 Mon Sep 17 00:00:00 2001 From: Jekabs Karklins Date: Tue, 4 Feb 2025 13:23:24 +0100 Subject: [PATCH] Reapply "feat: add colorization option to ConsoleLogger and implement corresponding tests" This reverts commit db90ec63c6391944cf8ee14b60e8183b8ef274fa. --- packages/logger/package.json | 2 +- .../implementation/ConsoleLogger.spec.ts | 113 ++++++++++++++++++ .../loggers/implementation/ConsoleLogger.ts | 30 +++-- 3 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 packages/logger/src/loggers/implementation/ConsoleLogger.spec.ts diff --git a/packages/logger/package.json b/packages/logger/package.json index 640dfe2..4bf0986 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@user-office-software/duo-logger", - "version": "2.3.0", + "version": "2.3.1", "description": "Logger implementation", "author": "SWAP", "license": "ISC", diff --git a/packages/logger/src/loggers/implementation/ConsoleLogger.spec.ts b/packages/logger/src/loggers/implementation/ConsoleLogger.spec.ts new file mode 100644 index 0000000..69abf07 --- /dev/null +++ b/packages/logger/src/loggers/implementation/ConsoleLogger.spec.ts @@ -0,0 +1,113 @@ +import safeStringify from 'fast-safe-stringify'; +import { ConsoleLogger } from './ConsoleLogger'; +import { LEVEL } from '../../enum/Level'; + +describe('ConsoleLogger', () => { + let consoleSpy: jest.SpyInstance; + + beforeEach(() => { + consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + }); + + test('logs info message without color when colorize is false', () => { + const logger = new ConsoleLogger(); // default config, colorize false + const context = { key: 'TestValue' }; + const message = 'Test info message'; + logger.logInfo(message, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain(LEVEL.INFO); + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs warn message with color when colorize is true', () => { + const logger = new ConsoleLogger({ colorize: true }); + const context = { key: 'TestValue' }; + const message = 'Test warn message'; + logger.logWarn(message, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + const expectedLevel = `\x1b[33m${LEVEL.WARN}\x1b[0m`; // yellow for WARN + expect(loggedMessage).toContain(expectedLevel); + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs error message with color when colorize is true', () => { + const logger = new ConsoleLogger({ colorize: true }); + const context = { key: 'TestValue' }; + const message = 'Test error message'; + logger.logError(message, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + const expectedLevel = `\x1b[31m${LEVEL.ERROR}\x1b[0m`; // red for ERROR + expect(loggedMessage).toContain(expectedLevel); + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs debug message without color even when colorize is true', () => { + const logger = new ConsoleLogger({ colorize: true }); + const context = { key: 'TestValue' }; + const message = 'Test debug message'; + logger.logDebug(message, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + // DEBUG messages are not colorized + expect(loggedMessage).toContain(LEVEL.DEBUG); + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs exception details when an Error instance is passed', () => { + const logger = new ConsoleLogger({ colorize: false }); + const error = new Error('Something went wrong'); + const message = 'Exception occurred'; + const context = { key: 'TestValue' }; + + logger.logException(message, error, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain(error.message); + expect(loggedMessage).toContain('stack'); + // because the context is merged, safeStringify(context) should be present + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs exception when a non-Error value is passed', () => { + const logger = new ConsoleLogger({ colorize: false }); + const notError = 'simple string exception'; + const message = 'Exception occurred'; + const context = { key: 'TestValue' }; + + logger.logException(message, notError, context); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + expect(loggedMessage).toContain(message); + expect(loggedMessage).toContain(notError); + expect(loggedMessage).toContain('TestValue'); + }); + + test('logs exception correctly when exception is null', () => { + const logger = new ConsoleLogger({ colorize: false }); + const message = 'Null exception test'; + + logger.logException(message, null); + + expect(consoleSpy).toHaveBeenCalled(); + const loggedMessage = consoleSpy.mock.calls[0][0]; + expect(loggedMessage).toContain(message); + }); +}); diff --git a/packages/logger/src/loggers/implementation/ConsoleLogger.ts b/packages/logger/src/loggers/implementation/ConsoleLogger.ts index 40f4d77..40f1285 100644 --- a/packages/logger/src/loggers/implementation/ConsoleLogger.ts +++ b/packages/logger/src/loggers/implementation/ConsoleLogger.ts @@ -4,6 +4,12 @@ import { LEVEL } from '../../enum/Level'; import { Logger } from '../Logger'; export class ConsoleLogger implements Logger { + private readonly colorize: boolean; + + constructor(config: { colorize: boolean } = { colorize: false }) { + this.colorize = config.colorize; + } + logInfo(message: string, context: Record) { this.log(LEVEL.INFO, message, context); } @@ -47,23 +53,27 @@ export class ConsoleLogger implements Logger { } log(level: LEVEL, message: string, context: Record) { + // Color definitions const colorReset = '\x1b[0m'; const colorRed = '\x1b[31m'; const colorYellow = '\x1b[33m'; const colorBold = '\x1b[1m'; let formattedLevel: string = level; - switch (level) { - case LEVEL.INFO: - formattedLevel = `${colorBold}${level}${colorReset}`; - break; - case LEVEL.ERROR: - formattedLevel = `${colorRed}${level}${colorReset}`; - break; - case LEVEL.WARN: - formattedLevel = `${colorYellow}${level}${colorReset}`; - break; + if (this.colorize) { + switch (level) { + case LEVEL.INFO: + formattedLevel = `${colorBold}${level}${colorReset}`; + break; + case LEVEL.ERROR: + formattedLevel = `${colorRed}${level}${colorReset}`; + break; + case LEVEL.WARN: + formattedLevel = `${colorYellow}${level}${colorReset}`; + break; + } } + // If not colorized, use plain level text console.log( `[${new Date().toISOString()}] ${formattedLevel} - ${message} \n ${safeStringify(