diff --git a/Cargo.toml b/Cargo.toml index de45d4d..10cfdf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,10 +125,13 @@ tower = { version = "0.5", default-features = false } tower-http = { version = "0.6", default-features = false, features = ["limit", "timeout"] } http-body-util = "0.1" -# OpenTelemetry — OTLP trace + metrics export +# OpenTelemetry — OTLP trace + metrics export. +# Use the blocking HTTP exporter client to avoid Tokio-reactor panics in +# OpenTelemetry background batch threads when ZeroClaw emits spans/metrics from +# non-Tokio contexts. opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] } opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] } -opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] } +opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-blocking-client", "reqwest-rustls-webpki-roots"] } # Serial port for peripheral communication (STM32, etc.) tokio-serial = { version = "5", default-features = false, optional = true } diff --git a/docs/config-reference.md b/docs/config-reference.md index 2bc0a93..f19cb27 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -26,6 +26,28 @@ Schema export command: | `default_model` | `anthropic/claude-sonnet-4-6` | model routed through selected provider | | `default_temperature` | `0.7` | model temperature | +## `[observability]` + +| Key | Default | Purpose | +|---|---|---| +| `backend` | `none` | Observability backend: `none`, `noop`, `log`, `prometheus`, `otel`, `opentelemetry`, or `otlp` | +| `otel_endpoint` | `http://localhost:4318` | OTLP HTTP endpoint used when backend is `otel` | +| `otel_service_name` | `zeroclaw` | Service name emitted to OTLP collector | + +Notes: + +- `backend = "otel"` uses OTLP HTTP export with a blocking exporter client so spans and metrics can be emitted safely from non-Tokio contexts. +- Alias values `opentelemetry` and `otlp` map to the same OTel backend. + +Example: + +```toml +[observability] +backend = "otel" +otel_endpoint = "http://localhost:4318" +otel_service_name = "zeroclaw" +``` + ## Environment Provider Overrides Provider selection can also be controlled by environment variables. Precedence is: diff --git a/tests/otel_dependency_feature_regression.rs b/tests/otel_dependency_feature_regression.rs new file mode 100644 index 0000000..0620b75 --- /dev/null +++ b/tests/otel_dependency_feature_regression.rs @@ -0,0 +1,17 @@ +#[test] +fn opentelemetry_otlp_uses_blocking_reqwest_client() { + let manifest = include_str!("../Cargo.toml"); + let otlp_line = manifest + .lines() + .find(|line| line.trim_start().starts_with("opentelemetry-otlp =")) + .expect("Cargo.toml must define opentelemetry-otlp dependency"); + + assert!( + otlp_line.contains("\"reqwest-blocking-client\""), + "opentelemetry-otlp must include reqwest-blocking-client to avoid Tokio reactor panics" + ); + assert!( + !otlp_line.contains("\"reqwest-client\""), + "opentelemetry-otlp must not include async reqwest-client in this runtime mode" + ); +}