69

I tried to use the internal Logger of nestjs (described on https://docs.nestjs.com/techniques/logger -> but with no description of how to use it)

But I had problems (tried to inject LoggerService and so on)

Can anybody explain how to do this?

activedecay
  • 10,129
  • 5
  • 47
  • 71
maku_at
  • 1,579
  • 2
  • 13
  • 21

7 Answers7

174

Best practice

Better than accessing the Logger statically is to create an instance for your class:

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

  @Get()
  async get() {
    this.logger.log('Getting stuff');
  }
}

Why is this better?

  1. You can provide a context in the constructor like new Logger(AppController.name) so that the class name (or anything else) will be part of all log messages in this class.

  2. If you at some point want to extend or replace the default LoggerService, you do not need to change any of your application code besides setting the new logger. Your new logger will automatically be used. If you access it statically it will continue to take the default implementation.

const app = await NestFactory.create(AppModule, {logger: new MyLogger()});
  1. You can mock the Logger in your tests:
module.useLogger(new NoOpLogger());
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • in point #2, if you use your own implementation, how does the scope of `AppController.name` come through? – Rahul Patel Apr 26 '19 at 00:10
  • 1
    @RahulPatel This is because of this line in the `Logger` implementation: `context || this.context`. `MyLogger`'s method is hence called with the instance variable `this.context` of `Logger` if it is not explicitly called with a `context`. See here: https://github.com/nestjs/nest/blob/f5d286b5569acfd033272e5c308004e04d6f1d16/packages/common/services/logger.service.ts#L90 – Kim Kern Apr 27 '19 at 14:29
  • 6
    @KimKern having `new Logger` in the code make the code not testable, right? – Reza Jul 28 '19 at 21:37
  • 3
    @RezaRahmati in this case it does not because you can overwrite the Logger with a mock or test implementation with `module.useLogger(new NoOpLogger());` – Kim Kern Jul 28 '19 at 21:44
  • 2
    How does `new Logger` pick up new logger implementation? Is it not the default implementation explicitly called? Does this `Logger` gte injected logger from app under the hood? – Gherman Nov 12 '19 at 13:09
  • 2
    @Gherman The instance created by `new Logger` holds a Logger instance as member variable. All methods internally call the method of `instance`. When you override the `Logger` it just sets that `instance` field. Have a look at the source code: https://github.com/nestjs/nest/blob/master/packages/common/services/logger.service.ts – Kim Kern Nov 12 '19 at 14:08
  • 2
    @KimKern I see. I would not expect Logger to be a Singleton but it is. I wonder why they didn't make it into an injectable service instead. That would be more usual. – Gherman Nov 12 '19 at 15:17
  • How to log with parameters / objects, etc. ? – Hendy Irawan Feb 03 '20 at 10:40
  • @HendyIrawan You can just pass a complex object as parameter to the Logger: `this.logger.log({message: 'error', params})`. This will work as long as the object is stringifyable; has no circular structure. – Kim Kern Feb 03 '20 at 12:00
  • Where are these logs saved/printed? What if I need to print them to console? – a p May 20 '20 at 09:15
  • @apun The default implementation of the `Logger` writes to `stdout`, see [source code](https://github.com/nestjs/nest/blob/baadb3f92593da397952060ec868bb27b9c766bf/packages/common/services/logger.service.ts#L154). If you want to change that, you can overwrite this behavior with a custom logger, see answer above under point 2). – Kim Kern May 20 '20 at 11:16
  • The logger's dependency injection works by setting a member variable ... *confused* I thought NestJS was all about decorators. Why isn't this like an `@Injectable` in the constructor, just like angular does it. That would make more sense, wouldn't it ? – bvdb Jul 24 '20 at 18:57
  • Correction on my previous message: actually dependency injection through the constructor is mentioned on the official log https://docs.nestjs.com/techniques/logger – bvdb Jul 24 '20 at 19:00
  • 2
    Mocking the logger in tests brought me here. Good practice. – Malkaviano Sep 08 '20 at 11:57
  • This is not a better practice at all! You are consuming more memory this method. Creating a logger on every class when you can just simply inject a single instance across your api is tons better and more effective. I have no idea how your answer got 150+ votes! – Dom Aug 03 '22 at 09:08
  • Can someone provide an example of how this service can be tested when using new Logger ? – julio Oct 17 '22 at 16:15
33

You need to import first into your class:

import { Logger } from '@nestjs/common';

and then you can begin with logging:

Logger.log('info')
Logger.warn('warning')
Logger.error('something went wrong! ', error)
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
Dani Mach
  • 441
  • 3
  • 6
6

Best practice is to inject the existing logger.

app.module.ts

import { Logger, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, Logger],
})
export class AppModule {}

