How to Log Requests in NestJS

Updated: December 31, 2023 By: Guest Contributor Post a comment

Introduction

Efficient request logging is crucial for debugging and monitoring applications. NestJS, a progressive Node.js framework, provides robust solutions for logging requests with ease. In this guide, we delve into several methods to achieve this using NestJS’s latest features and TypeScript.

Using Middleware for Basic Logging

To start logging requests in NestJS, you can utilize middleware, which are functions that have access to the request and response objects.

import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const { method, path } = req;
    const message = `${method} ${path}`;

    Logger.log(message, 'IncomingRequest');

    res.on('finish', () => {
      const { statusCode } = res;
      const responseMessage = `${method} ${path} ${statusCode}`;
      Logger.log(responseMessage, 'OutgoingResponse');
    });

    next();
  }
}

After creating your middleware, apply it globally in your main.ts file or attach it to specific routes or controllers.

Custom Logger Services

For finer control over your logging, building a custom logger service is ideal. This can involve extending NestJS’s built-in Logger class.

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

@Injectable({ scope: Scope.TRANSIENT })
class MyLogger extends Logger implements LoggerService {
  // Custom log method implementation
  log(message: string) {
    // Add your custom logging functionality here
    super.log(message); // Call the base class log method
  }

  // Other methods like error, warn, etc.
}

Integrate your custom logger with main.ts by setting it as the application’s logger of choice.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MyLogger } from './my-logger.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new MyLogger(),
  });

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

Interceptors for Advanced Logging

Interceptors offer a way to tap into the flow of a method execution. Here’s how to use an interceptor for advanced request logging.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const httpContext = context.switchToHttp();
    const request = httpContext.getRequest();
    const { method, url } = request;
    const reqLog = `Request: ${method} ${url}`;
    Logger.log(reqLog, 'RequestInterceptor');

    return next
      .handle()
      .pipe(
        tap(() => {
          const response = httpContext.getResponse();
          const resLog = `Response: ${method} ${url} ${response.statusCode}`;
          Logger.log(resLog, 'ResponseInterceptor');
        }),
      );
  }
}

Add the interceptor to your application.

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './logging-interceptor.service';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
  //... other module properties
})
class AppModule {}

Extended Request Logging

Logging can be more than just console output; storing logs for future analysis or real-time monitoring is often needed. A more elaborate setup using a logging library like Winston might include file transports or external log management solutions.

Here’s an example of how you might extend a logger for file-based logging with context.

// Additional imports and setup for Winston...

class ExtendedLogger extends Logger {
  private logger = new WinstonLogger(/* ...Winston configuration... */);

  log(message: string, context?: string) {
    this.logger.info(message, { context });
    super.log(message, context);
  }

//... other methods and implementation details ...
}

Such advanced setups allow you to create compact and structured logs that are suitable for processing with modern log management systems.

Conclusion

Through this guide, we have seen that NestJS offers several ways to implement request logging, from straightforward middleware-based methods to a more advanced interceptor-driven approach. Choose the strategy that aligns best with your project’s requirements and make sure your logs are meaningful and actionable.