appcore_ops/
observation_metrics.rs1use crate::{
14 InMemoryMetrics, ObservationEvent, ObservationKind, ObservationSeverity, ObservationSink,
15 SharedObservationEvent,
16};
17use std::sync::Arc;
18
19#[derive(Debug, Clone)]
21pub struct ObservationMetricsSink {
22 metrics: Arc<InMemoryMetrics>,
23}
24
25impl ObservationMetricsSink {
26 pub fn new(metrics: Arc<InMemoryMetrics>) -> Self {
28 Self { metrics }
29 }
30
31 pub fn metrics(&self) -> Arc<InMemoryMetrics> {
33 Arc::clone(&self.metrics)
34 }
35
36 fn record(&self, event: &ObservationEvent) {
37 let _ = self.metrics.increment("appcore.observations.total");
38 let _ = self.metrics.increment(kind_metric(event.kind));
39 let _ = self.metrics.increment(severity_metric(event.severity));
40 }
41}
42
43impl ObservationSink for ObservationMetricsSink {
44 fn emit(&self, event: ObservationEvent) {
45 self.record(&event);
46 }
47
48 fn emit_shared(&self, event: &SharedObservationEvent) {
49 self.record(event.as_event());
50 }
51}
52
53fn kind_metric(kind: ObservationKind) -> &'static str {
54 match kind {
55 ObservationKind::Lifecycle => "appcore.observations.kind.lifecycle",
56 ObservationKind::Configuration => "appcore.observations.kind.configuration",
57 ObservationKind::Health => "appcore.observations.kind.health",
58 ObservationKind::Security => "appcore.observations.kind.security",
59 ObservationKind::Storage => "appcore.observations.kind.storage",
60 ObservationKind::ControlPlane => "appcore.observations.kind.control_plane",
61 ObservationKind::PeerRpc => "appcore.observations.kind.peer_rpc",
62 ObservationKind::Scheduler => "appcore.observations.kind.scheduler",
63 ObservationKind::Sync => "appcore.observations.kind.sync",
64 ObservationKind::Audit => "appcore.observations.kind.audit",
65 ObservationKind::Diagnostic => "appcore.observations.kind.diagnostic",
66 }
67}
68
69fn severity_metric(severity: ObservationSeverity) -> &'static str {
70 match severity {
71 ObservationSeverity::Debug => "appcore.observations.severity.debug",
72 ObservationSeverity::Info => "appcore.observations.severity.info",
73 ObservationSeverity::Warning => "appcore.observations.severity.warning",
74 ObservationSeverity::Error => "appcore.observations.severity.error",
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn records_bounded_kind_and_severity_dimensions() {
84 let metrics = Arc::new(InMemoryMetrics::new());
85 let sink = ObservationMetricsSink::new(Arc::clone(&metrics));
86 sink.emit(ObservationEvent::new(
87 ObservationKind::Storage,
88 ObservationSeverity::Warning,
89 "untrusted.dynamic.name",
90 1,
91 ));
92
93 let snapshot = metrics.snapshot();
94 assert!(snapshot
95 .iter()
96 .any(|metric| metric.name == "appcore.observations.total" && metric.value == 1));
97 assert!(snapshot.iter().any(|metric| {
98 metric.name == "appcore.observations.kind.storage" && metric.value == 1
99 }));
100 assert!(snapshot.iter().any(|metric| {
101 metric.name == "appcore.observations.severity.warning" && metric.value == 1
102 }));
103 assert_eq!(snapshot.len(), 3);
104 }
105}