← Back to Articles         
How do you implement request logging?

How do you implement request logging?

Published on 01 Oct 2026     6 min read Angular
HTTP Interceptors

How do you implement request logging in Angular?

In Angular, request logging can be implemented globally using an HTTP interceptor.

A logging interceptor can capture information such as:

  • HTTP method — GET, POST, PUT, DELETE
  • Request URL
  • Request start time
  • Response status
  • Request duration
  • HTTP errors

This avoids adding logging code to every Angular service.


Full Simple Example

1. logging.interceptor.ts

import {
  HttpErrorResponse,
  HttpInterceptorFn,
  HttpResponse
} from '@angular/common/http';

import { tap } from 'rxjs';

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {

  const startTime = Date.now();

  console.log('HTTP Request');
  console.log('Method:', req.method);
  console.log('URL:', req.urlWithParams);

  return next(req).pipe(

    tap({

      next: event => {

        if (event instanceof HttpResponse) {

          const duration = Date.now() - startTime;

          console.log('HTTP Response');
          console.log('URL:', req.urlWithParams);
          console.log('Status:', event.status);
          console.log('Duration:', duration + ' ms');
        }
      },

      error: (error: HttpErrorResponse) => {

        const duration = Date.now() - startTime;

        console.error('HTTP Error');
        console.error('URL:', req.urlWithParams);
        console.error('Status:', error.status);
        console.error('Message:', error.message);
        console.error('Duration:', duration + ' ms');
      }

    })
  );
};

2. app.config.ts

Register the interceptor globally.

import { ApplicationConfig } from '@angular/core';

import {
  provideHttpClient,
  withInterceptors
} from '@angular/common/http';

import { loggingInterceptor } from './logging.interceptor';

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient(
      withInterceptors([
        loggingInterceptor
      ])
    )

  ]

};

3. product.service.ts

No logging code is required inside the service.

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private http = inject(HttpClient);

  private apiUrl =
    'https://localhost:7001/api/products';

  getProducts(): Observable<Product[]> {

    return this.http.get<Product[]>(
      this.apiUrl
    );
  }
}

Now whenever getProducts() is called, the interceptor automatically logs the request and response.


How does it work?

When Angular executes:

this.http.get(
  'https://localhost:7001/api/products'
);

the flow is:

Product Component
       ↓
ProductService
       ↓
HttpClient
       ↓
Logging Interceptor
       │
       ├── Log method
       ├── Log URL
       └── Record start time
       ↓
ASP.NET Core Web API
       ↓
HTTP Response
       ↓
Logging Interceptor
       │
       ├── Log status
       └── Calculate duration
       ↓
ProductService
       ↓
Component

Understanding the code

1. Get the HTTP method

req.method

For example:

GET
POST
PUT
DELETE

2. Get the request URL

req.urlWithParams

For example:

https://localhost:7001/api/products?page=1&pageSize=10

urlWithParams is useful because it includes query-string parameters.

Compare:

req.url

with:

req.urlWithParams

For a request such as:

/api/products?page=1

urlWithParams gives you the complete URL including ?page=1.


3. Record the request start time

const startTime = Date.now();

Suppose:

Start = 10:30:00.100

After the response arrives:

const duration = Date.now() - startTime;

Suppose the response arrived at:

10:30:00.350

Then:

Duration = 250 ms

This can help identify slow API calls.


4. Send the request

return next(req)

next(req) passes the request to the next interceptor or ultimately to the HTTP backend.

It returns an Observable.

Therefore, we can observe what happens using:

.pipe(...)

5. Use tap() for logging

return next(req).pipe(

  tap({
    next: event => {
    },

    error: error => {
    }
  })

);

tap() is useful for side effects such as:

Logging
Debugging
Metrics
Tracing

It normally doesn't modify the HTTP response.


Why check HttpResponse?

The interceptor receives a stream of HttpEvent objects, not necessarily only the final response.

Therefore:

if (event instanceof HttpResponse)

ensures we log the final HTTP response.

if (event instanceof HttpResponse) {

  console.log(event.status);

}

For example:

Status: 200

Example console output

Suppose Angular calls:

GET /api/products

You might see:

HTTP Request
Method: GET
URL: https://localhost:7001/api/products

