Logging Interceptors
⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 8 min
Logging Interceptors are one of the most common and practical uses of the Interceptor pattern, allowing you to record request details and execution times without polluting your controller logic.
Overview
Every robust API needs logging to monitor performance and debug issues. You typically want to log:
- When a request arrives.
- What the request was (URL, User, IP).
- How long it took to process.
Placing console.log() statements inside every controller method violates the DRY (Don’t Repeat Yourself) principle. A global logging interceptor handles this elegantly.
Key Concepts
tap()Operator: Because logging is a “side-effect” (it doesn’t change the response data), we use the RxJStap()operator.tap()executes logic but transparently passes the data stream through untouched.- Timing: We capture the start time before
next.handle(), and calculate the duration inside thetap()operator.
Code Examples
A Comprehensive Logging Interceptor
This interceptor logs the HTTP method, the URL, the user (if authenticated), and the total execution time.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
// Use Nest's built-in Logger class instead of console.log
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const request = ctx.getRequest();
const method = request.method;
const url = request.url;
// Safely get the user ID if an AuthGuard has already populated it
const userId = request.user?.id || 'Anonymous';
const now = Date.now();
this.logger.log(`[REQ] ${method} ${url} - User: ${userId}`);
return next
.handle()
.pipe(
tap(() => {
const delay = Date.now() - now;
this.logger.log(`[RES] ${method} ${url} - User: ${userId} - ${delay}ms`);
}),
);
}
}
Applying it Globally
To ensure every request is logged, bind it globally in your main.ts.
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './interceptors/logging.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Apply globally!
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
Best Practices
- Middleware vs. Interceptor for Logging:
- Use Middleware if you need to log requests before Authentication/Guards execute (e.g., logging every single hit, even unauthorized ones).
- Use Interceptors if you want to log the User ID (which is only available after Guards run) or if you want to log the execution time of the Controller specifically.
- Avoid Logging Sensitive Data: Never log passwords, credit card numbers, or full authorization tokens. Be very careful if you choose to log
request.body.