Skip to content

feat(middleware): add W3C Trace Context correlation middleware - #4970

Open
cofin wants to merge 5 commits into
litestar-org:mainfrom
cofin:feat/correlation-middleware
Open

feat(middleware): add W3C Trace Context correlation middleware#4970
cofin wants to merge 5 commits into
litestar-org:mainfrom
cofin:feat/correlation-middleware

Conversation

@cofin

@cofin cofin commented Aug 8, 2026

Copy link
Copy Markdown
Member

Description

Applications need a consistent correlation identifier across handlers, logging, and other request-scoped work without requiring an observability integration. This adds standalone correlation middleware for HTTP and WebSocket connections.

CorrelationMiddleware selects the first safe value from a configurable list of request headers and generates a UUID when no suitable value is present. W3C traceparent values are validated before extracting the trace ID. The selected identifier is available through CorrelationContext and scope["state"]["correlation_id"], and can be written to a configurable response header.

Header names are normalized and deduplicated in priority order. Empty values and values containing control characters are skipped. Existing response correlation headers are replaced, and request context and scope state are restored when the middleware unwinds.

The middleware reads directly from the ASGI scope without additional dependencies or blocking work. It uses __slots__, performs bounded header processing, and does not add application-level state.

Refs #4719


📚 Documentation preview 📚: https://litestar-org.github.io/litestar-docs-preview/4970

@cofin
cofin requested review from a team as code owners August 8, 2026 19:03
Comment thread litestar/middleware/correlation.py Outdated
return value


class CorrelationContext:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this necessary? It diverges from our usual design quite a bit, and context vars are easy to break e.g. by not copying context properly (which is quite common, and I'm not even sure we're always doing it correctly everywhere).

IMO we should put this in ScopeState, since it only exists within a request context, and whenever you're in a request context, you'll also have access to the scope, and therefore the ScopeState. You could still add a convenience wrapper, just one that stores it in scope instead of a context var.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yep - we can definitely go this route. Give me some time to rework this, but I'd much prefer to keep it simple if there's a better way.

Comment thread litestar/middleware/correlation.py Outdated
Comment thread litestar/middleware/correlation.py Outdated
"""
for name in self.header_names:
name_bytes = name.encode("latin-1")
for raw_name, raw_value in scope.get("headers", ()):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not use our built in headers utils for this?

Comment thread litestar/middleware/correlation.py Outdated
Comment thread litestar/middleware/correlation.py Outdated
Comment thread litestar/middleware/correlation.py Outdated
Comment on lines +24 to +27
"x-cloud-trace-context",
"grpc-trace-bin",
"x-amzn-trace-id",
"x-b3-traceid",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think these proprietary ones should not be enabled by default? Could lead to confusing results otherwise

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To me, I think auto-handling the cloud provider's injection would be the least confusing right? If someone is using this on a cloud console, it would "just work". Otherwise, they'd have a different correlation ID than the one pushed in by the vendor.

I think most of the cloud vendors honor one of these, so I don't think it's a massive list we have to maintain, but I can double check this.

Would this be a configuration item we'd need to add otherwise?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I thought about this since we last discussed. I do agree with taking them out. I'll remove the cloud specific ones and provide a way to override/append additional correlation headers.

Comment on lines +38 to +39
async def _await_middleware(awaitable: Awaitable[None]) -> None:
await awaitable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the purpose of this?

cofin and others added 5 commits August 10, 2026 13:40
Co-authored-by: Janek Nouvertné <provinzkraut@posteo.de>
… context var

- drop the ContextVar-based CorrelationContext in favor of storing the ID
  on ScopeState, making it available to handlers and other middlewares
  via the scope; add a get_correlation_id() accessor
- use Headers.from_scope() for header extraction instead of iterating
  raw scope headers
- base the middleware on ASGIMiddleware
- fix inverted hex validation that rejected all valid traceparent values
@cofin
cofin force-pushed the feat/correlation-middleware branch from 3c150ea to b5767d3 Compare August 10, 2026 13:43
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.73171% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.44%. Comparing base (733d72c) to head (b5767d3).
⚠️ Report is 19 commits behind head on main.

Files with missing lines Patch % Lines
litestar/middleware/correlation.py 72.15% 22 Missing ⚠️
litestar/middleware/__init__.py 0.00% 1 Missing ⚠️
litestar/utils/scope/state.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4970      +/-   ##
==========================================
+ Coverage   67.32%   67.44%   +0.11%     
==========================================
  Files         293      294       +1     
  Lines       15246    15377     +131     
  Branches     1728     1751      +23     
==========================================
+ Hits        10265    10371     +106     
- Misses       4834     4857      +23     
- Partials      147      149       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants