From 522c64775d3a254110699d13fbcd735af217796a Mon Sep 17 00:00:00 2001 From: Egor Kartashov Date: Sat, 20 Jan 2024 14:02:40 +0600 Subject: [PATCH] fix(logger): tests --- .../interfaces/logger.interfaces.ts | 4 ++ src/modules/logger/logger.adapter.ts | 58 +++++++++++++++---- src/modules/logger/logger.interfaces.ts | 14 ++++- src/modules/logger/logger.module.ts | 55 +++++++++++++++--- src/modules/logger/logger.spec.ts | 36 +++++++++--- tests/logger.spec.ts | 32 ++++++++-- tests/tests.factory.ts | 5 +- 7 files changed, 170 insertions(+), 34 deletions(-) diff --git a/src/infrastructure/interfaces/logger.interfaces.ts b/src/infrastructure/interfaces/logger.interfaces.ts index 3c72033..87ebe42 100644 --- a/src/infrastructure/interfaces/logger.interfaces.ts +++ b/src/infrastructure/interfaces/logger.interfaces.ts @@ -18,6 +18,10 @@ export interface ILoggerModuleOptions { opts: ILoggerOptions; } +export interface ILoggerTransportsModuleOptions { + transports: unknown[]; +} + export enum LogLevels { Silent = 'silent', Debug = 'debug', diff --git a/src/modules/logger/logger.adapter.ts b/src/modules/logger/logger.adapter.ts index 2fb51ee..aa74c0b 100644 --- a/src/modules/logger/logger.adapter.ts +++ b/src/modules/logger/logger.adapter.ts @@ -1,44 +1,78 @@ -import { ConsoleLogger, Injectable } from '@nestjs/common'; +import { ConsoleLogger, Inject, Injectable } from '@nestjs/common'; import { LoggerService } from '@nestjs/common/services/logger.service'; -import { ILoggerPort, ILogPayload, LogLevel } from '../../infrastructure/interfaces/logger.interfaces'; +import { + ILoggerModuleOptions, + ILoggerPort, + ILogPayload, + LogLevel, +} from '../../infrastructure/interfaces/logger.interfaces'; +import { InjectLoggerBase, InjectLoggerOpts } from './logger.decorators'; +import { INQUIRER } from '@nestjs/core'; +import { InjectContextRepository } from '../context/context.decorators'; +import { IContextRepository } from '../context'; @Injectable() export class LoggerAdapter extends ConsoleLogger implements LoggerService { - public constructor(private logger: ILoggerPort) { - super(); + private readonly _source: string; + private readonly _logger: ILoggerPort; + private readonly _configs: ILoggerModuleOptions; + private readonly _contextRepository: IContextRepository; + + constructor( + @Inject(INQUIRER) parentClass: object, + @InjectLoggerOpts() + configs: ILoggerModuleOptions, + @InjectLoggerBase() + logger: ILoggerPort, + @InjectContextRepository() + contextRepository: IContextRepository, + ) { + super() + // Set the source class from the parent class + this._source = parentClass?.constructor?.name; + this._configs = configs; + this._logger = logger; + this._contextRepository = contextRepository; } public setLogLevels(levels: LogLevel[]): void { - this.logger.setLogLevels(levels); + this._logger.setLogLevels(levels); } public log(message: any, ...optionalParams: any[]): void { - return this.logger.info(message, this.getLogData(optionalParams)); + return this._logger.info(message, this.getLogData(optionalParams)); } public error(message: any, ...optionalParams: any[]): void { - return this.logger.error(message, this.getLogData(optionalParams)); + return this._logger.error(message, this.getLogData(optionalParams)); } public warn(message: any, ...optionalParams: any[]): void { - return this.logger.warn(message, this.getLogData(optionalParams)); + return this._logger.warn(message, this.getLogData(optionalParams)); } public debug(message: any, ...optionalParams: any[]): void { - return this.logger.debug(message, this.getLogData(optionalParams)); + return this._logger.debug(message, this.getLogData(optionalParams)); } public verbose(message: any, ...optionalParams: any[]): void { - return this.logger.verbose(message, this.getLogData(optionalParams)); + return this._logger.verbose(message, this.getLogData(optionalParams)); } private getLogData(...optionalParams: any[]): ILogPayload { - const source = optionalParams[0]?.length ? optionalParams[0] : this.context; + const data = optionalParams?.[0]?.length ? optionalParams[0][0] : {}; + return { - source, + ...data, + organization: data?.organization || this._configs?.opts?.meta?.organization || '', + context: data?.context || this._configs?.opts?.meta?.context || '', + app: data?.app || this._configs?.opts?.meta?.app || '', + source: data?.source || this._source, + correlationId: + data?.correlationId || this._contextRepository.getContextId(), }; } diff --git a/src/modules/logger/logger.interfaces.ts b/src/modules/logger/logger.interfaces.ts index 0242011..8423666 100644 --- a/src/modules/logger/logger.interfaces.ts +++ b/src/modules/logger/logger.interfaces.ts @@ -1,5 +1,8 @@ import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; -import { ILoggerModuleOptions } from '../../infrastructure/interfaces/logger.interfaces'; +import { + ILoggerModuleOptions, + ILoggerTransportsModuleOptions, +} from '../../infrastructure/interfaces/logger.interfaces'; export interface ILoggerConfigFactory { createLoggerConfig(): Promise | ILoggerModuleOptions; @@ -8,3 +11,12 @@ export interface ILoggerConfigFactory { export interface ILoggerAsyncOptions extends Pick { useExisting: Type; } + + +export interface ILoggerTransportsConfigFactory { + createTransportsConfig(): Promise | ILoggerTransportsModuleOptions; +} + +export interface ILoggerTransportsAsyncOptions extends Pick { + useExisting: Type; +} diff --git a/src/modules/logger/logger.module.ts b/src/modules/logger/logger.module.ts index ac2ca83..ac1635e 100644 --- a/src/modules/logger/logger.module.ts +++ b/src/modules/logger/logger.module.ts @@ -2,8 +2,16 @@ import { DynamicModule, Global, Module, Provider } from '@nestjs/common'; import { LoggerConstants } from './logger.constants'; import { LoggerService } from './logger.service'; import { ContextModule } from '../context'; -import { ILoggerAsyncOptions, ILoggerConfigFactory } from './logger.interfaces'; -import { ILoggerModuleOptions, ILoggerPort } from '../../infrastructure/interfaces/logger.interfaces'; +import { + ILoggerAsyncOptions, + ILoggerConfigFactory, + ILoggerTransportsAsyncOptions, + ILoggerTransportsConfigFactory, +} from './logger.interfaces'; +import { + ILoggerModuleOptions, + ILoggerTransportsModuleOptions, +} from '../../infrastructure/interfaces/logger.interfaces'; import { LoggerAdapter } from './logger.adapter'; import { WinstonConstants } from '../../infrastructure/adapters/winston/winston.constants'; import { ConsoleTransport } from '../../infrastructure/adapters/winston/transports'; @@ -15,7 +23,41 @@ import { WinstonAdapter } from '../../infrastructure/adapters/winston'; }) export class LoggerModule { - // TODO: Add ForFeatureAsync method to change transports + public static forTransports(options: ILoggerTransportsModuleOptions): Provider { + const WinstonTransportsProvider: Provider = { + provide: WinstonConstants.winstonTransports, + useFactory: (opts: ILoggerModuleOptions) => { + const transports: unknown[] = [ + ConsoleTransport.create(opts?.opts?.console), + ...options.transports, + ]; + + return transports; + }, + inject: [LoggerConstants.options], + } + + return WinstonTransportsProvider; + } + + public static forTransportAsync(asyncOptions: ILoggerTransportsAsyncOptions): Provider { + const WinstonTransportsProvider: Provider = { + provide: WinstonConstants.winstonTransports, + useFactory: async (optionsFactory: ILoggerTransportsConfigFactory, opts: ILoggerModuleOptions) => { + const transportOpts = await optionsFactory.createTransportsConfig() + + const transports: unknown[] = [ + ConsoleTransport.create(opts?.opts?.console), + ...transportOpts.transports, + ]; + + return transports; + }, + inject: [asyncOptions.useExisting, LoggerConstants.options], + } + + return WinstonTransportsProvider; + } public static forRoot(options: ILoggerModuleOptions): DynamicModule { const LoggerOptionsProvider: Provider = { @@ -46,8 +88,7 @@ export class LoggerModule { const LoggerAdapterProvider: Provider = { provide: LoggerAdapter, - useFactory: (logger: ILoggerPort) => new LoggerAdapter(logger), - inject: [LoggerConstants.loggerBase], + useClass: LoggerAdapter, }; return { @@ -62,6 +103,7 @@ export class LoggerModule { exports: [ LoggerAdapterProvider, LoggerServiceProvider, + LoggerOptionsProvider, ], }; } @@ -98,8 +140,7 @@ export class LoggerModule { const LoggerAdapterProvider: Provider = { provide: LoggerAdapter, - useFactory: (logger: ILoggerPort) => new LoggerAdapter(logger), - inject: [LoggerConstants.loggerBase], + useClass: LoggerAdapter, }; return { diff --git a/src/modules/logger/logger.spec.ts b/src/modules/logger/logger.spec.ts index a33a12a..a67dc2d 100644 --- a/src/modules/logger/logger.spec.ts +++ b/src/modules/logger/logger.spec.ts @@ -1,11 +1,12 @@ -import { jest, beforeEach, describe, expect, it } from '@jest/globals'; +import { beforeEach, describe, expect, it, jest } from '@jest/globals'; import { Test } from '@nestjs/testing'; import { LoggerModule } from './logger.module'; import { LoggerAdapter } from './logger.adapter'; -import { ILoggerPort } from '../../infrastructure/interfaces/logger.interfaces'; +import { ILoggerPort, LogLevels } from '../../infrastructure/interfaces/logger.interfaces'; import { LoggerConstants } from './logger.constants'; +import winston from 'winston'; -describe('Logger', () => { +describe('Logger Unit Tests', () => { let logger: LoggerAdapter | undefined; let adapter: ILoggerPort | undefined; @@ -16,16 +17,29 @@ describe('Logger', () => { LoggerModule.forRoot({ opts: { hideTrace: false, - meta: {}, + meta: { + organization: 'Example LLC', + context: 'example-context', + app: 'example-service', + }, console: { - format: 'pretty', + format: 'json', }, } }) ], + providers: [ + LoggerModule.forTransports({ + transports: [new winston.transports.File({ + // lazy: true, + filename: 'app.log' + })], + }), + ] }).compile(); logger = moduleRef.get(LoggerAdapter); + logger?.setLogLevels([LogLevels.Log, LogLevels.Debug]) adapter = moduleRef.get(LoggerConstants.loggerBase); }) @@ -36,12 +50,18 @@ describe('Logger', () => { it('LoggerAdapter should log', () => { // @ts-ignore - const logSpy = jest.spyOn(adapter, 'info'); + const logSpy = jest.spyOn(adapter, 'debug'); - logger?.log('test'); + logger?.debug("Example Message", { + source: 'ExampleService', + event_name: "example_executed", + props: { + user_id: "123" + } + }); expect(logSpy).toHaveBeenCalled(); - expect(logSpy.mock.calls[0][0]).toEqual('test'); + expect(logSpy.mock.calls[0][0]).toEqual('Example Message'); expect(logger).toBeDefined(); logSpy.mockRestore(); }); diff --git a/tests/logger.spec.ts b/tests/logger.spec.ts index a914725..ee20baa 100644 --- a/tests/logger.spec.ts +++ b/tests/logger.spec.ts @@ -1,6 +1,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, jest, test } from '@jest/globals'; -import { FactoryTests } from './tests.factory'; import { LoggerModule } from '../modules'; +import { LoggerAdapter } from '../modules/modules/logger/logger.adapter'; +import { FactoryTests } from './tests.factory'; describe('LoggerModule', () => { const fixture = new FactoryTests(); @@ -30,9 +31,32 @@ describe('LoggerModule', () => { jest.resetAllMocks(); }); - describe('Logger', () => { - test('Logger', () => { - expect(true).toBeDefined(); + describe('Logger E2E Tests', () => { + test('LoggerAdapter should be defined', () => { + const adapter = fixture.application.get(LoggerAdapter); + expect(adapter).toBeDefined(); + }); + + + test('LoggerAdapter should log', () => { + const adapter = fixture.application.get(LoggerAdapter); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const logSpy = jest.spyOn(adapter, 'debug'); + + adapter?.debug('Example Message', { + source: 'ExampleService', + event_name: 'example_executed', + props: { + user_id: '123' + } + }); + + expect(logSpy).toHaveBeenCalled(); + expect(logSpy.mock.calls[0][0]).toEqual('Example Message'); + expect(adapter).toBeDefined(); + logSpy.mockRestore(); }); }); }); diff --git a/tests/tests.factory.ts b/tests/tests.factory.ts index 7aad0f3..ff7078f 100644 --- a/tests/tests.factory.ts +++ b/tests/tests.factory.ts @@ -1,6 +1,7 @@ import { ModuleMetadata } from '@nestjs/common/interfaces'; import { NestApplication } from '@nestjs/core'; import { Test } from '@nestjs/testing'; +import { LoggerAdapter } from '../modules/modules/logger/logger.adapter'; // import { LoggerAdapter } from '../src/modules/logger/logger.adapter'; export class FactoryTests { @@ -24,8 +25,8 @@ export class FactoryTests { }); // TODO: Fix this - // const logger = this._application.get(LoggerAdapter, { strict: false }); - // this._application.useLogger(logger); + const logger = this._application.get(LoggerAdapter, { strict: false }); + this._application.useLogger(logger); await this.application.listen(port); }