DEV Community

Micael Levi L. C.
Micael Levi L. C.

Posted on

NestJS tip: how to inject the native logger as any other provider

for NestJS v8, v9 and v10

Why?

The Logger class from @nestjs/common is not a built-in provider. So we can't do this:

import { Module, Logger } from '@nestjs/common'

@Module({})
export class AppModule {
  constructor(private readonly logger: Logger) {}

  onModuleInit() {
    this.logger.debug('hi')
  }
}
Enter fullscreen mode Exit fullscreen mode

So we should initialize that class by ourselves. This is good because we can define the context that the logger will have:

// ...
export class AppModule {
  private readonly logger = new Logger(AppModule.name)

  onModuleInit() {
    this.logger.debug('hi')
  }
}
Enter fullscreen mode Exit fullscreen mode

but this is bad because we are not relying on DI (Dependency Injection) as we do for others native classes in our NestJS app, which looks a bit hacky. Also, if we want to ignore or change the behavior of that logger in some test suite, it would be harder than simply using the auto mocking feature from @nestjs/testing or using any other idiomatic approach.

What?

Now we can inject the Logger class as any other provider, like this:

import { Module, Logger, Scope, Inject } from '@nestjs/common'
import { INQUIRER } from '@nestjs/core'

@Module({
  providers: [
    {// Register this provider in the module that you want to inject the logger
      provide: Logger,
      scope: Scope.TRANSIENT,
      inject: [INQUIRER],
      useFactory: (parentClass: object) => new Logger(parentClass.constructor.name),
    }
  ],
})
export class AppModule {
  @Inject()
  private readonly logger: Logger

  // or via constructor-based injection
  //constructor(private readonly logger: Logger) {}

  onModuleInit() {
    this.logger.debug('hi')
  }
}
Enter fullscreen mode Exit fullscreen mode

demo

How?

In order to make that possible, we will have a new factory provider using the transient scope and the INQUIRER native provider.

As per the docs:

Transient providers are not shared across consumers. Each consumer that injects a transient provider will receive a new, dedicated instance.

It means a new instance of the Logger class will be created every time they are injected somewhere, instead of being a singleton as usual. This is good because we can then inject the INQUIRER provider (from @nestjs/core) into that factory provider.

The value of INQUIRER provider will be the class where a provider was constructed. So we can use that value to define the logger context (ie., the first argument on new Logger constructor).

And that's how we can move the initialization of the Logger class to the module definition instead of having them polluting our providers.

Top comments (1)

Collapse
 
kostyatretyak profile image
Костя Третяк

The Logger class from @nestjs/common is not a built-in provider. So we can't do this:

import { Module, Logger } from '@nestjs/common'

@Module({})
export class AppModule {
  constructor(private readonly logger: Logger) {}

  onModuleInit() {
   this.logger.debug('hi')
 }
}

Another strange feature of NestJS. Logger is so fundamental that it should be in @nestjs/core.