Logging with the built-in logger when using raw SQL
Troubleshooting a deployed application can be challenging. We can’t use the debugger and stop an application used by other people. Instead, we need to…
Troubleshooting a deployed application can be challenging. We can’t use the debugger and stop an application used by other people. Instead, we need to implement logs that we can inspect if we suspect that our API is not working as expected. In this article, we look at the logger built into NestJS and use it in different cases. Logger available in NestJS NestJS is equipped with a logger ready to use out of the box. The official documentation suggests creating an instance of the logger on top of each class we want to use it in. users.service.ts import { Injectable, Logger } from '@nestjs/common'; import UsersRepository from './users.repository'; @Injectable() class UsersService { private readonly logger = new Logger(); // ... } export default UsersService; Log levels A crucial concept we need to learn is the level of the log. There are a few to choose from, sorted by severity: error warn log verbose debug Each logger instance has a set of methods that correspond with the above levels. users.service.ts import { Injectable, Logger } from '@nestjs/common'; import { CreateUserDto } from './dto/createUser.dto'; import UsersRepository from './users.repository'; import UserAlreadyExistsException from './exceptions/userAlreadyExists.exception'; @Injectable() class UsersService { private readonly logger = new Logger(UsersService.name); constructor(private readonly usersRepository: UsersRepository) {} async create(user: CreateUserDto) { try { return await this.usersRepository.create(user); } catch (error) { if (error instanceof UserAlreadyExistsException) { this.logger.warn(error.message); } throw error; } } // ... } export default UsersService;The more severe a particular log, the more alarmed we should be. For example, a user trying to sign up using an occupied email address is not a reason to panic. Because of that, above, we use the logger.warn function. We can specify what levels of logs we want to appear in our terminal. To do that, we need to adjust our bootstrap() function. main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log', 'verbose', 'debug'], }); app.use(cookieParser()); await app.listen(3000); } bootstrap();It is a common approach to disregard some of the logs in the production environment. We might want to avoid having too many logs in production so that we see a potential issue more clearly. main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; import { LogLevel } from '@nestjs/common/services/logger.service'; async function bootstrap() { const isProduction = process.env.NODE_ENV === 'production'; const logLevels: LogLevel[] = isProduction ? ['error', 'warn', 'log'] : ['error', 'warn', 'log', 'verbose', 'debug']; const app = await NestFactory.create(AppModule, { logger: logLevels, }); app.use(cookieParser()); await app.listen(3000); } bootstrap(); A surprising thing might be that providing ['debug'] turns on all of the log levels. When we look at the isLogLevelEnabled function we can see that NestJS looks for the highest severity included in the array and turns on all of the logs with the lower severity too. Above, we provide a full array for the sake of readability. Once we do the above, logs start appearing in our terminal. There we can see that thanks to providing the name of the service with new Logger(UsersService.name), the name appears at the beginning of the log. Using the logger in an interceptor The above approach can be handy when handling some specific cases. Unfortunately, covering all possible situations with logs would be pretty demanding. Fortunately, we can use the power of interceptors that NestJS offers us. Let’s write an interceptor that allows us to cover a particular endpoint with a wide variety of logs. authentication.controller.ts import { Body, Controller, Post, ClassSerializerInterceptor, UseInterceptors, } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import RegisterDto from './dto/register.dto'; import { LoggerInterceptor } from '../utils/logger.interceptor'; @Controller('authentication') @UseInterceptors(ClassSerializerInterceptor) export class AuthenticationController { constructor(private readonly authenticationService: AuthenticationService) {} @Post('register') @UseInterceptors(LoggerInterceptor) async register(@Body() registrationData: RegisterDto) { return this.authenticationService.register(registrationData); } // ... }The job of the LoggerInterceptor is to gather information bout the request and response. We can write an interceptor that binds some logic before the method execution. If you want to know more about interceptors, check out API with NestJS #5. Serializing the response with interceptors logger.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger, } from '@nestjs/common'; import { Request, Response } from 'express'; @Injectable() export class LoggerInterceptor implements NestInterceptor { private readonly logger = new Logger('HTTP'); intercept(context: ExecutionContext, next: CallHandler) { const httpContext = context.switchToHttp(); const request = httpContext.getRequest