Implementing request logging and tracing in a Nest.js application typically involves several key steps, including setting up middleware, using interceptors, configuring a logging service, and potentially integrating with external logging tools or platforms. Below are detailed steps and examples for implementation:
1. Create a Logging Service
First, create a logging service. This service handles log generation and storage, which can be simple console output or stored to the file system, database, or remote logging systems such as ELK Stack, Datadog, etc.
typescriptimport { Injectable } from '@nestjs/common'; @Injectable() export class LoggerService { log(message: string) { console.log(message); } error(message: string, trace: string) { console.error(message, trace); } warn(message: string) { console.warn(message); } debug(message: string) { console.debug(message); } verbose(message: string) { console.verbose(message); } }
2. Use Middleware to Log Requests and Responses
Middleware can access request and response objects, making it ideal for logging each incoming request and its response.
typescriptimport { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { LoggerService } from './logger.service'; @Injectable() export class LoggingMiddleware implements NestMiddleware { constructor(private logger: LoggerService) {} use(req: Request, res: Response, next: NextFunction): void { const { method, originalUrl } = req; const startTime = Date.now(); res.on('finish', () => { const elapsedTime = Date.now() - startTime; const { statusCode } = res; const logMessage = `${method} ${originalUrl} ${statusCode} ${elapsedTime}ms`; this.logger.log(logMessage); }); next(); } }
3. Register Middleware in the Main Module
Next, register this middleware in the application's main module so it can be applied globally.
typescriptimport { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggingMiddleware } from './logging.middleware'; import { LoggerService } from './logger.service'; @Module({ providers: [LoggerService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggingMiddleware) .forRoutes('*'); // Apply to all routes } }
4. Use Interceptors for Granular Logging
Interceptors provide additional hooks in the request processing pipeline, enabling more granular logging such as recording method execution time and failed requests.
typescriptimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; import { LoggerService } from './logger.service'; @Injectable() export class LoggingInterceptor implements NestInterceptor { constructor(private logger: LoggerService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const now = Date.now(); const method = context.getHandler().name; return next .handle() .pipe( tap(() => this.logger.log(`${method} executed in ${Date.now() - now}ms`)) ); } }
5. Integrate with External Tools and Platforms
To achieve better log management and monitoring, consider sending logs to external systems, such as by integrating Winston with its various transports or using error tracking systems like Sentry to enhance error logging functionality.
This approach typically provides stronger log analysis and query capabilities in production environments, helping development and operations teams effectively track and resolve issues.
Summary
By following the above steps, you can implement comprehensive request logging and tracing in a Nest.js application, thereby improving its maintainability and monitoring capabilities. These logging strategies not only assist developers in daily debugging but also enable quick issue identification and resolution in production environments.