Good logging hygiene in Nest.js

30 December 2024 | Eric

Yes, I know a lot of you still use console.log to debug your application. This is not so surprising as it is straightforward to use and provide immediate feedback. The issue is that more often than we would like, some of the logs end up in production where they pollute the system with useless information.

Logging is also important when it comes to analysis of failures. You can read a great article about this on Daniel Gerlach’s blog.

In his article, I particularly like this statement:

Rule of Silence: When a program has nothing surprising to say, it should say nothing.

Having a good logging hygiene is important and logging everything will certainly spam your log stream. In the end you won’t read it anymore. Instead, what we are aiming for is to log specifically what we want to see, like errors, or warnings, and avoid having debug information in production.

Let’s see how to do this in Nest.js! 🚀

Set the log level properly

Nest.js allows to set the log level during the application bootstrap. This is great because as we said we probably don’t need debug logs in production, but we still want to keep them in our code for the convenience of debugging when we work on your project.

Let’s do that in main.ts:

const logger: LogLevel[] = process.env.NODE_ENV === 'development'
                                                  ? ['fatal', 'error', 'warn', 'log', 'debug']
                                                  : ['fatal', 'error', 'warn', 'log'];
const app = await NestFactory.create(AppModule, { logger });

Simply, you define the levels you want to see and nothing else will show. Here, we show only fatal, error, warn, and log in production but we also show debug while developing locally.

FastifyAdapter

If you use the Fastify adapter (you should!), it’s a bit different as Fastify uses Pino under the hood, but we want to disable it completely.

const logger: LogLevel[] = process.env.NODE_ENV === 'development'
                                                  ? ['fatal', 'error', 'warn', 'log', 'debug']
                                                  : ['fatal', 'error', 'warn', 'log'];
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter({ logger: false }), // <- Disable Pino
  { logger }
);

NestCommander

NestCommander works exactly the same as the default ExpressAdapter:

const logger: LogLevel[] = process.env.NODE_ENV === 'development'
                                                  ? ['fatal', 'error', 'warn', 'log', 'debug']
                                                  : ['fatal', 'error', 'warn', 'log'];
await CommandFactory.run(AppModule, { logger });

Test AppController

You can use the test controller provided below to test the difference. Obviously, this works only if you use the Nest.js Logger. It doesn’t work with console.log, so simply stop using it. 😉

@Controller({})
export class AppController {
  logger = new Logger(AppController.name);

  @Get("log")
  getLog(): string {
    this.logger.debug("LogLevel[debug]");
    this.logger.verbose("LogLevel[verbose]");
    this.logger.log("LogLevel[log]");
    this.logger.warn("LogLevel[warn]");
    this.logger.error("LogLevel[error]");
    this.logger.fatal("LogLevel[fatal]");
  }
}

The result will be something like this:

Not so big

Enjoy logging! ❤️