camel_component_api/runtime_observability.rs
1//! `RuntimeObservability` — narrow trait exposing `metrics()` and `health()`
2//! to Endpoint consumers/producers. Defined per ADR-0012 §Signal-replacement-API
3//! and the Phase A closure spec
4//! (`docs/superpowers/specs/2026-06-05-adr-0012-closure-design.md`).
5//!
6//! **Why a separate trait:** `ComponentContext` carries `resolve_component`,
7//! `resolve_language`, `platform_service` — far more authority than an
8//! emitter needs. Passing `Arc<dyn ComponentContext>` would be a
9//! service-locator anti-pattern inviting future misuse. This trait exposes
10//! exactly the two observability surfaces an ADR-0012 emitter calls.
11
12use std::sync::Arc;
13use std::sync::atomic::AtomicU64;
14
15use camel_api::{ComponentMetrics, MetricsCollector};
16
17use crate::{ComponentContext, HealthCheckRegistry};
18
19/// Narrow observability surface available to Endpoint consumers and producers.
20///
21/// Implemented blanket for every `T: ComponentContext`, so
22/// `CamelContext` (the spec's "DefaultRuntime") and any test ctx
23/// automatically satisfy this trait. Endpoints receive
24/// `Arc<dyn RuntimeObservability>` at `create_consumer` / `create_producer`
25/// time (per Phase A closure spec).
26///
27/// `Send + Sync` is mandatory — Endpoints spawn across tokio tasks.
28pub trait RuntimeObservability: Send + Sync {
29 /// Active metrics collector. Used for `increment_errors(route_id, label)`
30 /// per ADR-0012 categories (b′) and (e).
31 fn metrics(&self) -> Arc<dyn MetricsCollector>;
32
33 /// Active health-check registry. Used for
34 /// `force_unhealthy_for_route(route_id, name, reason)` per ADR-0012
35 /// category (g).
36 fn health(&self) -> Arc<dyn HealthCheckRegistry>;
37
38 /// Facade for the uniform component-operations family
39 /// (`camel_component_operations_total{component,operation,outcome}`,
40 /// dashboard-observability Task 4.1). The lever snapshot is baked in
41 /// at facade construction: the component series flow only with the
42 /// `[observability.metrics].components` lever on, while failures
43 /// always reach the non-disableable error family.
44 ///
45 /// Default: lever off — impls that do not override
46 /// `component_metrics_enabled()` (manual test runtimes AND any
47 /// context wired without a levers snapshot) keep the component
48 /// series suppressed and only forward errors. In production the
49 /// controller-path `ControllerComponentContext` overrides it with
50 /// the levers snapshot taken at pipeline assembly.
51 fn component_metrics(&self) -> ComponentMetrics {
52 ComponentMetrics::new(self.metrics(), false)
53 }
54
55 /// Context-global counter of accepted-not-completed exchanges
56 /// (drainclaim), forwarded from [`ComponentContext::in_flight_counter`].
57 /// Default `None` keeps runtimes that install no counter uncounted.
58 fn in_flight_counter(&self) -> Option<Arc<AtomicU64>> {
59 None
60 }
61}
62
63// Blanket impl: every ComponentContext is automatically a RuntimeObservability.
64// This covers `CamelContext` (production), `NoOpComponentContext` (tests),
65// and any future impl.
66//
67// No `?Sized` bound: ComponentContext itself requires Sized (the trait has
68// methods returning `Arc<dyn …>` which need a concrete owner type). If a
69// future dynamically-sized ctx is needed, add a separate explicit impl.
70impl<T: ComponentContext> RuntimeObservability for T {
71 fn metrics(&self) -> Arc<dyn MetricsCollector> {
72 <Self as ComponentContext>::metrics(self)
73 }
74
75 fn health(&self) -> Arc<dyn HealthCheckRegistry> {
76 <Self as ComponentContext>::health(self)
77 }
78
79 fn component_metrics(&self) -> ComponentMetrics {
80 ComponentMetrics::new(
81 <Self as ComponentContext>::metrics(self),
82 <Self as ComponentContext>::component_metrics_enabled(self),
83 )
84 }
85
86 fn in_flight_counter(&self) -> Option<Arc<AtomicU64>> {
87 <Self as ComponentContext>::in_flight_counter(self)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn runtime_observability_is_send_sync() {
97 fn assert_send_sync<T: Send + Sync + ?Sized>() {}
98 assert_send_sync::<dyn RuntimeObservability>();
99 }
100}