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:
traceparent: Encodes the version, trace ID, parent ID/span ID, and trace flags.tracestate: Contains vendor-specific tracing metadata.
Using standard trace headers ensures that your downstream services can correlate the incoming Axios request with the distributed trace lifecycle.