Skip to content

feat: Propagate OTel context via WebSocket HTTP upgrade headers#174

Merged
lhchavez merged 3 commits intomainfrom
feat/otel-ws-header-propagation
Mar 1, 2026
Merged

feat: Propagate OTel context via WebSocket HTTP upgrade headers#174
lhchavez merged 3 commits intomainfrom
feat/otel-ws-header-propagation

Conversation

@lhchavez
Copy link
Collaborator

Why

River's WebSocket connections don't carry any OTel context (traceparent, tracestate, baggage) from client to server. This means distributed tracing and baggage propagation are broken at the WebSocket boundary — the server has no way to inherit the caller's trace context or read OTel baggage entries.

What changed

Uses the standard W3C HTTP header approach — the same mechanism any HTTP service uses for OTel propagation — applied to the WebSocket upgrade request.

Client side (client_transport.py, v2/session.py)

  • Before calling websockets.connect(), inject the current OTel context into a headers dict via propagate.inject().
  • Pass those headers as extra_headers (v1 legacy API) / additional_headers (v2 asyncio API) to the connect call.
  • This automatically includes traceparent, tracestate, and baggage headers if the corresponding propagators are configured in the global textmap.

Server side (server.py)

  • In Server.serve(), extract the OTel context from websocket.request_headers via propagate.extract().
  • Attach the extracted context as the ambient OTel context for the lifetime of the connection using context.attach() / context.detach().
  • Any handler code running within the connection can now read baggage via baggage.get_all() and inherits the caller's trace context.

Tests (tests/v1/test_opentelemetry.py)

  • test_baggage_propagated_via_ws_headers: Sets two baggage entries on the client, verifies the server handler can read them.
  • test_no_baggage_when_none_set: Verifies clean behavior when no baggage is set.
  • test_traceparent_propagated_via_ws_headers: Sets both an active span and baggage on the client, verifies both propagate.

Test plan

$ uv run pytest tests/ -v
64 passed in 8.46s

All existing tests pass unchanged. The 3 new tests verify end-to-end OTel context propagation through the WebSocket connection.

Revertibility

Safe to revert — only adds new extra_headers/additional_headers to websockets.connect() and a propagate.extract() + context.attach() wrapper on the server. No wire protocol changes, no schema changes, no data mutations.

~ written by Zerg 👾

Propagate traceparent, tracestate, and baggage through the WebSocket
connection using standard W3C HTTP headers on the upgrade request,
matching how any HTTP-based service would propagate OTel context.

Client side (v1 + v2):
- Use propagate.inject() to capture the current OTel context into a
  headers dict, then pass it as extra_headers/additional_headers to
  websockets.connect().

Server side:
- In Server.serve(), use propagate.extract() on websocket.request_headers
  to restore the OTel context, then attach it as the ambient context for
  the lifetime of the connection.
@lhchavez lhchavez marked this pull request as ready for review March 1, 2026 00:25
@lhchavez lhchavez requested a review from a team as a code owner March 1, 2026 00:25
@lhchavez lhchavez requested review from darshkpatel and imen-kedir and removed request for a team March 1, 2026 00:25
@lhchavez lhchavez enabled auto-merge (squash) March 1, 2026 00:29
@lhchavez lhchavez merged commit 6c7a537 into main Mar 1, 2026
3 checks passed
@lhchavez lhchavez deleted the feat/otel-ws-header-propagation branch March 1, 2026 00:41
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