fix(observability): prevent otel reactor panic in non-tokio contexts

This commit is contained in:
Chummy 2026-02-20 15:35:54 +08:00
parent 2d6205ee58
commit e7ccb573fa
3 changed files with 44 additions and 2 deletions

View file

@ -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 }

View file

@ -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:

View file

@ -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"
);
}