In this blog post, we will be setting up logging in NestJS with Winston.

At the time of writing this blog, the versions used are NestJS@10.4.15 and Winston@3.17.0

Installing Winston

Let’s start by installing winston. You can use below commands to add winston to your application.

pnpm add winston

or

npm i winston

Setting up Winston

Let us create a logging.service.ts file in a logging folder. We will be using this file to instantiate winston and our logging service.

Below is the folder structure I use in my application. Folder structure

Import winston and create a instance of logger. We will be only using single instance of this logger throughout our application.

// --file-- logging.service.ts

import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  transports: [
    new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), winston.format.simple()) }), // This will log the logging messages to console
    new winston.transports.File({ filename: 'logs/combined.log' }), // This will create a log file in a logs folder
  ],
  format: winston.format.json(), // All you logs will be exported in json format
  level: 'debug', 
});

Creating Logging Service

Now Let’s create a LoggingService class in same logging.service.ts file. To create this Logging Service we need to extend existing logging class.

import winston from 'winston';
import { LoggerService, Injectable, LogLevel } from '@nestjs/common';

const logger = winston.createLogger({
  transports: [new winston.transports.Console(), new winston.transports.File({ filename: 'logs/combined.log' })],
  format: winston.format.json(),
  level: 'debug',
});

@Injectable()
export class AppLoggingService implements LoggerService {
  log(message: any, ...optionalParams: any[]) {
    logger.log('info', message, ...optionalParams);
  }
  error(message: any, ...optionalParams: any[]) {
    logger.error(message, ...optionalParams);
  }
  warn(message: any, ...optionalParams: any[]) {
    logger.warn(message, ...optionalParams);
  }
  debug(message: any, ...optionalParams: any[]) {
    logger.debug(message, ...optionalParams);
  }
  verbose?(message: any, ...optionalParams: any[]) {
    logger.verbose(message, ...optionalParams);
  }
  fatal?(message: any, ...optionalParams: any[]) {
    logger.error(message, ...optionalParams);
  }
  setLogLevels?(levels: LogLevel[]) {
    if (levels && levels.length > 0) {
      const level = levels[0]; // Default to the first level in the array
      logger.level = level;
      logger.info(`Log level set to ${level}`);
    }
  }
}

Create Logging module

We need to export this logging service through a module so that we can access it across our nestJS application.

import { Module } from '@nestjs/common';
import { AppLoggingService } from './logging.service';

@Module({
  providers: [AppLoggingService],
  exports: [AppLoggingService],
})
export class LoggerModule {}

Importing The logging module

You need to import the logging module in your service

@Module({
  imports: [
    AuthModule,
    UsersModule,
    BusinessesModule,
    CustomersModule,
    CustomerTypesModule,
    PrismaModule,
    LoggerModule, // Our logging service
  ],
  controllers: [AppController, AuthController],
  providers: [AppService],
})
export class AppModule {}

Now you can access the service by using

@Injectable()
export class CustomersService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly logger: AppLoggingService, // Nest will automatically inject the service
  ) {}
  // ...other code
  async remove(businessId: string, id: string) {
    try {
      return await this.prisma.customer.delete({
        where: {
          businessId: businessId,
          id: id,
        },
      });
    } catch (err) {
      if (err instanceof PrismaClientKnownRequestError) {
        if (err.code == 'P2025') {
          throw new NotFoundException();
        }
      }
      this.logger.error(`Error Deleting Customer`, { err: err, args: arguments }); // Logging
      throw new InternalServerErrorException();
    }
  }
}

Below is how it is logged

Logging

Now you have a fully configured logging system in your NestJS application using Winston. You can adjust log levels, add multiple transports, and even set up centralized logging services for better monitoring. Logging is a crucial aspect of maintaining and debugging applications, and with Winston’s powerful configuration options, you can ensure that your logs are informative, readable, and scalable.

Next Steps

  • Centralized Logging: Integrate centralized logging services like Datadog, AWS CloudWatch, or Loggly for better management and analysis of logs.
  • Log Rotation: Set up log rotation to prevent log files from consuming too much disk space.
  • Performance Monitoring: Consider integrating logging with application performance monitoring (APM) tools for more detailed insights into your app’s behavior.

Happy coding, and happy logging! 🎉