This release is a pre-release and may not be stable for production use.
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. WhenOTEL_TRACES_EXPORTERorOTEL_METRICS_EXPORTERis set and no SDK provider has been installed yet,instrument()configuresopentelemetry-sdkfrom theOTEL_*environment, exactly asopentelemetry-instrumentwould: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-httpfor this HTTP/protobuf configuration. Without the protocol setting,otlpdefaults 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_instrumentorentry point, soopentelemetry-instrument reflex runenables it (the auto-instrumentationsitecustomizereaches the backend worker through the inheritedPYTHONPATH).
What you get
Traces:
- One span per event handler run, named after the event:
CONSUMERfor events sent by the frontend (a new trace, or a child of the browser'sPRODUCERspan when the event carries atraceparentfield),INTERNALfor chained events, which are children of the span that enqueued them. Only stringtraceparent/tracestatefields are read from the event; they never reach the handler, andbaggageor anything else a client sends is ignored. The sampled flag of a clienttraceparentis honoured by the SDK's default parent-based sampler, so a client decides whether its own events are recorded; useParentBased(root=..., remote_parent_sampled=..., remote_parent_not_sampled=...)orOTEL_TRACES_SAMPLER=always_onto 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.compilespan per app compile (reflex.compile.trigger,reflex.compile.dry_run) with the stagesreflex.compile.evaluate_pages,.pages,.copy_assets,.install_frontend_packages,.writeas 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
PRODUCERspan and a W3Ctraceparent, 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 withweb_vital.value/web_vital.rating; - with
render_timing=True, React commits asreact.renderspans (react.render.phase,react.render.actual_duration_ms); this aliasesreact-dom/clientto thereact-dom/profilingbuild and emits one span per commit, so it is off by default; socket.connect/socket.disconnectspans 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.2.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| reflex_otel-0.2.0a1.tar.gz | 12.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reflex_otel-0.2.0a1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.7 kB
Release files / reflex_otel-0.2.0a1.tar.gz
| Download URL | reflex_otel-0.2.0a1.tar.gz |
|---|---|
| Size | 12.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a4bc9cfa5975e15935eaa358866eaec6880ba44d8bd42629431bd0cdfefcf4f7
|
|
BLAKE2b-256 checksum How to use checksums |
1312802c1dfcf71e63353de99be20124eb5a4c3f5837f47759d0405d4ad1399f
|
| 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.2.0a1-py3-none-any.whl
| Download URL | reflex_otel-0.2.0a1-py3-none-any.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c57cea0cfc46941c9b8f9e982ecd8423789924070b9549a4b11df1aa945e3f9
|
|
BLAKE2b-256 checksum How to use checksums |
bdea559a127db16b5e7d2dcd66bf0265a7d0071f20c3b83f45a1947b59bf7b16
|
| 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}
|