And in the app.service.ts

import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class AppService {
  constructor(private readonly logger: Logger) {}

  sayHello() {
    this.logger.log('Hello world!') 
  }
}
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
Adrian H
  • 167
  • 3
  • 4
  • 2
    Can you explain why this is an "even better best practice" please? The example in the NestJS docs does not inject the existing logger. https://docs.nestjs.com/techniques/logger#using-the-logger-for-application-logging – Jarod May 03 '22 at 20:51
  • 2
    I can't speak to why @Adrian likes it better but I like using injection to pass global objects as it allows you change the logger type without changing local definitions. – Nathaniel Johnson May 13 '22 at 14:11
  • There's an example in the NestJS docs in `Injecting a custom logger` section [here](https://docs.nestjs.com/techniques/logger#injecting-a-custom-logger) – Farista Latuconsina Nov 25 '22 at 08:35
4

This answer might be useful for others who are trying with CustomLogger Implementation. I am trying to show a sample custom logger implementation and how it can be injected to the Nestjs framework.

I understand that Nestjs inherently uses pino logger. This is just a custom implementation of logger service (which you can replace with bunyan, winston, etc..) This is the folder structure I use:

> src /  
>   modules /
>      database /
>        ...
>        database.module.ts
>      api /
>        services /
>        controllers /
>        interceptors /
>        middlewares /
>        models /
>        schemas /
>      shared /
>        services /
>           app.util.service.ts
>           pino.logger.service.ts
>        utils / 
>        interceptors /
>        filters /
>        main.ts    
>        app.controller.ts    
>        app.service.ts
>        server.util.service.ts 

This is the main gist of it. So the logger service is implemented as follows

import {Injectable, LoggerService, Scope} from "@nestjs/common";
import * as pino from 'pino';
import {AppUtilService} from "./app.util.service";
import * as os from "os";
import {APP_LOG_REDACT, APP_MESSAGE_KEY} from "../utils/app.constants";

    @Injectable({
        scope: Scope.DEFAULT
    })
    export class PinoLoggerService implements LoggerService{
        constructor(private appUtilService: AppUtilService) {

        }

        logService = (fileNameString): pino.Logger => {
            return pino({
                useLevelLabels: true,
                prettyPrint: this.appUtilService.isDevEnv(),
                // tslint:disable-next-line: object-literal-sort-keys
                messageKey: APP_MESSAGE_KEY,
                level: this.appUtilService.getLogLevel(),
                redact: {
                    paths: APP_LOG_REDACT,
                    censor: '**SECRET-INFO**'
                },
                base: {
                    hostName: os.hostname(),
                    platform: os.platform(),
                    processId: process.pid,
                    timestamp: this.appUtilService.getCurrentLocaleTimeZone(),
                    // tslint:disable-next-line: object-literal-sort-keys
                    fileName: this.appUtilService.getFileName(fileNameString),
                },
            });
        }

        debug(message: any, context?: string): any {
        }

        error(message: any, trace?: string, context?: string): any {
        }

        log(message: any, context?: string): any {
        }

        warn(message: any, context?: string): any {
        }

    }

The custom implementation is implemented with the my specific options in pinojs github I am using fastifyjs instead of express (again to match my prject needs). So I've added the logger in fastify js server options. If you are using express, its better to specify the new custom implementation in the Nest application Adapter as stated above.

My util service that takes care of implementing the fastify server

import * as fastify from "fastify";
import {Http2Server, Http2ServerRequest, Http2ServerResponse} from "http2";
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
import * as fs from "fs";
import * as path from "path";
import * as uuid from "uuid";
import * as qs from "query-string";
import {PinoLoggerService} from "./modules/shared/services/pino.logger.service";
import {AppUtilService} from "./modules/shared/services/app.util.service";
import {AppConstantsService} from "./modules/shared/services/app.constants.service";
import {AppModel} from "./modules/shared/model/app.model";
import {Reflector} from "@nestjs/core";
export class ServerUtilService {
    private logService;
    private appConstantsService;
    private appUtilServiceInstance: AppUtilService;
    private fastifyInstance: fastify.FastifyInstance<Http2Server, Http2ServerRequest, Http2ServerResponse>;
    constructor() {
        this.appUtilServiceInstance = new AppUtilService();
        this.logService = new PinoLoggerService(this.appUtilServiceInstance);
        this.appConstantsService = new AppConstantsService(this.appUtilServiceInstance);
    }

