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