0
0
NestJSframework~15 mins

Logging interceptor in NestJS - Deep Dive

Choose your learning style9 modes available
Overview - Logging interceptor
What is it?
A logging interceptor in NestJS is a special piece of code that runs before and after a request is handled. It helps track what happens during the request, like when it started, ended, and if any errors occurred. This makes it easier to understand how your app behaves and find problems. It works by intercepting the flow of data and adding logging steps without changing the main logic.
Why it matters
Without logging interceptors, developers struggle to see what happens inside their app during requests, making debugging and monitoring hard. Logging interceptors provide a clear, consistent way to record important events automatically. This helps keep apps reliable and maintainable, especially as they grow bigger and handle many users.
Where it fits
Before learning logging interceptors, you should understand basic NestJS concepts like controllers, providers, and middleware. After mastering interceptors, you can explore advanced topics like custom decorators, exception filters, and performance monitoring tools.
Mental Model
Core Idea
A logging interceptor wraps around request handling to automatically record key events before and after processing without changing the main code.
Think of it like...
It's like a security camera installed at a store entrance that records who comes in and leaves, without disturbing the shoppers or staff.
┌─────────────────────────────┐
│ Incoming Request            │
└─────────────┬───────────────┘
              │
      ┌───────▼────────┐
      │ Logging Interceptor │
      └───────┬────────┘
              │
      ┌───────▼────────┐
      │ Controller/Handler │
      └───────┬────────┘
              │
      ┌───────▼────────┐
      │ Response Sent    │
      └─────────────────┘
Build-Up - 7 Steps
1
FoundationWhat is an interceptor in NestJS
🤔
Concept: Interceptors are special classes that can run code before and after a request is handled.
In NestJS, an interceptor implements the `NestInterceptor` interface. It has an `intercept` method that receives the request context and a handler to continue processing. You can add code before calling the handler and after it completes.
Result
You can run extra logic around request handling without changing the controller code.
Understanding interceptors as wrappers around request handling unlocks how NestJS lets you add features like logging, caching, or transforming responses cleanly.
2
FoundationBasics of logging in Node.js
🤔
Concept: Logging means recording messages about what the app is doing, usually to the console or a file.
Node.js apps often use `console.log` to print messages. NestJS apps can use built-in `Logger` service for structured logs. Logs help track app behavior and errors.
Result
You can see messages in the terminal or log files that describe app events.
Knowing how to log messages is essential before automating logging with interceptors.
3
IntermediateCreating a simple logging interceptor
🤔Before reading on: do you think the interceptor logs before, after, or both before and after the request handler? Commit to your answer.
Concept: A logging interceptor can log messages both before and after the request handler runs.
Create a class implementing `NestInterceptor`. In the `intercept` method, log a message before calling `next.handle()`. Then use RxJS `tap` operator to log after the handler completes. Example: ```typescript import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable, tap } from 'rxjs'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const method = request.method; const url = request.url; this.logger.log(`Incoming Request: ${method} ${url}`); const now = Date.now(); return next.handle().pipe( tap(() => this.logger.log(`Request to ${method} ${url} completed in ${Date.now() - now}ms`)), ); } } ``` Apply this interceptor globally or to specific controllers.
Result
When a request comes in, logs show when it started and how long it took after finishing.
Logging both before and after the handler gives a full picture of request timing and flow.
4
IntermediateUsing RxJS operators in interceptors
🤔Before reading on: do you think interceptors can modify the response data or only log it? Commit to your answer.
Concept: Interceptors use RxJS operators to react to or change the response stream.
The `next.handle()` returns an Observable representing the response. Using RxJS operators like `tap`, `map`, or `catchError`, you can log, transform, or handle errors in the response stream. Example: Using `tap` to log without changing data. ```typescript return next.handle().pipe( tap(() => this.logger.log('Response sent')), ); ``` You can also use `map` to modify the response if needed.
Result
You can add side effects like logging or modify responses cleanly in the interceptor.
Knowing RxJS lets you control the flow of data and side effects in interceptors, making them powerful tools.
5
IntermediateApplying interceptors globally and locally
🤔
Concept: Interceptors can be applied to all requests or only specific routes or controllers.
To apply globally, use `app.useGlobalInterceptors(new LoggingInterceptor())` in your main app module. To apply locally, use the `@UseInterceptors(LoggingInterceptor)` decorator on controllers or methods. This flexibility lets you control where logging happens.
Result
You can log all requests or only some, depending on your needs.
Understanding scope helps optimize performance and avoid noisy logs.
6
AdvancedHandling errors and exceptions in logging interceptor
🤔Before reading on: do you think errors thrown in handlers are caught automatically by interceptors? Commit to your answer.
Concept: Interceptors can detect when a request handler throws an error and log it before passing it on.
Use RxJS `catchError` operator inside the interceptor to catch errors from the handler. Example: ```typescript import { throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; return next.handle().pipe( tap(() => this.logger.log('Request completed')), catchError(err => { this.logger.error(`Request failed: ${err.message}`); return throwError(() => err); }), ); ``` This logs errors while letting NestJS handle the response.
Result
Errors during requests are logged clearly, aiding debugging.
Knowing how to catch and log errors in interceptors prevents silent failures and improves observability.
7
ExpertPerformance impact and best practices for logging interceptors
🤔Before reading on: do you think logging every request in detail always improves app quality? Commit to your answer.
Concept: Logging interceptors add overhead; careful design balances detail with performance and log noise.
Logging every request with full details can slow down your app and flood logs. Experts use conditional logging (e.g., only errors or slow requests), structured logs for easy searching, and asynchronous logging to minimize impact. Also, avoid logging sensitive data. Use correlation IDs to trace requests across distributed systems. Example: Add conditions inside interceptor to log only slow requests. ```typescript const start = Date.now(); return next.handle().pipe( tap(() => { const duration = Date.now() - start; if (duration > 1000) { this.logger.warn(`Slow request: ${duration}ms`); } }), ); ```
Result
Logging is efficient, useful, and safe in production environments.
Understanding tradeoffs in logging helps build scalable, maintainable apps without performance loss or security risks.
Under the Hood
NestJS interceptors are classes that implement the `NestInterceptor` interface. When a request comes in, NestJS calls the interceptor's `intercept` method with the execution context and a handler. The interceptor can run code before calling `next.handle()`, which returns an Observable representing the response stream. Using RxJS operators, the interceptor can add side effects or transform data as it flows back. This design leverages reactive programming to handle asynchronous request lifecycles cleanly.
Why designed this way?
Interceptors were designed to provide a flexible, reusable way to add cross-cutting concerns like logging, caching, and transformation without cluttering business logic. Using Observables and RxJS fits NestJS's reactive style and allows asynchronous, composable operations. This approach avoids middleware limitations and integrates tightly with NestJS's dependency injection and lifecycle.
┌─────────────────────────────┐
│ Incoming HTTP Request        │
└─────────────┬───────────────┘
              │
      ┌───────▼────────┐
      │ LoggingInterceptor │
      │ intercept()       │
      └───────┬────────┘
              │ calls next.handle()
      ┌───────▼────────┐
      │ Controller/Handler │
      └───────┬────────┘
              │ returns Observable
      ┌───────▼────────┐
      │ RxJS Operators  │
      │ (tap, catchError)│
      └───────┬────────┘
              │
      ┌───────▼────────┐
      │ Response Sent    │
      └─────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does a logging interceptor change the response data by default? Commit to yes or no.