HTTP Response
URL: https://localhost:7001/api/products
Status: 200
Duration: 185 ms

If the API fails:

HTTP Request
Method: GET
URL: https://localhost:7001/api/products

HTTP Error
URL: https://localhost:7001/api/products
Status: 500
Message: Http failure response...
Duration: 120 ms

Logging multiple request types

The same interceptor automatically handles:

this.http.get(...)
this.http.post(...)
this.http.put(...)
this.http.delete(...)

For example:

GET    /api/products
POST   /api/products
PUT    /api/products/10
DELETE /api/products/10

You don't need separate logging code for each API method.


Logging Request Body

Technically, you could log:

console.log('Body:', req.body);

For example:

console.log(
  'Method:', req.method,
  'URL:', req.urlWithParams,
  'Body:', req.body
);

However, this should be used carefully.

A request body could contain:

Password
Access token
Refresh token
Credit-card information
Personal information
Other sensitive data

Therefore, avoid indiscriminately logging request bodies in production.


Don't log JWT tokens

Similarly, avoid logging the complete headers:

console.log(req.headers);

if they contain:

Authorization: Bearer eyJhbGciOi...

Your logs should never become a source of leaked credentials.


Combining Auth, Logging and Error Interceptors

A real application might register:

provideHttpClient(
  withInterceptors([
    authInterceptor,
    loggingInterceptor,
    errorInterceptor
  ])
)

Conceptually:

Component
    ↓
Service
    ↓
HttpClient
    ↓
Auth Interceptor
    │
    │ Add JWT
    ↓
Logging Interceptor
    │
    │ Log request
    ↓
Error Interceptor
    ↓
ASP.NET Core API

Responses travel back through the interceptor chain in the opposite direction.


Better Production Logging

console.log() is fine for learning and local development:

console.log(req.method);
console.log(req.urlWithParams);

For a production application, you may instead use a dedicated telemetry/logging solution.

The general architecture could be:

Angular Application
        ↓
Logging Interceptor
        ↓
Telemetry / Monitoring

The backend should also perform server-side request logging. Angular logging cannot replace ASP.NET Core server logs because client-side logs only describe what the browser sees.


Request Logging vs Error Interceptor

They serve different purposes.

Logging Interceptor Error Interceptor
Logs requests Handles errors
Logs responses Handles status codes
Measures duration Handles 401, 403, 500, etc.
Useful for diagnostics Useful for centralized error behavior
Usually uses tap() Usually uses catchError()

You can use both:

withInterceptors([
  loggingInterceptor,
  errorInterceptor
])

Key Points

  • Use an HTTP interceptor for centralized request logging.
  • req.method provides the HTTP method.
req.method
  • req.urlWithParams provides the URL including query parameters.
req.urlWithParams
  • Record request time using:
const startTime = Date.now();
  • Calculate duration:
const duration = Date.now() - startTime;
  • tap() is useful for logging without transforming the response.
next(req).pipe(
  tap(...)
)
  • Check the final response using:
event instanceof HttpResponse
  • Avoid logging sensitive information such as passwords, JWTs, refresh tokens, or other secrets.

Interview Questions and Answers

1. How do you implement request logging in Angular?

Use an HTTP interceptor and log request information before calling next(req).

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {

  console.log(req.method);
  console.log(req.urlWithParams);

  return next(req);
};

2. How do you log the response?

Use RxJS tap():

return next(req).pipe(

  tap(event => {

    if (event instanceof HttpResponse) {
      console.log(event.status);
    }

  })

);

3. How do you measure API execution time?

Record the time before sending the request:

const startTime = Date.now();

Then calculate the difference when the response arrives:

const duration =
  Date.now() - startTime;

4. Why use tap() for logging?

tap() allows us to perform side effects such as logging and diagnostics without normally changing the emitted HTTP response.


5. Should request bodies and headers always be logged?

No. They may contain sensitive data such as passwords, JWTs, refresh tokens, or personal information. Production logging should sanitize or omit sensitive fields.

Interview one-line answer

Request logging in Angular can be implemented globally using an HTTP interceptor, where request details are logged before next(req), and RxJS tap() is used to log the response, errors, and request execution time without modifying the HTTP data.

← Back to Articles