Search code examples
typescriptloggingnestjswinston

How to inject custom Service inside of Custom Provider NestJS?


I faced problem using logger in NestJS, even though the building of CUSTOM Modules and Services didn't take too much time. So let's se how it was built. First of all I installed Winston/Nest library to make a Winston Logger Module:

@Module({})
export class WinstonLoggerModule {
  static forRoot(): DynamicModule {
    return {
      imports: [
        WinstonModule.forRoot({
          transports: [
            new winston.transports.Console({
              format: winston.format.combine(
                winston.format.timestamp(),
                winston.format.ms(),
                nestWinstonModuleUtilities.format.nestLike('MyApp', {
                  colors: true,
                  prettyPrint: true,
                }),
              ),
            }),
          ],
          // other options
        }),
      ],
      module: WinstonLoggerModule,
      providers: [
        { provide: WINSTON_LOGGER_SERVICE, useClass: WinstonLoggerService },
      ],
      exports: [WinstonLoggerModule],
    };
  }
}

After that I created a Winston Logger Service

import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';                         // Logger by Winston!!
import { IWinstonLoggerService } from './linterface';

@Injectable()
export class WinstonLoggerService implements IWinstonLoggerService {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
  ) {}

  warn(message): void {
    this.logger.warn(message);
  }

  error(message): void {
    this.logger.error(message);
  }

  debug(message): void {
    this.logger.debug(message);
  }

  info(message): void {
    this.logger.info(message);
  }
}

I guess no need to show here the IWinstonService cause it's pretty obvious. After that, I imported the WinstonLoggerModule into the application module. And I can also inject the WinstonLoggerService into other services to catch some debugs. Like this:

@Injectable()
export class DefaultCacheService implements ICacheService {
  constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
@Inject(WINSTON_LOGGER_SERVICE) private logger: IWinstonLoggerService
) {}
  async checkInCache(expression: string) {

    this.logger.info('Checking Cache...') //    logger!!!

    const value = await this.cacheManager.get(expression);
    return value;
  }
  async setToCache(cacheData: HistoryItemResponseDto) {
    const { expression, result } = cacheData;
    await this.cacheManager.set(expression, result, 0);

    return cacheData;
  }
}

Until this moment everything works fine. But I have to main issues. ** The first ** one is how to inject WinstonLoggerService inside of custom provider so I could use it's methods for logging errors which may happen during some connections for example Mongo DB Provider

type DataBaseProvider = {
  provide: string;
  useFactory: (logger: IWinstonLoggerService) => Promise<typeof mongoose>;
}[];


export const databaseProviders: DataBaseProvider = [
  {
    provide: DATABASE_CONNECTION,
    useFactory: async (HERE!!!): Promise<typeof mongoose> => { //How to pass logger here? Should i pass service or Interface or token?
      try {
        return await mongoose.connect(process.env.DATA_BASE_URI);
      } catch (err) {
       logger.error( err ) // And how to reach the logger here?
        throw new Error(err);
      }
    },
  },
];

And ** the second ** issue is how to log context with my custom logger. I mean, it's one thing to log inputs and outputs inside functions. I would like to log that the service is running in principle. Like the standard Nest.JS logger does it. I mean this:

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name); // How to use here My Custom Logger?

  doSomething() {
    this.logger.log('Doing something...');
  }
}

I would be very grateful if you could help me with these questions. Thanks.


Solution

  • For your first question how to inject a provider into a custom provider, that's what the inject array property is for. So you take your custom provider and update the inject array and the useFactory to be like so

    type DataBaseProvider = {
      provide: string;
      useFactory: (logger: IWinstonLoggerService) => Promise<typeof mongoose>;
    }[];
    
    
    export const databaseProviders: DataBaseProvider = [
      {
        provide: DATABASE_CONNECTION,
        inject: [WINSTON_LOGGER_SERVICE],
        useFactory: async (logger: IWinstonLoggerService): Promise<typeof mongoose> => { //How to pass logger here? Should i pass service or Interface or token?
          try {
            return await mongoose.connect(process.env.DATA_BASE_URI);
          } catch (err) {
           logger.error( err ) // And how to reach the logger here?
            throw new Error(err);
          }
        },
      },
    ];
    

    and then as long as the module has access to the WINSTON_LOGGER_SERVICE it'll all just work. Docs on Custom Providers for more information

    For your second question, so long as you use app.useLogger() in the main.ts then whenever you use Logger from Nest, Nest will properly call to the custom logger you've said to use. This is described in the docs here