Skip to main content

reflex-otel

OpenTelemetry instrumentation for the Reflex framework.

from reflex_otel import ReflexInstrumentor

ReflexInstrumentor().instrument()

Nothing in the app module needs guarding: a second instrument() is a silent no-op, so the line above can run every time the module is imported.

Configure the SDK the way you prefer:

  • Set the standard variables and let instrument() do it. When OTEL_TRACES_EXPORTER or OTEL_METRICS_EXPORTER is set and no SDK provider has been installed yet, instrument() configures opentelemetry-sdk from the OTEL_* environment, exactly as opentelemetry-instrument would:

    OTEL_SERVICE_NAME=myapp OTEL_TRACES_EXPORTER=otlp OTEL_METRICS_EXPORTER=otlp \
      OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
      OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 reflex run
    

    Install opentelemetry-exporter-otlp-proto-http for this HTTP/protobuf configuration. Without the protocol setting, otlp defaults to gRPC and requires the separate gRPC exporter package.

  • Or build the providers yourself and pass them: ReflexInstrumentor().instrument(tracer_provider=provider, meter_provider=meter_provider). Do that from a module that is imported once (not the app module, which test harnesses may re-import) or the SDK warns about overriding providers.

  • Or use no code at all: the package registers an opentelemetry_instrumentor entry point, so opentelemetry-instrument reflex run enables it (the auto-instrumentation sitecustomize reaches the backend worker through the inherited PYTHONPATH).

What you get

Traces:

  • One span per event handler run, named after the event: CONSUMER for events sent by the frontend (a new trace, or a child of the browser's PRODUCER span when the event carries a traceparent field), INTERNAL for chained events, which are children of the span that enqueued them. Only string traceparent/tracestate fields are read from the event; they never reach the handler, and baggage or anything else a client sends is ignored. The sampled flag of a client traceparent is honoured by the SDK's default parent-based sampler, so a client decides whether its own events are recorded; use ParentBased(root=..., remote_parent_sampled=..., remote_parent_not_sampled=...) or OTEL_TRACES_SAMPLER=always_on to keep that decision on the server.
  • HTTP requests and the websocket connection are wrapped in the standard OpenTelemetry ASGI middleware (per-message websocket spans are off).
  • One reflex.compile span per app compile (reflex.compile.trigger, reflex.compile.dry_run) with the stages reflex.compile.evaluate_pages, .pages, .copy_assets, .install_frontend_packages, .write as child spans.

What leaves the process: event and handler names, a pseudonymous session.id (a truncated SHA-256 of the client token, never the token itself), exception types, messages and stack traces of failed handlers, and the ASGI middleware's request attributes with the token query parameter of the websocket URL redacted. Event payloads and state are never recorded.

Metrics:

Instrument Type Unit Attributes
reflex.event.duration histogram s reflex.event.name, reflex.event.background, error.type
reflex.state.acquire.duration histogram s reflex.event.name
reflex.websocket.message.size histogram By network.io.direction (transmit/receive); default sio only
reflex.websocket.connections up-down counter {connection}

Plus the ASGI middleware's http.server.* metrics. The instrumentor opts the middleware into the stable HTTP semantic conventions (OTEL_SEMCONV_STABILITY_OPT_IN=http) unless that variable is already set, so request attributes use the same generation of names as Reflex's own.

Browser (frontend) tracing

# rxconfig.py
from reflex_otel import OtelPlugin

config = rx.Config(
    app_name="myapp",
    plugins=[OtelPlugin(endpoint="https://collector.example.com/v1/traces")],
)

The plugin compiles a small OpenTelemetry web bundle into the frontend:

  • every event sent to the backend gets a PRODUCER span and a W3C traceparent, so the backend event span joins the browser trace (one trace per interaction, browser → backend → chained events);
  • web vitals (web_vital.LCP, CLS, INP, FCP, TTFB) as spans with web_vital.value / web_vital.rating;
  • with render_timing=True, React commits as react.render spans (react.render.phase, react.render.actual_duration_ms); this aliases react-dom/client to the react-dom/profiling build and emits one span per commit, so it is off by default;
  • socket.connect / socket.disconnect spans for reconnect tracking (unintentional disconnects are marked as errors).

Options: endpoint (OTLP/HTTP traces URL reachable from the browser; required to export, with no default and no OTEL_EXPORTER_OTLP_* fallback: without it no exporter is installed and browser spans are dropped), service_name (default <app_name>-frontend), headers (compiled into the public bundle — no secrets), web_vitals, render_timing. The endpoint must allow CORS from the app origin.

Options

instrument() accepts tracer_provider, meter_provider, excluded_urls (comma-separated URL patterns skipped by the ASGI middleware; defaults to OTEL_PYTHON_REFLEX_EXCLUDED_URLS, else OTEL_PYTHON_EXCLUDED_URLS, else /ping plus the compiled frontend's /assets/ when the backend serves it, as reflex run --env prod does on one port; pass "", or set the variable to an empty string, to exclude nothing) and the ASGI hooks server_request_hook, client_request_hook, client_response_hook. Call instrument() before the app is served: uninstrument() turns the framework trace points off again, but an ASGI middleware that was already installed stays until the process restarts.

Metadata

Release files for reflex-otel 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reflex-otel 0.1.0
File Size Uploaded
reflex_otel-0.1.0.tar.gz 12.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for reflex-otel 0.1.0
File Interpreter ABI Platform
reflex_otel-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.5 kB

Release files / reflex_otel-0.1.0.tar.gz

Download URL reflex_otel-0.1.0.tar.gz
Size 12.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7a51dca2331ae046d620e626b45045ab678498ce6585f9977a8b05432b757e7b
BLAKE2b-256 checksum
How to use checksums
725330b2ad67c63a6f2e3f20e50a0b330ba8c9fd6e9d7aa3efa4549584f0133c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / reflex_otel-0.1.0-py3-none-any.whl

Download URL reflex_otel-0.1.0-py3-none-any.whl
Size 14.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a98fd362026c2901280fc2bb85f31a7fd11bead11c06de1ca0a83b2b44c13f2a
BLAKE2b-256 checksum
How to use checksums
1171e06f8ff195f7eb762fd7ae1719bc06cc371700983083c59fb51055d1d4cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page