nestjswinstonmorgan

NestJs conbining the output of Morgan and Nest-Winston in a file


I am trying to log all express logs and unhandled exceptions in a file using nest - winston. Followed following articles ::

  1. https://www.npmjs.com/package/nest-winston
  2. https://www.section.io/engineering-education/logging-with-winston/
  3. Node.js - logging / Use morgan and winston

I researched about it and found that one should use morgan for logging express logs.

const winston = require("winston");
const morgan = require("morgan");

async function bootstrap() {
  const appOptions = {
    cors: true,
    logger: WinstonModule.createLogger({
      transports: [
        new winston.transports.Console({}),   // ==> 1
        new winston.transports.File({
          filename:
            "logs/Combined-" + new Date(Date.now()).toDateString() + ".log",
          level: "info",
          handleExceptions: true,
             }),
        new winston.transports.File({
          filename:
            "logs/Errors-" + new Date(Date.now()).toDateString() + ".log",
          level: "error",
        }),
      ],

      format: winston.format.combine(
        winston.format.timestamp({
          format: "MMM-DD-YYYY HH:mm:ss",
        }),
        winston.format.printf(
          (error) => `${error.level}: ${[error.timestamp]}: ${error.message}`
        )
      ),
    }),
  };
  const app = await NestFactory.create(ApplicationModule, appOptions);
  app.setGlobalPrefix("api");
  
  app.use(morgan("combined", { stream: winston.stream.write }));  // ==> 2

  const options = new DocumentBuilder()
    .setTitle("xx")
    .setDescription("xx")
    .setVersion("1.0")
    .setBasePath("api")
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup("/docs", app, document);

  await app.listen(3000);
}
bootstrap();

Winston is correctly logging the data to the files.

error: Jul-08-2021 18:12:34: Input data validation failed
error: Jul-08-2021 18:26:28: Input data validation failed
error: Jul-08-2021 18:27:09: Input data validation failed
error: Jul-08-2021 20:57:52: Input data validation failed
info: Jul-08-2021 21:47:40: Mapped {/api/pricing/:id, GET} route
info: Jul-08-2021 21:47:40: Mapped {/api/route/:slug, DELETE} route
info: Jul-08-2021 21:47:40: Nest application successfully started

Now I wanna log all the express logs for which I inserted morgan, as shown in code (Point 2).It logs to the console, but doesnt log to the file. But if I comment out point 1 , ie logging to console. The project doesn't start. It gets stuck after below 2 lines. I waited for 15 mins but no progress.

[nodemon] restarting due to changes...
[nodemon] starting `node ./index index.js`

Solution

  • Sorry for the late response. I have used a nest winston for logging exceptions and a middleware which logs the express queries.

    My main.ts file looks like this.

    import { NestFactory } from "@nestjs/core";
    import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
    import { WinstonModule } from "nest-winston";
    import { transports } from "winston";
    import { ApplicationModule } from "./app.module";
    const winston = require("winston");
    
    async function bootstrap() {
      const appOptions = {
        cors: true,
        logger: WinstonModule.createLogger({
          transports: [
            // new winston.transports.Console({}),
            new winston.transports.File({
              filename:
                "logs/Combined-" + new Date(Date.now()).toDateString() + ".log",
              level: "info",
              handleExceptions: true,
                 }),
            new winston.transports.File({
              filename:
                "logs/Errors-" + new Date(Date.now()).toDateString() + ".log",
              level: "error",
            }),
          ],
          exceptionHandlers: [
            new transports.File({ filename: 'logs/exceptions.log' })
          ],
          
          format: winston.format.combine(
            winston.format.timestamp({
              format: "DD/MM/YYYY, HH:mm:ss",
            }),
            winston.format.printf(
              (error) => `[Nest] 5277   - ${[error.timestamp]}  [${error.context}] :  ${error.level}: ${error.message}`
            )
          ),
        }),
      };
      const app = await NestFactory.create(ApplicationModule, appOptions);
      
      app.setGlobalPrefix("api");
      
      const options = new DocumentBuilder()
        .setTitle("xxx")
        .setDescription("xxx")
        .setVersion("1.0")
        .setBasePath("api")
        .addBearerAuth()
        .build();
      const document = SwaggerModule.createDocument(app, options);
      SwaggerModule.setup("/docs", app, document);
    
      await app.listen(3000);
    }
    bootstrap();
    
    

    Middleware Configuration looks like this.

    
    import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
    import { Request, Response, NextFunction } from 'express';
    
    @Injectable()
    export class LoggerMiddleware implements NestMiddleware {
      private logger = new Logger('HTTP');
    
      use(request: Request, response: Response, next: NextFunction): void {
        const { ip, method, originalUrl } = request;
        const userAgent = request.get('user-agent') || '';
    
        response.on('finish', () => {
          const { statusCode } = response;
          const contentLength = response.get('content-length');
    
          this.logger.log(
            `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
          );
        });
    
        next();
      }
    }
    

    Finally I have configured my middleware in app.module.ts

    export class ApplicationModule implements NestModule{
      configure(consumer: MiddlewareConsumer) {
        consumer.apply(LoggerMiddleware).forRoutes('*');
      }
      constructor(private readonly connection: Connection) {}
    }