Skip to content

ENG-9166 feat(otel): browser tracing plugin — traceparent per event, web vitals, render timing (3/3) - #6901

Open
FarhanAliRaza wants to merge 3 commits into
farhan/eng-9166-reflex-otel-3from
farhan/eng-9166-reflex-otel-4
Open

ENG-9166 feat(otel): browser tracing plugin — traceparent per event, web vitals, render timing (3/3)#6901
FarhanAliRaza wants to merge 3 commits into
farhan/eng-9166-reflex-otel-3from
farhan/eng-9166-reflex-otel-4

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Part of #6227 (ENG-9166). Stack: 1/32/33/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 to rx.Config(plugins=[...])) compiles a small OpenTelemetry web bundle into the frontend:

  • every event sent to the backend gets a PRODUCER span and a W3C traceparent, so one user interaction is one trace: browser → backend SERVER event → chained INTERNAL events;
  • web vitals (web_vital.LCP/CLS/INP/FCP/TTFB) as spans with value/rating attributes;
  • socket.connect / socket.disconnect spans (unintentional disconnects marked ERROR);
  • opt-in render_timing=True: react.render span per React commit via a root <Profiler>; the plugin aliases react-dom/client → react-dom/profiling in vite.config.js because <Profiler> is a no-op in production builds otherwise. Off by default (span volume). Verified in a --env prod build.

Wiring: get_frontend_dependencies (pinned @opentelemetry/*, web-vitals), get_static_assets (utils/otel.js), update_env_json (OTEL entry: endpoint, service name, headers, flags), add_modify_task on entry.client.js (import + root wrap). Endpoint defaults follow OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces; must allow CORS. headers are compiled into the public bundle (documented).

Framework seam: state.js calls window.__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).

Review in cubic

@FarhanAliRaza
FarhanAliRaza requested review from a team and Alek99 as code owners August 17, 2026 20:58
@linear-code

linear-code Bot commented Aug 17, 2026

Copy link
Copy Markdown

ENG-9166

@codspeed-hq

codspeed-hq Bot commented Aug 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing farhan/eng-9166-reflex-otel-4 (e8571d8) with farhan/eng-9166-reflex-otel-3 (7c86654)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on farhan/eng-9166-reflex-otel-3 (acf9a22) during the generation of this report, so 29e0c67 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds an optional browser OpenTelemetry plugin and connects it to Reflex’s frontend event and socket lifecycle.

  • Adds trace-context propagation for browser events, web-vital spans, socket spans, and optional React render timing.
  • Adds compile-time dependency, asset, environment, entry-point, and Vite profiling configuration.
  • Adds browser-observability documentation and unit coverage for plugin compilation behavior.

Confidence Score: 4/5

The 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

Important Files Changed

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

Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js
Comment thread packages/reflex-otel/src/reflex_otel/plugin.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-otel/src/reflex_otel/plugin.py Outdated
Comment thread packages/reflex-otel/src/reflex_otel/plugin.py
Comment thread tests/units/reflex_otel/test_plugin.py
@FarhanAliRaza
FarhanAliRaza force-pushed the farhan/eng-9166-reflex-otel-4 branch from e732a12 to e8571d8 Compare August 17, 2026 21:53

@Alek99 Alek99 left a comment

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.

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({

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.

[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);

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.

[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();

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.

[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.

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