nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
66.77k stars 7.55k forks source link

Logger with dynamic module that uses useFactory to create a transient logger / add context not working #13692

Closed ochaoui closed 2 months ago

ochaoui commented 2 months ago

Is there an existing issue for this?

Current behavior

I am struggling trying to create a loggerservice that is transient and add automatically it's context. I found this github issue that explains marely how to do it : https://github.com/nestjs/nest/issues/12565 So I tried to implement it but still get an error.

So first I created the LoggerService that implements the generic one :

import {
  LoggerService as NestLoggerService,
  Injectable,
  Scope,
} from '@nestjs/common';
import { Logger, createLogger, format, transports } from 'winston';
import { utilities } from 'nest-winston';
import { ConfigService } from '@nestjs/config';
import { Format } from 'logform';

@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService implements NestLoggerService {
  private readonly logger: Logger;
  private context?: string;

  constructor(private configService: ConfigService) {
    const env: string = this.configService.get<string>('ENV') || 'local';
    let logFormat: Format;

    if (env === 'local') {
      logFormat = format.combine(
        format.timestamp({ format: 'DD/MM/YYYY hh:mm:ss A' }),
        utilities.format.nestLike(),
      );
    } else {
      logFormat = format.json();
    }

    this.logger = createLogger({
      transports: [
        new transports.Console({
          level: this.configService.get<string>('LOG_LEVEL') || 'info',
          format: logFormat,
        }),
      ],
    });
  }

  public log(message: string, context?: string): void {
    this.logger.info(message, { context: context || this.context });
  }

  public info(message: string, context?: string): void {
    this.logger.info(message, { context: context || this.context });
  }

  public error(message: string, stack: string, context?: string): void {
    this.logger.error(message, { stack, context: context || this.context });
  }

  public warn(message: string, context?: string): void {
    this.logger.warn(message, { context: context || this.context });
  }

  public debug(message: string, context?: string): void {
    this.logger.debug(message, { context: context || this.context });
  }

  public setContext(context: string) {
    this.context = context;
  }
}

I created the LoggerModule and tryed to apply what is in the github issue :

import { Global, Module, Scope } from '@nestjs/common';
import { LoggerService } from './logger.service';
import { ConfigService } from '@nestjs/config';

@Global()
@Module({
  providers: [
    {
      scope: Scope.TRANSIENT,
      provide: LoggerService,
      inject: [ConfigService],
      useFactory: (config: ConfigService, instance) => {
        const logger = new LoggerService(config);
        logger.setContext(instance.constructor.name);
        return logger;
      },
    },
  ],
  exports: [LoggerService],
})
export class LoggerModule {}

I add the module to the App Module :

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { configModule } from 'src/config/config.module';
import { CatModule } from 'src/modules/cat/cat.module';
import { LoggerModule } from 'src/log/logger.module';

