Skip to Content
ftui-runtimeTelemetry

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

crates/ftui-runtime/src/telemetry_schema.rs
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";
TargetCovers
ftui.runtimeProgram lifecycle, lane resolution, rollout policy, reconcile timings.
ftui.effectCmd execution, effect-queue enqueue/process/drop, task panics.
ftui.processProcessSubscription spawn, stdout/stderr, exit.
ftui.decision.resizeResize coalescer verdicts and SLA events.
ftui.voiValue-of-information sampling for adaptive ticking.
ftui.bocpdBayesian online change-point detection (anomaly detection).
ftui.eprocessE-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.

crates/ftui-runtime/src/telemetry_schema.rs
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:

crates/ftui-runtime/src/telemetry_schema.rs
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:

dashboard.rs
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:

FeatureEffectCost when off
tracingEmit 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.
telemetryPulls 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:

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"]:

main.rs
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.

VariableMeaning
OTEL_SDK_DISABLED=trueDisable telemetry entirely, even if other vars are set.
OTEL_TRACES_EXPORTER=otlpExplicitly enable OTLP export.
OTEL_TRACES_EXPORTER=noneExplicitly disable.
OTEL_EXPORTER_OTLP_ENDPOINTCollector endpoint (gRPC or http/protobuf).
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTTraces-only endpoint (overrides).
FTUI_OTEL_HTTP_ENDPOINTFrankenTUI-specific convenience (http/protobuf).
OTEL_SERVICE_NAMEService name on the OTLP resource.
OTEL_TRACE_ID / OTEL_PARENT_SPAN_IDExplicit 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:

main.rs
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 run

Pitfalls

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.

Cross-references