Skip to content

Correctly propagate the trace ID#652

Open
sebsto wants to merge 10 commits intomainfrom
sebsto/trace-id
Open

Correctly propagate the trace ID#652
sebsto wants to merge 10 commits intomainfrom
sebsto/trace-id

Conversation

@sebsto
Copy link
Collaborator

@sebsto sebsto commented Feb 25, 2026

Issue

#633

Description of changes

(read original PR description below)

Updated from the initial PR — based on review feedback, the trace ID propagation has been reworked to use ServiceContext from swift-service-context instead of a Lambda-specific @TaskLocal.

The trace ID from AWS wasn't being propagated in a way that's accessible to the full async task tree. Tracing libraries like OpenTelemetry need the trace ID without requiring an explicit LambdaContext reference, and the old approach of only setting the _X_AMZN_TRACE_ID environment variable doesn't work safely in multi-concurrency mode since env vars are process-global.

This change propagates the trace ID via ServiceContext.current, the standard context propagation mechanism in the Swift server ecosystem. The runtime sets ServiceContext.current?.traceID before calling the handler, making it automatically available to all code in the handler's async task tree — including downstream libraries that have no dependency on AWSLambdaRuntime.

A ServiceContextKey (LambdaTraceIDKey) is defined with a public traceID accessor on ServiceContext, so any library depending only on swift-service-context can read the trace ID:

import ServiceContextModule

if let traceID = ServiceContext.current?.traceID {
    // propagate to outgoing requests
}

In single-concurrency mode, the runtime still sets and clears the _X_AMZN_TRACE_ID env var for backward compatibility with legacy tooling. In multi-concurrency mode, only ServiceContext is used to avoid races between concurrent invocations.

The runLoop function accepts an isSingleConcurrencyMode flag so it knows which strategy to use. Call sites in LambdaRuntime and LambdaManagedRuntime pass the appropriate value.

A "Trace ID Propagation" section has been added to the README documenting both access patterns (from a handler via context.traceID, and from downstream libraries via ServiceContext).

New/existing dependencies

Added swift-service-context (1.3.0+) as a new dependency of the AWSLambdaRuntime target. This is a lightweight package from Apple that provides the standard ServiceContext type used across the Swift server ecosystem (swift-distributed-tracing, swift-service-lifecycle, etc.). It has no transitive dependencies.

Conventional Commits

feat: propagate trace ID via ServiceContext for ecosystem-wide access

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Original PR description for history

The trace ID from AWS wasn't being propagated in a way that's accessible to the full async task tree. Tracing libraries like OpenTelemetry need the trace ID without requiring an explicit LambdaContext reference, and the old approach of only setting the _X_AMZN_TRACE_ID environment variable doesn't work safely in multi-concurrency mode since env vars are process-global.

This change adds a @TaskLocal static property LambdaContext.currentTraceID that the runtime sets before calling the handler. It's automatically available to all code in the handler's async task tree, including child tasks and background work.

In single-concurrency mode, the runtime still sets and clears the _X_AMZN_TRACE_ID env var for backward compatibility with legacy tooling. In multi-concurrency mode, only the TaskLocal is used to avoid races between concurrent invocations.

The runLoop function now accepts an isSingleConcurrencyMode flag so it knows which strategy to use. Call sites in LambdaRuntime and LambdaManagedRuntime pass the appropriate value.

Tests cover TaskLocal scoping, child task inheritance, concurrent isolation between tasks, env var lifecycle in both modes, and coexistence of the static TaskLocal with the existing instance traceID property.

New/existing dependencies impact assessment, if applicable

No new dependencies were added to this change.

Conventional Commits

feat: propagate trace ID via TaskLocal for async task tree access

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@sebsto sebsto self-assigned this Feb 25, 2026
@sebsto sebsto added the 🆕 semver/minor Adds new public API. label Feb 25, 2026
@sebsto sebsto linked an issue Feb 25, 2026 that may be closed by this pull request
6 tasks
@sebsto
Copy link
Collaborator Author

sebsto commented Feb 25, 2026

I think API Breakage test is a false positive as the function has package access. It's not part of the public API. Nevertheless, I added a @deprecated notation to silence this error.

This comment was marked as outdated.

@sebsto sebsto requested a review from 0xTim February 25, 2026 12:18
@sebsto sebsto removed the request for review from 0xTim March 1, 2026 11:28
@sebsto sebsto force-pushed the sebsto/trace-id branch from 90992c4 to 7f4dee7 Compare March 1, 2026 12:17
}
defer {
if isSingleConcurrencyMode {
unsetenv("_X_AMZN_TRACE_ID")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want to have a constant for this string somewhere?

responseWriter: writer,
context: LambdaContext(
requestID: invocation.metadata.requestID,
traceID: traceId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: traceID for consistency

/// // propagate traceID to outgoing HTTP requests, etc.
/// }
/// ```
public var traceID: String? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit problematic to declare the key in the lambda runtime, it'd be better if it was declared inside some other library that is "the xray library" and aws lambda runtime would depend on it for the key.

This way other libs which want to use xray specifically could do so without conflicting here...

I think what we may need to do in the short term -- unless we spin out a lib -- would be to call this awsLambdaXRayTraceID as long as it is, it won't cause conflicts in the future if there were some "xray library" which would be the right place to declare the proper key and aws lambda runtime would then use it as well 🤔

WDYT?

Also cc @slashmo for opinions

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxday How runtimes are supporting this in other languages ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbelkins should we create a swift-xray-library that extends ServiceConfig with that key ? Can the AWS SDK use it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[core] Implement X-Ray Trace ID Propagation via Environment Variable

4 participants