NestJS: How to log stack trace of an error

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

Understanding the Stack Trace Error in NestJS

Logging a stack trace for errors is essential when developing applications in Node.js with NestJS. A stack trace provides a detailed snapshot of the call stack and helps us determine where the error originated. The absence of stack trace can make debugging more challenging as you won’t get enough context to understand the error cause.

Introducing the Logger Service

NestJS comes with a built-in logger service that makes it simple to log errors and their stack traces. However, to log a stack trace when an error occurs, you need to catch the exception and pass it to the logger service. This process can take place in several places within your application, such as in your services, controllers, or through global filters.

Using Global Exception Filters

One way to log error stack traces is to implement a global exception filter. Global exception filters can catch exceptions from anywhere in the application. Let’s create a custom HttpExceptionFilter which will log every exception along with its stack trace.

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';

@Catch()
class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(HttpExceptionFilter.name);

  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
    const response = ctx.getResponse();
    const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;

    this.logger.error(`Status: ${status} Error: ${exception.message}`, exception.stack);

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

You can apply this filter globally in your main.ts file to ensure all unhandled exceptions are logged.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

Using Interceptors for Enhanced Control

If you want more control over the responses or handle other types of non-HTTP exceptions, you might consider using interceptors. Interceptors can manipulate the request and response objects, and also handle errors.

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

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

  intercept(context: ExecutionContext, next: CallHandler): Observable {
    return next
      .handle()
      .pipe(
        catchError((error) => {
          this.logger.error('An error occurred', error.stack);
          throw error;
        }),
      );
  }
}

You could then attach this interceptor either globally or at a controller/method scope, depending on your application needs.

Using Controllers and Services

Finally, an error can also be logged from within a service or controller by catching the error in a try-catch block and using the Logger service.

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

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

  async someFunction(): Promise {
    try {
      // ... Your logic here
    catch (error) {
      this.logger.error('An error occurred', error.stack);
      throw error; // Rethrow the error if you want to handle it further up the stack
    }
  }
}

You would write similar code within a controller, catching exceptions within route handlers.

Conclusion

Being able to log a stack trace when an error occurs in NestJS is essential for understanding and resolving issues effectively. In this guide, we explored using global exception filters, custom interceptors, and direct logging within services or controllers. While the error handling strategy might differ depending on the project needs, logging the stack trace remains a critical debugging aid.