Propagate W3C Trace Context in Axios
Distributed tracing enables monitoring and troubleshooting of
microservices by tracking HTTP requests across multiple service
boundaries. This article explains how to integrate distributed tracing
IDs into the Axios HTTP client using the standard W3C Trace Context
specification (traceparent and tracestate
headers). You will learn how to manually inject trace headers using
Axios interceptors as well as how to automate trace propagation using
OpenTelemetry instrumentation.
Understanding the W3C Trace Context Standard
The W3C Trace Context specification defines standard HTTP headers to propagate context across system boundaries:
traceparent: A hyphen-separated string containing:version: Usually00.trace-id: A 16-byte (32-hex-character) unique identifier for the distributed trace.parent-id(span-id): An 8-byte (16-hex-character) unique identifier for the current operation/span.trace-flags: An 8-bit field (2-hex characters) for options like sampling (01= recorded).- Example:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: An optional header used for vendor-specific metadata key-value pairs.
Method 1: Manual Header Injection with Axios Interceptors
If you manage trace IDs manually or use a custom tracing solution,
use an Axios request interceptor to append the traceparent
header to every outgoing HTTP call.
import axios from 'axios';
import crypto from 'crypto';
// Helper to generate compliant hex IDs
const generateTraceId = () => crypto.randomBytes(16).toString('hex');
const generateSpanId = () => crypto.randomBytes(8).toString('hex');
// Create an Axios instance
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
// Attach a request interceptor to inject trace context
apiClient.interceptors.request.use((config) => {
// Retrieve existing trace ID from context or generate a new one
const traceId = config.headers['x-trace-id'] || generateTraceId();
const spanId = generateSpanId();
const traceFlags = '01'; // 01 indicates sampled
const traceparent = `00-${traceId}-${spanId}-${traceFlags}`;
// Set the W3C traceparent header
config.headers['traceparent'] = traceparent;
return config;
}, (error) => {
return Promise.reject(error);
});
export default apiClient;Method 2: Automatic Propagation with OpenTelemetry
OpenTelemetry provides native instrumentation that automatically extracts the active trace context from the application's runtime context and injects the W3C headers into all Axios requests.
1. Install Required Packages
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/instrumentation-http @opentelemetry/instrumentation-axios2. Initialize the OpenTelemetry Tracing SDK
Initialize OpenTelemetry before loading Axios or your application entry point.
// tracing.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { AxiosInstrumentation } from '@opentelemetry/instrumentation-axios';
import { W3CTraceContextPropagator } from '@opentelemetry/core';
const sdk = new NodeSDK({
textMapPropagator: new W3CTraceContextPropagator(),
instrumentations: [
new HttpInstrumentation(),
new AxiosInstrumentation(),
],
});
sdk.start();3. Executing Requests with Active Context
With instrumentation configured, standard Axios calls will
automatically include the active traceparent header without
manual configuration.
import axios from 'axios';
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('example-service');
async function makeCall() {
await tracer.startActiveSpan('fetch-data-span', async (span) => {
try {
// Axios automatically attaches 'traceparent' matching this active span
const response = await axios.get('https://api.example.com/items');
console.log('Response:', response.data);
} catch (error) {
span.recordException(error);
throw error;
} finally {
span.end();
}
});
}Best Practices
- Validate Downstream Propagation: Ensure downstream
services extract the
traceparentheader to maintain continuous trace graphs. - Maintain Trace Flags: Forward trace flags (such as sampling decisions) accurately to avoid unnecessary overhead in high-throughput environments.
- Avoid Overwriting: Check if a
traceparentheader already exists before generating a new one to prevent breaking upstream parent-child relationships.