Architecture • 21 August 2026 • Written by Lochan Chugh

Distributed Tracing in Swift: Context Propagation with Task-Locals

Distributed Tracing in Swift: Context Propagation with Task-Locals

Distributed Tracing in Swift: Context Propagation with Task-Locals

Debugging asynchronous systems can be challenging. When a network request triggers multiple background tasks, database writes, or remote API calls, tracing the path of that specific transaction through your logs is difficult. Traditional logging systems rely on thread-local storage (Thread.current.threadDictionary) to store correlation IDs. However, in Swift Concurrency, tasks hop across threads, rendering thread-local storage useless.

Swift addresses this context-propagation issue with Task-Local Values. Using the @TaskLocal macro property wrapper, you can bind transaction identifiers at the start of an execution scope, and the runtime automatically propagates them down the structured task tree.


Binding and Accessing Context

Task-local values are bound within a specific execution scope. Any structured child tasks (spawned using task groups or async let) automatically inherit the parent’s task-local bindings.

Here is how you bind a request identifier at the entry point of a transaction, accessing it inside down-stream logging utilities:

import Foundation

enum TraceContext {
    // Declared as a task-local value
    @TaskLocal static var correlationID: String?
}

class TransactionService {
    func handleIncomingRequest(payload: [String: String]) async {
        let requestID = payload["X-Request-ID"] ?? UUID().uuidString
        
        // Developer Thoughts: We bind the request ID to the current task scope.
        // Any async functions called inside this block inherit the value.
        await TraceContext.$correlationID.withValue(requestID) {
            await performDatabaseWrite()
            
            // Structured concurrency inherits the context automatically
            async let networkResult = dispatchTelemetry()
            await networkResult
        }
    }
    
    private func performDatabaseWrite() async {
        // Developer Thoughts: We extract the correlation ID without passing 
        // it explicitly as a method argument.
        let traceID = TraceContext.correlationID ?? "unknown"
        print("[\(traceID)] Writing request parameters to disk database.")
    }
    
    private func dispatchTelemetry() async {
        let traceID = TraceContext.correlationID ?? "unknown"
        print("[\(traceID)] Dispatching analytics payload to remote server.")
    }
}

Context Leak Risks: Unstructured Concurrency

While structured concurrency (async let and Task Groups) inherits task-local context by default, unstructured concurrency behaves differently:

func triggerAsynchronousTask() {
    // Unstructured tasks copy the parent's task-local values at creation time.
    Task {
        let traceID = TraceContext.correlationID ?? "unbound"
        print("[\(traceID)] This task inherits the context.")
    }
    
    // Detached tasks do NOT copy task-local values.
    Task.detached {
        let traceID = TraceContext.correlationID ?? "unbound"
        print("[\(traceID)] This is detached. Output is always 'unbound'.")
    }
}

Key Differences

  1. Task Initializer (Task { }): Copies the active task-local values from the parent scope, maintaining tracing capability for simple background dispatching.
  2. Detached Task (Task.detached { }): Starts a fresh task tree with zero inherited context, meaning any correlation identifiers are lost. Use this configuration only when running operations that are completely independent of the parent context.

Summary

Task-Local values provide a type-safe way to propagate transaction contexts across asynchronous boundaries in Swift. By binding correlation IDs at the entry point of your tasks, you can maintain distributed tracing without cluttering your method signatures. Keep in mind that detached tasks do not inherit this context, so you must pass identifiers explicitly if your execution branches out of the structured tree.

References & Further Reading

Ready for more depth?

Master these concepts with our structured technical roadmap.

View Roadmap