loggingnestjswinstonnestjs-confignest-winston

NestJS: How to customise log messages to include request id and name of the file the log message occurred


I am new to NestJS and would like to customise the log messages to include the x-request-id/x-correlation-id and the name of the file the log message originated but am not sure if there is anything in NestJS to do that.

My application is using NestJS with the Fastify adapter and has the following configuration in the bootstrap() function

  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
    {
        logger: WinstonModule.createLogger(winston.createLogger({
          exitOnError: false,
          level: 'debug',
          handleExceptions: true,
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.ms(),
            winston.format.colorize(),
            winston.format.align(),
            winston.format.splat(),
            winston.format.printf((info) => {
                return `${info.timestamp} [ ${info.level} ] : ${info.message}`;
            }),
          ),
          transports: [
            new (winston.transports.Console)()
          ]
        }),
      )
    }
  );

This seems to format the logs using winston as expected.

2022-03-09T11:21:22.131Z [ info ] : Starting Nest application...

However, I would also like to include the request/correlation id in the message and the name of the file the log message occurred e.g.

2022-03-09T11:21:22.131Z 2cfd4eee-ca2b-4869-b66b-2b7da291f567 [ info ] [ Main.ts ]: Starting Nest application...

Is there anything in NestJS itself to allow this or any external libraries that I could use to achieve the desired result ?


Solution

  • I managed to get it working using the nest-pino library:

    // main.ts
    
    import { Logger } from 'nestjs-pino';
    
    async function bootstrap() {
    
    const app = await NestFactory.create<NestFastifyApplication>(
        AppModule,
        new FastifyAdapter(),
        { bufferLogs: true }
      );
    
      app.useLogger(app.get(Logger));
      
    }
    bootstrap();

    // app.module.ts
    
    import { LoggerModule } from 'nestjs-pino';
    
    @Module({
      imports: [
        LoggerModule.forRoot({
          pinoHttp: {
            level: process.env.LOG_LEVEL || 'debug',
            redact: ['request.headers.authorization'],
            prettyPrint: {
              colorize: true,
              singleLine: true,
              levelFirst: false,
              translateTime: "yyyy-MM-dd'T'HH:mm:ss.l'Z'",
              messageFormat: "{req.headers.x-correlation-id} [{context}] {msg}",
              ignore: "pid,hostname,context,req,res,responseTime",
              errorLikeObjectKeys: ['err', 'error']
            }
          }
        }),
      ],
      controllers: [MyController],
    })
    export class AppModule {}

    // my.controller.ts
    import { Controller, Get, Param, Logger } from '@nestjs/common';
    
    @Controller()
    export class MyController {
        private readonly logger: Logger = new Logger(MyController.name);
    
        @Get('/:id')
        async getCustomerDetails(@Headers() headers, @Param('id') id: string): Promise<Customer> {
            this.logger.log(`Accepted incoming request with id: ${id}`);
    
            // Do some processing ....
    
            return customer;
        }
    }

    [2022-11-14T11:03:07.100Z] INFO: 428f0df9-d12b-4fca-9b11-805a13ff41be [MyController] Accepted incoming request with id: 1

    ****** UPDATE **********

    I also managed to update the redacted fields to be configurable from a .yaml file

    // app.module.ts
    
    import { ConfigModule, ConfigService } from '@nestjs/config';
    import { LoggerModule } from 'nestjs-pino';
    
    @Module({
      imports: [
        LoggerModule.forRootAsync({
          imports: [ConfigModule],
          useFactory: async (configService: ConfigService) => ({
            pinoHttp: {
              level: process.env.LOG_LEVEL || 'info',
              redact: configService.get<string[]>('logger.redacted.fields'),
              prettyPrint: {
                colorize: false,
                singleLine: true,
                levelFirst: false,
                translateTime: "yyyy-mm-dd'T'HH:MM:ss.l'Z'",
                messageFormat: '{req.headers.x-correlation-id} [{context}] {msg}',
                ignore: 'pid,hostname,context,req,res,responseTime',
                errorLikeObjectKeys: ['err', 'error'],
              },
            },
          }),
          inject: [ConfigService],
        }),
      ],
      controllers: [MyController],
    })
    export class AppModule {}

    dev.yaml

    logger:
        redacted:
            fields:
                - 'headers.Authorization'
                - 'headers["X-Api-Key"]'
    

    ****** FURTHER UPDATE ******

    This article on medium also describes how to add correlation ids to log messages with winston

    https://medium.com/@jose-luis-navarro/logging-on-nestjs-like-a-pro-with-correlation-ids-log-aggregation-winston-morgan-and-more-d03e3bb56772