nestjsnest-winston

nestjs global inject nestwinston and format


I'm trying to inject nestwinston globally in my nestjs app and also to keep a beautiful format in my console/terminal and in Grafana, and most important to display custom objects where needed, but using a beautiful format, not simply JSON.

My problem is that the nestwinston changes its behavior when is injected globally.

I followed https://github.com/gremo/nest-winston#replacing-the-nest-logger-also-for-bootstrapping, but when I'm running the next code sample, my error log's object is not displayed in log whatever format type I use (tried all of them and the only option was to format the log as simple JSON, which looks awful in Grafana):

import { Injectable, Logger } from '@nestjs/common';
...
  constructor(
    private readonly logger: Logger,
  ) {
    this.logger.log("my message", {myValue: true});
  }

result:

[NestWinston] Info [Bootstrapper] my message - {}

see that {myValue: true} is not displayed in log... even it displays an empty object which I really don't know what it is...


Solution

  • I achived that by creating a custom LoggerService in which I use the nestwinston alongside with a custom logger formatter, then replacing the nestjs default logger with this service, so it will be used everywhere in my project.

    LoggerService:

    import { LoggerService as LS } from '@nestjs/common';
    import * as winston from 'winston';
    const { combine, timestamp, printf } = winston.format;
    import * as Transport from 'winston-transport';
    import {
      utilities as nestWinstonModuleUtilities,
      WinstonModule,
    } from 'nest-winston';
    
    export class LoggerService implements LS {
      private logger: LS;
    
      constructor() {
        this.logger = WinstonModule.createLogger({
          transports: this.logTransports(),
        });
      }
    
      log(message: any, fields?: any) {
        this.logger.log(this.toPrettyJson(message, fields));
      }
      error(message: any, fields?: any) {
        this.logger.error(this.toPrettyJson(message, fields));
      }
      warn(message: any, fields?: any) {
        this.logger.warn(this.toPrettyJson(message, fields));
      }
      debug(message: any, fields?: any) {
        this.logger.debug(this.toPrettyJson(message, fields));
      }
      verbose(message: any, fields?: any) {
        this.logger.verbose(this.toPrettyJson(message, fields));
      }
    
      private toPrettyJson(message: any, fields?: any) {
        let log = {};
        if (typeof message === 'string') {
          log['message'] = message;
        } else if (typeof message === 'object') {
          for (const [key, value] of Object.entries(message)) {
            log[key] = value;
          }
        }
        if (fields) {
          if (typeof fields === 'object') {
            for (const [key, value] of Object.entries(fields)) {
              log[key] = value;
            }
          } else if (typeof fields === 'string') {
            log['context'] = fields;
          }
        }
        return log;
      }
    
      private logTransports = () => {
        const format = combine(
          timestamp(),
          nestWinstonModuleUtilities.format.nestLike(),
        );
    
        const logTransports: Transport[] = [
          new winston.transports.Console({
            format: format,
          }),
        ];
    
        return logTransports;
      };
    }
    
    

    main.ts:

    ...
      const app = await NestFactory.create(AppModule, {
        logger: new LoggerService(),
      });
    ...
    

    then in any module you want to use the new Logger, add it to providers:

    import { Logger } from '@nestjs/common';
    ...
    providers: [Logger...]
    ...
    

    and then to any module's service:

    import { Injectable, Logger } from '@nestjs/common';
    ...
      constructor(
        private readonly logger: Logger,
      ) {
        // method 1
        this.logger.log("my message", {myValue: true});
    
        // method 2
        this.logger.log({message: "my message", myValue: true});
      }
    

    =>

    [NestWinston] Info [Bootstrapper] my message - {"myValue": true}