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