How to use nestjs Logging service
Asked Answered
S

8

75

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?

Spoilsman answered 4/10, 2018 at 15:51 Comment(1)
you wanna use logger in all service automatically?!Vocoid
F
181

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());
Fairbanks answered 20/10, 2018 at 16:21 Comment(17)
in point #2, if you use your own implementation, how does the scope of AppController.name come through?Butene
@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: github.com/nestjs/nest/blob/…Fairbanks
@KimKern having new Logger in the code make the code not testable, right?Hokeypokey
@RezaRahmati in this case it does not because you can overwrite the Logger with a mock or test implementation with module.useLogger(new NoOpLogger());Fairbanks
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?Osteal
@Osteal 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: github.com/nestjs/nest/blob/master/packages/common/services/…Fairbanks
@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.Osteal
How to log with parameters / objects, etc. ?Screening
@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.Fairbanks
Where are these logs saved/printed? What if I need to print them to console?Clavier
@apun The default implementation of the Logger writes to stdout, see source code. If you want to change that, you can overwrite this behavior with a custom logger, see answer above under point 2).Fairbanks
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 ?Kathiekathleen
Correction on my previous message: actually dependency injection through the constructor is mentioned on the official log docs.nestjs.com/techniques/loggerKathiekathleen
Mocking the logger in tests brought me here. Good practice.Porridge
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!Illness
Can someone provide an example of how this service can be tested when using new Logger ?Bryce
Same issue here. I need to test calls to the logger. Since this is not injected I am unsure how to get access to it or mock it in a test. I would prefer to inject the logger in a class usage, but then I seem to lose the ability to set a name and provide options for it. Why is testing stuff in NestJS so strange?Trudy
H
40

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)
Hyperactive answered 13/11, 2018 at 13:46 Comment(6)
Works, but Kim's answer is better, because you have also the name of the class within the outputRegen
I tried this and it does not invoke custom implementation if used without new.Osteal
Why did I have to scroll down so much is something I don't understand. Thank you.Delftware
@Regen it depends on what you want the logging for. This is a great first answer for getting a first log output to the screen.Bulky
Very usefull to log from main.ts fileSeptuagint
@Regen I had to use this way inside the constructor of my service, since accessing the member logger instance is not possible there. You can still pass the context, i.e. the name of the class: Logger.log('my message', MyServiceClass.name)Compendium
D
9

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!') 
  }
}
Debit answered 22/4, 2022 at 13:16 Comment(6)
Can you explain why this is an "even better best practice" please? The example in the NestJS docs does not inject the existing logger. docs.nestjs.com/techniques/…Yorkist
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.Eggleston
There's an example in the NestJS docs in Injecting a custom logger section hereCatawba
DI (dependency injection) pattern is the core of the NestJS, everything is built around of DI in NestJS. In the docs they mention logger in constructor as new Logger(), but it is an antipattern. Class should never new up any dependency, as you will be stuck with testing. Google about this antipattern. A better approach is to use DI, as it is in this answer. The only thing that I always do in my approach is set context in the constructor like this: logger.setContext(AppService.name);, so next time in your code when you use methods like log(), you don't need explicitly set context.Bastardize
@VitaliyMarkitanov Where are you getting logger.setContext(...) from? That method does not exist for me. How can I use an injected logger AND set the context to the current class name?Trudy
@ChrisBarr, if your logger inherits from class ConsoleLogger implements LoggerService, it does have setContext(), see: node_modules\@nestjs\common\services\console-logger.service.d.tsBastardize
W
5

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');

}
Weinhardt answered 27/5, 2020 at 6:54 Comment(2)
Can you share an example of express and winston ?Lindholm
Sorry .I havent tried winston since I am a fan of pino. But it should be very similarWeinhardt
S
3

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');
Spoilsman answered 5/10, 2018 at 5:10 Comment(0)
M
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

Malposition answered 22/9, 2022 at 7:6 Comment(0)
H
2

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);
  }
}
Hydrogenous answered 3/3, 2023 at 12:31 Comment(0)
I
0

here is a simple usage of nestJs internal Logger:

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

export abstract class TestLogger {
  protected abstract readonly logger: Logger;

  async testLogger(){
    this.logger.warn(
      'Document was not found with filterQuery: '
    );
  }
}

for more complex usage and setup guide you can visit this article:

Implementing Nest JS Default Logger in your Application

Inflatable answered 2/5 at 23:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.