    retrieveAppConstants(): AppModel {
        return this.appConstantsService.getServerConstants();
    }

    retrieveAppUtilService(): AppUtilService {
        return this.appConstantsService;
    }
    createFastifyServerInstance = (): fastify.FastifyInstance<Http2Server, Http2ServerRequest, Http2ServerResponse> => {
        const serverConstants = this.appConstantsService.getServerConstants();
        const httpsOptions = {
            cert: fs.readFileSync(path.join(process.cwd() + '/https-keys/cert.pem')),
            key: fs.readFileSync(path.join(process.cwd() + '/https-keys/key.pem')),

            allowHTTP1: true,
            rejectUnauthorized: true,
        };
        this.fastifyInstance = fastify({

            http2: true,
            https: httpsOptions,
            bodyLimit: 26214400,
            pluginTimeout: 20000,
            genReqId: () => {
                return uuid.v4().toString();
            },
            requestIdHeader: serverConstants.requestIdHeader,
            modifyCoreObjects: true,
            trustProxy: serverConstants.trustProxy,
            ignoreTrailingSlash: true,
            logger: this.logService,
            querystringParser: (str) => {
                return qs.parse(str);
            },
        });
        this.addContentTypeParser();
        return this.fastifyInstance;
    };

    private addContentTypeParser() {
        this.fastifyInstance.addContentTypeParser('*', (req, done) => {
            let data = '';
            req.on('data', chunk => {
                console.log('inside data listener event');
                return data += chunk; });
            req.on('end', () => {
                done(null,data);
            })
        });
    }


}
export const ServerUtilServiceInstance = new ServerUtilService();

And in my main.ts

async function bootstrap() {
  const fastifyServerInstance = 
  ServerUtilServiceInstance.createFastifyServerInstance();
  const serverConstants = ServerUtilServiceInstance.retrieveAppConstants();
  const app: NestFastifyApplication = await NestFactory.create<NestFastifyApplication>(
      AppModule,
      new FastifyAdapter(fastifyServerInstance)
  );
    ....
    ... // global filters, interceptors, pipes
    ....
    await app.listen(serverConstants.port, '0.0.0.0');

}
vijayakumarpsg587
  • 1,079
  • 3
  • 22
  • 39
3

Simply you can use logger for your requirement(for error, for warn).This is the sample code for it.

import {Logger, Injectable} from '@nestjs/common';

@Injectable()
export class EmployersService {
 private readonly logger = new Logger(EmployersService.name);

 findAll() {
  this.logger.log('info message'); //for info
  this.logger.warn('warn message'); //for warn
  this.logger.error('error message'); //for error
 }
}

then output: enter image description here

Anush
  • 151
  • 1
  • 5
2

The answer is simple. There are static methods on the Logger class.

e.g.

static log(message: string, context = '', isTimeDiffEnabled = true) 

Usage:

Logger.log('Only a test');
maku_at
  • 1,579
  • 2
  • 13
  • 21
1

My approach to this is to use an AppLogger service via the NestJS DI, which wraps the NestJS logger. This means:

  • We can easily change/mock the implementation of AppLogger in unit tests (which is a pain with the private readonly logger = new Logger(AppController.name); approach)
  • Our code depends on our own classes/interfaces instead of the NestJS ones, in the sprit of Hexagonal Architecture.

It looks like:

@Injectable()
export class MyService {
  constructor(private readonly logger: AppLogger) {}

  doSomething() {
    this.logger.log('Did something.', MyService.name);
  }
}

@Global()
@Module({
  imports: [],
  controllers: [],
  providers: [
    AppLogger,
    Logger,
  ],
  exports: [AppLogger],
})
export class ConfigModule {}
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class AppLogger {
  constructor(private readonly logger: Logger) {}

  error(message: any, context: string) {
    this.logger.error(message, context);
  }

  warn(message: any, context: string) {
    this.logger.warn(message, context);
  }

  log(message: any, context: string) {
    this.logger.log(message, context);
  }

  debug(message: any, context: string) {
    this.logger.debug(message, context);
  }

  verbose(message: any, context: string) {
    this.logger.verbose(message, context);
  }
}
Ross Motley
  • 210
  • 3
  • 5