Observability¶
The observability / production layer (Track O) gives your app telemetry, structured logs, an error boundary, feature flags and client auth — all in typed Python, identical in both modes. 📊
Under construction (Track O)
This layer is the roadmap's Track O. Phases O0–O4 are detailed in the design plan. This page describes the planned surface and the adapter pattern.
The adapter pattern¶
Every provider follows the same principle: a minimal interface that you swap without touching the app. You program against the API; the adapter decides where it goes (console, Sentry, GrowthBook, …).
your app ──calls──▶ Provider (stable API) ──delegates──▶ Adapter (backend)
console / sentry / posthog / ...
Swapping backend does not change calls
Migrating from console to sentry changes no track() call. It is the
same promise as tempest-react-sdk, now in typed Python.
O0 — Telemetry¶
Instruments framework and app events (service worker, push, offline replay, errors) with a pluggable provider.
from tempestweb.observability import telemetry
from tempestweb.observability.telemetry import ConsoleAdapter
telemetry.init(adapter=ConsoleAdapter())
telemetry.track("order_submitted", {"items": 3, "total": 99.9})
telemetry.identify("user-42")
Do not leak PII
Do not put personal data in props, and use sampling so you do not flood the
backend. Telemetry is diagnostics, not a user database.
O1 — Logger¶
Structured logging with pluggable sinks and typed levels (LogLevel).
from tempestweb.observability import create_logger, console_sink
log = create_logger(sinks=[console_sink])
log.info("order created", order_id="o-1", total=99.9)
log.error("payment failed", order_id="o-1", reason="card_declined")
In Mode A the default sink is the browser console
Network sinks (sending logs to a server) must be async/non-blocking — in Mode A a blocking sink freezes the tab.
O2 — Error boundary¶
Captures render errors → shows a visual fallback + fires a report, without bringing the app down. The rest of the tree stays alive.
from tempestweb.observability import error_boundary
from tempest_core import Text, Widget
@error_boundary(fallback=lambda err: Text(content=f"Something broke: {err}"))
def risky_panel(app: object) -> Widget:
"""Render a panel that may raise during build.
Args:
app: The running app handle.
Returns:
The rendered panel widget.
"""
return build_dashboard(app.state) # if it raises, the fallback appears
Render error ≠ async handler error
The boundary catches render errors (during view()). Async handler errors
go to the event loop's handling. In both cases, report — never swallow the
stack.
O3 — Feature flags¶
Toggles features at runtime with gradual rollout. The adapter interface is tiny (~20 lines to implement a new one).
from tempestweb.observability import feature_flags
from tempestweb.observability.feature_flags import InMemoryAdapter
feature_flags.init(adapter=InMemoryAdapter({"new_checkout": True}))
def view(app: object) -> object:
"""Render checkout, gated by a feature flag."""
if feature_flags.is_enabled("new_checkout"):
return new_checkout(app)
return legacy_checkout(app)
Flags are not secrets; have a safe default
When the flags backend is down, is_enabled must fall back to a safe
default — never break the app. And never use flags to hide secrets: they are
visible on the client.
O4 — Client auth¶
Auth store + route guard + JWT helpers + a refresh queue that serializes concurrent renewals (one refresh, many waiters).
from tempestweb.observability import create_auth_store, create_refresh_queue
auth = create_auth_store()
refresh = create_refresh_queue(auth)
async def call_api(app: object) -> dict[str, object]:
"""Call a protected endpoint, refreshing the token once if needed.
Args:
app: The running app handle.
Returns:
The decoded JSON response.
"""
if auth.is_token_expired():
await refresh.run() # many concurrent calls => ONE refresh
response = await app.native.http.request(
"GET", "/api/me", headers={"Authorization": f"Bearer {auth.token}"}
)
return response.json()
The token lives in different places per mode
In Mode A the token lives in the browser (storage) — treat XSS as a
real risk. In Mode B it lives in the server session, more protected. The
server reuses JWTUtils from tempest-fastapi-sdk.
Recap¶
- Observability uses the adapter pattern: swap the backend without changing the app.
- Telemetry (O0), Logger (O1), Error boundary (O2), Feature flags (O3) and Auth (O4) are all typed Python, identical in both modes.
- Safe defaults and care with PII/tokens are part of the contract.
This layer mirrors the tempest-react-sdk providers. For phase-by-phase detail,
read the
design plan.
🚀