QQUEUEWATCH
Back to Blog
Observability5 min readMay 28, 2026

How to Implement Trace IDs Across API and Queue Boundaries

In microservices, understanding request flow is simple when calls are synchronous (HTTP/gRPC). OpenTelemetry headers propagate easily across network hops. However, when an API publishes a job to a background queue, the context boundary is broken. The job is stored in a database (like Redis) and executed minutes or hours later by an isolated worker node. Without a unified correlation identifier, debugging a failure that started with a customer request and ended with a background worker crash is extremely difficult. This article shows you how to implement and propagate Trace IDs across API and queue boundaries.

The Challenge of Async Boundary Tracing

When an HTTP request triggers an asynchronous background job, the request thread finishes and returns a response immediately. The actual business logic runs later in a separate process.

If that background job fails due to an database anomaly, standard logs will show the exception in isolation. You won't know which user initiated the request, what HTTP parameters were supplied, or which API container published the job. Connecting these dots manually require stitching timestamps across disparate log aggregates.

What is a Trace ID?

A Trace ID is a unique 128-bit correlation identifier generated at the system entry point (e.g., an API gateway or load balancer). It acts as a transaction signature that is forwarded to every downstream service, database client, and background queue involved in completing that request.

By attaching the Trace ID to every log statement and telemetry package, SREs can query the entire distributed lifecycle of a transaction with a single search.

Generating and Propagating Trace IDs

To preserve context across async boundaries, we follow a three-step integration pattern:

1. Capture or Generate: Use W3C Trace Context headers (e.g., `traceparent`) in your API middleware. If none exists, generate a unique uuid.

2. Inject into Payload: Store the Trace ID inside the queue job payload or options metadata before adding it to Redis.

3. Extract on Worker: When the worker process pulls the job, extract the trace identifier and bind it to a local AsyncLocalStorage context so that subsequent logs automatically inherit the correlation ID.

tracePropagation.ts
// Propagating Trace IDs from API to BullMQ Workers using Node.js AsyncLocalStorage
import express from 'express';
import { Queue, Worker } from 'bullmq';
import { AsyncLocalStorage } from 'async_hooks';
import { v4 as uuidv4 } from 'uuid';

const queue = new Queue('order_processing');
const storage = new AsyncLocalStorage<{ traceId: string }>();

// API Middleware: Generate/Extract Trace ID
const apiApp = express();
apiApp.use((req, res, next) => {
  const traceId = (req.headers['x-trace-id'] as string) || uuidv4();
  storage.run({ traceId }, () => {
    req.traceId = traceId;
    next();
  });
});

apiApp.post('/checkout', async (req, res) => {
  const payload = { orderId: 'ord_9941', userId: 'usr_701' };
  
  // Inject the active Trace ID into the job metadata
  await queue.add('process_payment', {
    ...payload,
    _meta: { traceId: req.traceId }
  });
  
  res.status(202).json({ status: 'queued', traceId: req.traceId });
});

// Worker: Extract Trace ID and Bind Logger Context
const worker = new Worker('order_processing', async (job) => {
  const traceId = job.data._meta?.traceId || uuidv4();
  
  await storage.run({ traceId }, async () => {
    // Every log inside this scope will print the correct trace ID
    logger.info(`Processing payment for order ${job.data.orderId}`);
    await chargeCustomerCard(job.data);
  });
});

const logger = {
  info: (msg: string) => {
    const context = storage.getStore();
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      message: msg,
      traceId: context?.traceId || 'no-trace'
    }));
  }
};

The Benefits of End-to-End Tracing

Implementing unified Trace IDs unlocks massive observability benefits:

• Accelerated MTTR: Locate the exact API endpoint and payload details that led to a background worker failure.

• Blast Radius Auditing: Trace how one slow downstream dependency creates a cascade of delayed jobs across separate queues.

• Log Correlation: Link structured system metrics, exceptions, and runtime trace statements together automatically.

How QueueWatch Harnesses Trace Contexts

QueueWatch automatically parses W3C Trace Context and custom trace metadata fields from incoming BullMQ telemetry packets. The platform constructs active service dependency topology maps and places failing queue events directly onto a unified incident timeline.

When an anomaly occurs, QueueWatch trace views allow SREs to navigate seamlessly from an API webhook trigger to the BullMQ broker state, and deep into the worker container logs—making async debugging immediate.

Conclusion

Asynchronous boundaries shouldn't translate to observability blindspots. By propagating Trace IDs through your job payloads and utilizing context propagation structures, you can trace business transactions end-to-end, keeping your distributed system highly observable.

Turn operational failures into actionable intelligence with QueueWatch.

Get full observability into your BullMQ background processors, auto-resolve recurring worker issues, and secure pipeline logs.