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)
}
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()
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:
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:
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 {}
- 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
}
- 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()
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:
or, with pino-pretty
:
Top comments (0)