ENG-9166 feat(otel): browser tracing plugin — traceparent per event, web vitals, render timing (3/3) - #6901
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThe PR adds an optional browser OpenTelemetry plugin and connects it to Reflex’s frontend event and socket lifecycle.
Confidence Score: 4/5The PR is not yet safe to merge because telemetry exceptions can still interrupt core event delivery and socket lifecycle processing. The previously reported isolation issue remains: each telemetry callback executes synchronously before required event emission, hydration, cleanup, or reconnection work, without a failure boundary. Files Needing Attention: packages/reflex-base/src/reflex_base/.templates/web/utils/state.js
|
| Filename | Overview |
|---|---|
| packages/reflex-base/src/reflex_base/.templates/web/utils/state.js | Adds optional telemetry callbacks around event sends and socket connection lifecycle. |
| packages/reflex-otel/src/reflex_otel/otel.js | Implements browser tracing, trace-context injection, web-vital spans, socket spans, and React render profiling. |
| packages/reflex-otel/src/reflex_otel/plugin.py | Adds compile-time browser assets, pinned frontend dependencies, public OTEL configuration, entry wrapping, and the optional React profiling alias. |
| tests/units/reflex_otel/test_plugin.py | Covers endpoint resolution, generated configuration and assets, idempotent entry patching, and profiling alias behavior. |
| docs/api-reference/observability.md | Documents backend instrumentation and optional frontend tracing configuration and behavior. |
Reviews (4): Last reviewed commit: "fix(otel): put the profiling alias insid..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 81df35005c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
All reported issues were addressed across 11 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
81df350 to
cd466c4
Compare
cd466c4 to
e732a12
Compare
e732a12 to
e8571d8
Compare
Alek99
left a comment
There was a problem hiding this comment.
The browser → backend → chained-event parentage, production render spans, FCP, and TTFB all worked in a local Reflex app. These remaining browser-policy/lifecycle issues are not covered by the happy path.
|
|
||
| const config = env.OTEL ?? {}; | ||
|
|
||
| const provider = new WebTracerProvider({ |
There was a problem hiding this comment.
[P2] Make browser sampling configurable or align it with the backend policy
WebTracerProvider defaults to ParentBased(AlwaysOn), and these browser spans are roots. Every injected traceparent therefore carries sampled=1; a backend configured with the common parentbased_traceidratio policy (for example 1%) correctly honors that remote sampled parent and records effectively 100% of browser event traces. This can defeat the deployment's intended volume/cost control. Please expose a browser sample rate/sampler (or compile the relevant OTel sampler settings) and test the propagated trace flags.
| "web_vital.id": metric.id, | ||
| "web_vital.navigation_type": metric.navigationType, | ||
| }; | ||
| let startTime = epoch(0); |
There was a problem hiding this comment.
[P2] Anchor Web Vital intervals to the current navigation lifecycle
FCP/LCP/TTFB values are not always offsets from the original page timeOrigin. web-vitals@6.1.1 supplies metric.navigationStartTime for BFCache restores, and prerender values are relative to activation. Using epoch(0) here places those spans at the original load instead of the restore/activation (potentially tens of seconds early). Build the base timestamp from navigationStartTime/the relevant performance entry and activationStart, with BFCache and prerender coverage.
| if (!reason.startsWith("io ")) { | ||
| span.setStatus({ code: SpanStatusCode.ERROR, message: reason }); | ||
| } | ||
| span.end(); |
There was a problem hiding this comment.
[P2] Flush an unload-triggered disconnect span after ending it
The pinned browser BatchSpanProcessor registers its visibilitychange/pagehide force-flush listeners during provider construction, before Reflex later registers the socket pagehide handler. On the pagehide/BFCache path, that flush can snapshot and empty the queue before onSocketDisconnect() creates this span; the new span then sits on the normal five-second timer and is lost when the page tears down. My live OTLP capture exported transport/ping disconnects but no intentional io client disconnect. Force-flush after ending the intentional unload span, or ensure the disconnect hook runs before the processor's hide flush.
Part of #6227 (ENG-9166). Stack: 1/3 → 2/3 → 3/3 (this). Base is
farhan/eng-9166-reflex-otel-3; the diff is the two commits on top of 2/3.What
reflex_otel.OtelPlugin(add torx.Config(plugins=[...])) compiles a small OpenTelemetry web bundle into the frontend:PRODUCERspan and a W3Ctraceparent, so one user interaction is one trace: browser → backendSERVERevent → chainedINTERNALevents;web_vital.LCP/CLS/INP/FCP/TTFB) as spans with value/rating attributes;socket.connect/socket.disconnectspans (unintentional disconnects marked ERROR);render_timing=True:react.renderspan per React commit via a root<Profiler>; the plugin aliasesreact-dom/client → react-dom/profilinginvite.config.jsbecause<Profiler>is a no-op in production builds otherwise. Off by default (span volume). Verified in a--env prodbuild.Wiring:
get_frontend_dependencies(pinned@opentelemetry/*,web-vitals),get_static_assets(utils/otel.js),update_env_json(OTELentry: endpoint, service name, headers, flags),add_modify_taskonentry.client.js(import + root wrap). Endpoint defaults followOTEL_EXPORTER_OTLP_TRACES_ENDPOINT/OTEL_EXPORTER_OTLP_ENDPOINT→/v1/traces; must allow CORS.headersare compiled into the public bundle (documented).Framework seam:
state.jscallswindow.__reflex_otel?.onEventSend/onSocketConnect/onSocketDisconnect— undefined unless the plugin is active.Docs: new
docs/api-reference/observability.md(backend + frontend setup, hot-reload-safe guard, metrics table). Log correlation is out of this stack (follow-up: #6903).Known limitation: the browser has no per-event completion signal, so the browser event span has no duration; it exists to carry the trace context.
Tests
tests/units/reflex_otel/test_plugin.py(env/endpoint resolution, asset, env.json, entry patch idempotency + warning, profiling alias patch).