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

Merged
FA
1mo ago
+1132-224

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