In this lesson, you will learn how to encapsulate the entire duration of a batch background worker into a single "Root Span" to accurately measure its execution lifecycle.
The Problem with Short-Lived Traces
In request-response cycles, we typically use automatic instrumentation that starts a span when a request arrives and ends it when the response is sent. However, background jobs (like nightly database reconciliations or bulk email processing) don't have an incoming HTTP request to trigger this lifecycle. If you rely on sub-traces for individual database queries or message processing steps without a parent, your observability platform will show fragmented data, making it impossible to calculate the "Total Wall-Clock Time" of the job.
Creating the Root Span
To gain visibility into the lifecycle, you must manually create a Root Span that encapsulates the entire execution. This span acts as the anchor for every sub-operation (like logging, file I/O, or API calls) performed during the job.
const tracer = api.trace.getTracer('batch-worker-tracer');
async function runBatchJob(jobData) {
// Create a root span for the entire job
return await tracer.startActiveSpan('batch_job_lifecycle', async (rootSpan) => {
try {
rootSpan.setAttribute('job.id', jobData.id);
// Perform sub-tasks; they will automatically be children of the root span
await processRecords(jobData.records);
await cleanupResources();
rootSpan.setStatus({ code: SpanStatusCode.OK });
} catch (err) {
rootSpan.recordException(err);
rootSpan.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
} finally {
// End the root span when the job terminates
rootSpan.end();
}
});
}
Why this matters
By wrapping the logic in startActiveSpan, any asynchronous operation triggered inside the callback automatically attaches itself to the rootSpan. This gives you a clean hierarchy in your tracing UI, allowing you to identify exactly which part of the long-running process consumed the most time. Without this root container, you lose the ability to visualize the job as a single, cohesive unit of work within your telemetry backend.
Key Takeaway: Always wrap your background job execution in a root span to consolidate disparate sub-operations into a single, measurable lifecycle.