Common Belief:Logging interceptors modify the response data to add logging info.
Tap to reveal reality
Reality:Logging interceptors usually only observe or log data without changing the response unless explicitly coded to do so.
Why it matters:Assuming interceptors change data can cause confusion and bugs when responses differ unexpectedly.
Quick: Do interceptors run before middleware in NestJS? Commit to yes or no.
Common Belief:Interceptors run before middleware and can replace them.
Tap to reveal reality
Reality:Middleware runs before interceptors. Interceptors wrap around the controller after middleware processing.
Why it matters:Misunderstanding order can lead to placing logic in the wrong place, causing unexpected behavior.
Quick: Can interceptors catch all errors thrown in the app? Commit to yes or no.
Common Belief:Interceptors catch every error thrown anywhere in the app.
Tap to reveal reality
Reality:Interceptors only catch errors in the request handler they wrap. Global exceptions require exception filters.
Why it matters:Relying on interceptors for error handling can miss errors and cause unhandled exceptions.
Quick: Does logging every request always improve app quality? Commit to yes or no.
Common Belief:More logging is always better for debugging and monitoring.
Tap to reveal reality
Reality:Excessive logging can degrade performance, flood logs, and hide important info.
Why it matters:Blindly logging everything can cause slowdowns and make real issues harder to find.
Expert Zone
1
Logging interceptors can be combined with correlation IDs to trace requests across distributed microservices, which is often overlooked.
2
Using asynchronous logging inside interceptors prevents blocking the main request thread, improving performance under load.
3
Interceptors can be stacked, but the order affects behavior; understanding this order is crucial to avoid unexpected log sequences.
When NOT to use
Avoid using logging interceptors for heavy data transformations or complex error handling; use dedicated middleware or exception filters instead. For very high-performance needs, consider lightweight logging libraries or external monitoring tools that minimize overhead.
Production Patterns
In production, logging interceptors often log only warnings and errors, use structured JSON logs for easy parsing, and integrate with centralized logging systems like ELK or Datadog. They also include request IDs and user info for traceability.
Connections
Middleware
Middleware runs before interceptors and can modify requests early; interceptors wrap around handlers for post-processing.
Understanding middleware-interceptor order clarifies where to place cross-cutting logic for best effect.
Aspect-Oriented Programming (AOP)
Logging interceptors implement AOP principles by separating cross-cutting concerns from business logic.
Recognizing interceptors as AOP helps grasp their role in clean, modular code design.
Security Camera Systems
Like security cameras monitoring activity without interfering, logging interceptors observe requests transparently.
This cross-domain view highlights the importance of non-intrusive monitoring in software.
Common Pitfalls
#1Logging sensitive user data directly in interceptors.
Wrong approach:this.logger.log(`User password: ${request.body.password}`);
Correct approach:this.logger.log(`User login attempt for username: ${request.body.username}`);
Root cause:Not considering privacy and security risks when logging request data.
#2Applying logging interceptor globally without filtering.
Wrong approach:app.useGlobalInterceptors(new LoggingInterceptor()); // logs every request in detail
Correct approach:app.useGlobalInterceptors(new LoggingInterceptor({ logLevel: 'warn' })); // logs only warnings and errors
Root cause:Not balancing logging detail with performance and noise.
#3Catching errors in interceptor but not rethrowing them.
Wrong approach:return next.handle().pipe(catchError(err => { this.logger.error(err); return EMPTY; }));
Correct approach:return next.handle().pipe(catchError(err => { this.logger.error(err); return throwError(() => err); }));
Root cause:Misunderstanding that swallowing errors prevents proper error handling downstream.
Key Takeaways
Logging interceptors in NestJS wrap request handling to add automatic logging before and after processing.
They use RxJS Observables to handle asynchronous flows and can log timings, errors, and other details without changing business logic.
Applying interceptors globally or locally gives flexible control over where logging happens.
Proper error handling and performance considerations are essential to avoid common pitfalls in logging interceptors.
Understanding interceptors as an implementation of aspect-oriented programming helps design clean, maintainable applications.