@Module({
  imports: [configModule, CatModule, LoggerModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Last of all, I add the logger in the main.ts :
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { LoggerService } from './log/logger.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useLogger(await app.resolve(LoggerService));
  await app.listen(3000);
}
bootstrap();

With this setup, I get this error : [Nest] 14869 - 06/13/2024, 2:33:15 PM LOG [NestFactory] Starting Nest application... [Nest] 14869 - 06/13/2024, 2:33:15 PM LOG [InstanceLoader] ConfigHostModule dependencies initialized +13ms [Nest] 14869 - 06/13/2024, 2:33:15 PM LOG [InstanceLoader] ConfigModule dependencies initialized +0ms [Nest] 14869 - 06/13/2024, 2:33:15 PM LOG [InstanceLoader] LoggerModule dependencies initialized +8ms [Nest] 14869 - 06/13/2024, 2:33:15 PM ERROR [ExceptionHandler] Cannot read properties of undefined (reading 'constructor') TypeError: Cannot read properties of undefined (reading 'constructor') at InstanceWrapper.useFactory [as metatype] (/home/ochaoui/code/templates/nest-template/src/log/logger.module.ts:14:36) at Injector.instantiateClass (/home/ochaoui/code/templates/nest-template/node_modules/@nestjs/core/injector/injector.js:368:55) at callback (/home/ochaoui/code/templates/nest-template/node_modules/@nestjs/core/injector/injector.js:65:45) at Injector.resolveConstructorParams (/home/ochaoui/code/templates/nest-template/node_modules/@nestjs/core/injector/injector.js:144:24) at Injector.loadInstance (/home/ochaoui/code/templates/nest-template/node_modules/@nestjs/core/injector/injector.js:70:13)

Normally it should work as explained in the last issue

Minimum reproduction code

https://codesandbox.io/p/devbox/thirsty-wood-ynklrk?workspaceId=2a2a2b80-a9fc-4d5a-8819-b0984cffe8ed&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clxiqf41m0007356ksjbb19yr%2522%252C%2522sizes%2522%253A%255B33.93351940179292%252C66.06648059820708%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clxiqf41m0002356krk3t6ywf%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clxiqf41m0004356k0ttskl49%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clxiqf41m0006356kb4bejlrz%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B74.54371849857559%252C25.456281501424414%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clxiqf41m0002356krk3t6ywf%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxiqf41l0001356k5s70n4xf%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clxiqf41m0002356krk3t6ywf%2522%252C%2522activeTabId%2522%253A%2522clxiqf41l0001356k5s70n4xf%2522%257D%252C%2522clxiqf41m0006356kb4bejlrz%2522%253A%257B%2522id%2522%253A%2522clxiqf41m0006356kb4bejlrz%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxiqf41m0005356kcqn95uej%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522start%2522%252C%2522port%2522%253A3000%252C%2522path%2522%253A%2522%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clxiqf41m0005356kcqn95uej%2522%257D%252C%2522clxiqf41m0004356k0ttskl49%2522%253A%257B%2522id%2522%253A%2522clxiqf41m0004356k0ttskl49%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clxiqf41m0003356kgeq02ryp%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522start%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clxiqf41m0003356kgeq02ryp%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A21.953125%257D

Steps to reproduce

No response

Expected behavior

It should create a logger with the context already setup

Package

Other package

No response

NestJS version

8.1.3

Packages versions

{
  "name": "nest-typescript-starter",
  "private": true,
  "version": "1.0.0",
  "description": "Nest TypeScript starter repository",
  "license": "MIT",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^10.3.2",
    "@nestjs/config": "^3.2.2",
    "@nestjs/core": "^10.3.2",
    "@nestjs/platform-express": "^10.3.2",
    "nest-winston": "^1.10.0",
    "reflect-metadata": "^0.2.1",
    "rxjs": "^7.8.1",
    "winston": "^3.13.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.3.1",
    "@nestjs/schematics": "^10.1.0",
    "@nestjs/testing": "^10.3.2",
    "@swc/cli": "^0.3.9",
    "@swc/core": "^1.4.0",
    "@types/express": "^4.17.21",
    "@types/jest": "^29.5.12",
    "@types/node": "^20.11.16",
    "@types/supertest": "^6.0.2",
    "@typescript-eslint/eslint-plugin": "^6.21.0",
    "@typescript-eslint/parser": "^6.21.0",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "jest": "^29.7.0",
    "prettier": "^3.2.5",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.4",
    "ts-jest": "^29.1.2",
    "ts-loader": "^9.5.1",
    "ts-node": "^10.9.2",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.3.3"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Node.js version

No response

In which operating systems have you tested?

Other

No response

kamilmysliwiec commented 2 months ago

Thank you for taking the time to submit your report! From the looks of it, this could be better discussed on our Discord. If you haven't already, please join here and send a new post in the #⁠ 🐈 nestjs-help forum. Make sure to include a link to this issue, so you don't need to write it all again. We have a large community of helpful members, who will assist you in getting this to work.