Telemetry
FrankenTUI emits structured tracing data through the tracing crate
and, optionally, exports spans and metrics to an OTLP collector
(Jaeger, Tempo, Honeycomb, Datadog, …). Names are canonical: every
target, event, metric, and field has a constant in
telemetry_schema.rs so dashboards and CI grep patterns remain stable.
Files:
- Canonical schema:
crates/ftui-runtime/src/telemetry_schema.rs - OTLP integration:
crates/ftui-runtime/src/telemetry.rs(feature-gated) - Counters:
crates/ftui-runtime/src/effect_system.rs
Canonical tracing targets
pub const TARGET_RUNTIME : &str = "ftui.runtime";
pub const TARGET_EFFECT : &str = "ftui.effect";
pub const TARGET_PROCESS : &str = "ftui.process";
pub const TARGET_RESIZE : &str = "ftui.decision.resize";
pub const TARGET_VOI : &str = "ftui.voi";
pub const TARGET_BOCPD : &str = "ftui.bocpd";
pub const TARGET_EPROCESS: &str = "ftui.eprocess";| Target | Covers |
|---|---|
ftui.runtime | Program lifecycle, lane resolution, rollout policy, reconcile timings. |
ftui.effect | Cmd execution, effect-queue enqueue/process/drop, task panics. |
ftui.process | ProcessSubscription spawn, stdout/stderr, exit. |
ftui.decision.resize | Resize coalescer verdicts and SLA events. |
ftui.voi | Value-of-information sampling for adaptive ticking. |
ftui.bocpd | Bayesian online change-point detection (anomaly detection). |
ftui.eprocess | E-process throttles and barrier events. |
Canonical event names
Event names are the event = "..." field inside a span. They let
dashboards filter reliably without parsing span names.
pub mod event {
pub const RUNTIME_STARTUP : &str = "runtime.startup";
pub const EFFECT_QUEUE_SHUTDOWN : &str = "effect_queue.shutdown";
pub const SPAWN_EXECUTOR_SHUTDOWN: &str = "spawn_executor.shutdown";
pub const SUBSCRIPTION_STOP_ALL : &str = "subscription.stop_all";
pub const SUBSCRIPTION_STOP : &str = "subscription.stop";
pub const EFFECT_COMMAND : &str = "effect.command";
pub const EFFECT_SUBSCRIPTION : &str = "effect.subscription";
pub const QUEUE_DROP : &str = "effect_queue.drop";
pub const EFFECT_TIMEOUT : &str = "effect.timeout";
pub const EFFECT_PANIC : &str = "effect.panic";
}Canonical metric names (counters)
Stable names for the atomic counters exposed by the effect system:
pub mod metric {
pub const EFFECTS_COMMAND_TOTAL : &str = "effects_command_total";
pub const EFFECTS_SUBSCRIPTION_TOTAL : &str = "effects_subscription_total";
pub const EFFECTS_EXECUTED_TOTAL : &str = "effects_executed_total";
pub const EFFECTS_QUEUE_ENQUEUED : &str = "effects_queue_enqueued";
pub const EFFECTS_QUEUE_PROCESSED : &str = "effects_queue_processed";
pub const EFFECTS_QUEUE_DROPPED : &str = "effects_queue_dropped";
pub const EFFECTS_QUEUE_HIGH_WATER : &str = "effects_queue_high_water";
pub const EFFECTS_QUEUE_IN_FLIGHT : &str = "effects_queue_in_flight";
}Read them any time without locking:
use ftui_runtime::effect_system::*;
println!("commands : {}", effects_command_total());
println!("queue depth : {} (high water {})",
queue_telemetry().in_flight, queue_telemetry().high_water);
println!("dropped : {}", effects_queue_dropped());Feature flags: tracing vs telemetry
ftui-runtime has two cargo features that affect observability:
| Feature | Effect | Cost when off |
|---|---|---|
tracing | Emit structured spans and events on the targets above. | Most call-sites compile down to no-ops via tracing’s zero-cost macros; enabling the feature doesn’t install a subscriber on its own. |
telemetry | Pulls in opentelemetry, opentelemetry_sdk, opentelemetry-otlp, and tracing-opentelemetry. Enables OTLP export. | None — feature-off leaves the module absent entirely. |
Enable either or both in your own Cargo.toml:
[dependencies]
ftui = { version = "0.3", features = ["telemetry"] }
# or if depending on runtime directly:
ftui-runtime = { version = "0.3.1", features = ["tracing", "telemetry"] }Tracing subscribers are your responsibility. The runtime never
calls try_init on a global subscriber unless you hand it one via
TelemetryConfig::install().
OTLP integration (feature telemetry)
With features = ["telemetry"]:
use ftui_runtime::telemetry::TelemetryConfig;
fn main() -> anyhow::Result<()> {
// Install the OTLP subscriber from env vars.
// Returns a guard that flushes pending spans on drop.
let _telemetry_guard = TelemetryConfig::from_env().install()?;
// ... run your program ...
Ok(())
}For applications that already install a tracing subscriber, use
build_layer() instead and attach the returned layer to your
tracing_subscriber::Registry.
Environment variables
Parsed in TelemetryConfig::from_env. Behaviour is strictly
additive — telemetry is never enabled without an explicit env var.
| Variable | Meaning |
|---|---|
OTEL_SDK_DISABLED=true | Disable telemetry entirely, even if other vars are set. |
OTEL_TRACES_EXPORTER=otlp | Explicitly enable OTLP export. |
OTEL_TRACES_EXPORTER=none | Explicitly disable. |
OTEL_EXPORTER_OTLP_ENDPOINT | Collector endpoint (gRPC or http/protobuf). |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | Traces-only endpoint (overrides). |
FTUI_OTEL_HTTP_ENDPOINT | FrankenTUI-specific convenience (http/protobuf). |
OTEL_SERVICE_NAME | Service name on the OTLP resource. |
OTEL_TRACE_ID / OTEL_PARENT_SPAN_ID | Explicit parent trace context (for subprocesses). |
The resolved configuration (endpoint source, reason for enabled /
disabled, trace-context source) is recorded in a structured evidence
log — see docs/spec/telemetry.md in the repo for the full contract.
Debugging without OTLP
If you just want to see structured logs on stderr:
use tracing_subscriber::{fmt, EnvFilter};
fn main() -> std::io::Result<()> {
fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,ftui=debug")),
)
.init();
// ... run your program ...
Ok(())
}Then:
RUST_LOG=ftui.effect=trace,ftui.runtime=debug cargo runPitfalls
Don’t mix inline mode with stderr trace noise. Inline mode
renders into the current terminal scrollback; verbose tracing on
stderr will interleave with your UI. Use
Cmd::Log, a file subscriber, or the
evidence sink instead.
install() fails if a global subscriber already exists. For
applications that manage their own subscriber, use
TelemetryConfig::from_env().build_layer()? and compose the
returned layer into your registry.