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: Usually 00.
    • 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-axios

2. 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 traceparent header 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 traceparent header already exists before generating a new one to prevent breaking upstream parent-child relationships.