DEV Community

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

Posted on • Edited on

NestJS tip: how to always use your custom logger

TL;DR

for NestJS v8, v9 and v10

Since we always want to use our own logger instead of falling back to Nest's native logger in case of initialization errors, you'll need to create the logger instance outside of your root module like this:



// ...
async function createLogger() {
  @Module({
    imports: [LoggerModule.forRoot()],
  })
  class TempModule {}

  const tempApp = await NestFactory.createApplicationContext(TempModule, {
    logger: false,
    abortOnError: false,
  })
  await tempApp.close()
  return tempApp.get(Logger)
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: await createLogger(),
  })
  // ...
  await app.listen(3000)
}


Enter fullscreen mode Exit fullscreen mode

Odd behavior for custom loggers that are defined too late

When we replace Nest's native logger with another one (like nestjs-pino), we might still could face log messages that uses the native one due to some error on creating the application!

Check this out:



import { Module } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { Logger, LoggerModule } from 'nestjs-pino'

@Module({
  imports: [LoggerModule.forRoot()],
})
export class AppModule {
  // This will cause a 'provider not found' error
  constructor(private foo: number) {} 

  onModuleInit() {
    console.log('This will not be invoked due to the initialization error!')
  }
}


async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    bufferLogs: true, // So we could hijack our own logger
  })
  app.useLogger( app.get(Logger) ) // (A)
  await app.listen(3000)
}
bootstrap()


Enter fullscreen mode Exit fullscreen mode

error

As you can see above, although we're using our own logger (line (A)), since we got an initilization error, ie., the app couldn't be created due to some error while resolving its modules, the error log still uses the native logger.

When the app do start with success, the output would look like this:

sucess

How to always use our custom logger

To fix that, we should supply our custom logger to the logger option of NestFactory.create. The limitation here is that that option requires an instance of the logger, see:

opts

In order to create that instance using NestJS DI container, we'll use NestFactory.createApplicationContext method. Its has the same interface as the NestFactory.create method but it only returns the app context instead of the whole app itself, then it will take less steps to finish its job.

The root (or 'entry') module should be minimal as possible. In this example it will only import the module that creates the logger itself. I will use the nestjs-pino because Pino is an amazing logger lib.

Let's say we have the following AppModule:



import { Module } from '@nestjs/common'
import { LoggerModule } from 'nestjs-pino'

@Module({
  imports: [LoggerModule.forRoot()],
})
export class AppModule {}


Enter fullscreen mode Exit fullscreen mode
  • 1) Add a function that will start and return the logger instance created by NestJS DI system:


// ...
import { Logger, LoggerModule } from 'nestjs-pino'

async function createLogger(): Promise<Logger> {
  @Module({
    imports: [LoggerModule.forRoot()],
    //        ^ Make sure you're using the same logger config
    //          that you've used in your AppModule
  })
  class TempModule {}

  const tempApp = await NestFactory.createApplicationContext(TempModule, {
    // Disable the native logger entirely
    // to avoid falling back to the initial problem
    logger: false,
    // thus we must not crash the app on errors at TempModule
    // initialization, otherwise we won't see any error messages
    abortOnError: false,
  })
  await tempApp.close()
  return tempApp.get(Logger) // retrieve the created instance
}


Enter fullscreen mode Exit fullscreen mode
  • 2) Supply that logger instance to NestFactory.create:


async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: await createLogger(),
  })

  await app.listen(3000)
}
bootstrap()


Enter fullscreen mode Exit fullscreen mode

now there's no need to buffer logs (which is good for memory consumption btw), and no need to call app.useLogger too!


Now my app's initialization errors would look like this:

better

or, with pino-pretty:

better pretty

Top comments (0)