Inject Custom Telemetry Metadata in Axios Requests

Injecting custom telemetry metadata into outgoing Axios HTTP requests is essential for distributed tracing, application performance monitoring (APM), and debugging microservices. This guide demonstrates how to use Axios request interceptors to automatically attach custom headers—such as correlation IDs, trace contexts, user IDs, and execution timestamps—to every outbound HTTP call.

Using Axios Request Interceptors

The primary mechanism for injecting metadata into outgoing requests is the Axios request interceptor (axios.interceptors.request.use). Interceptors allow you to inspect or modify the request configuration object (AxiosRequestConfig) before the HTTP request is sent over the network.

Step-by-Step Implementation

To attach telemetry data as HTTP headers and internal execution metadata, configure an Axios instance as shown below:

import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';

// 1. Create a dedicated Axios instance
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

// 2. Add the request interceptor
apiClient.interceptors.request.use(
  (config) => {
    // Generate or retrieve telemetry identifiers
    const correlationId = uuidv4();
    const sessionId = getSessionId(); // Custom function retrieving current session

    // Inject metadata into HTTP request headers
    config.headers = config.headers || {};
    config.headers['X-Correlation-ID'] = correlationId;
    config.headers['X-Session-ID'] = sessionId;
    config.headers['X-Client-Version'] = '1.4.0';

    // Inject internal metadata for local telemetry (e.g., latency measurement)
    config.metadata = {
      startTime: Date.now(),
      correlationId: correlationId,
    };

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 3. Add a response interceptor to process telemetry upon completion
apiClient.interceptors.response.use(
  (response) => {
    const duration = Date.now() - response.config.metadata.startTime;
    
    // Log telemetry metrics to your APM or logger
    console.log(`Request to ${response.config.url} completed in ${duration}ms [Correlation ID: ${response.config.metadata.correlationId}]`);
    
    return response;
  },
  (error) => {
    if (error.config && error.config.metadata) {
      const duration = Date.now() - error.config.metadata.startTime;
      console.error(`Request to ${error.config.url} failed after ${duration}ms [Correlation ID: ${error.config.metadata.correlationId}]`);
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Passing Per-Request Dynamic Metadata

If certain metadata must be set per individual request rather than globally, you can pass custom fields directly within the request call and forward them to headers inside the interceptor:

// Invoking the request with custom telemetry parameters
apiClient.get('/users', {
  telemetry: {
    featureFlag: 'new-checkout-v2',
    initiator: 'checkout-button-click'
  }
});

// Handling custom fields inside the interceptor
apiClient.interceptors.request.use((config) => {
  if (config.telemetry) {
    config.headers['X-Feature-Flag'] = config.telemetry.featureFlag;
    config.headers['X-Initiator'] = config.telemetry.initiator;
  }
  return config;
});

TypeScript Definition Support

If you are using TypeScript, extend the AxiosRequestConfig interface to prevent type-checking errors for custom metadata properties:

import 'axios';

declare module 'axios' {
  export interface AxiosRequestConfig {
    metadata?: {
      startTime: number;
      correlationId: string;
    };
    telemetry?: {
      featureFlag?: string;
      initiator?: string;
    };
  }
}

Standard Distributed Tracing (W3C Trace Context)

When integrating with standard tracing vendors like OpenTelemetry, Datadog, or New Relic, inject standard W3C headers rather than arbitrary keys:

Using standard trace headers ensures that your downstream services can correlate the incoming Axios request with the distributed trace lifecycle.