1use std::sync::Arc;
10
11#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
14pub struct ReliabilityObservation {
15 pub id: String,
16 pub event: String,
17 pub timestamp: String,
18 pub service: Option<String>,
19 pub operation: Option<String>,
20 pub environment: Option<String>,
21 pub deployment: Option<String>,
22 pub outcome: String,
23 pub conditions: Vec<String>,
24 pub duration_ms: Option<u64>,
25 pub trace_id: Option<String>,
26}
27
28impl ReliabilityObservation {
29 pub fn new(id: impl Into<String>, event: impl Into<String>) -> Self {
30 ReliabilityObservation {
31 id: id.into(),
32 event: event.into(),
33 timestamp: String::new(),
34 service: None,
35 operation: None,
36 environment: None,
37 deployment: None,
38 outcome: String::new(),
39 conditions: Vec::new(),
40 duration_ms: None,
41 trace_id: None,
42 }
43 }
44}
45
46pub trait ObservationSink: Send + Sync {
50 fn emit(&self, observation: &ReliabilityObservation);
51}
52
53#[derive(Debug, Default, Clone)]
55pub struct NoopSink;
56
57impl ObservationSink for NoopSink {
58 fn emit(&self, _observation: &ReliabilityObservation) {}
59}
60
61#[derive(Debug, Default)]
64pub struct CapturingSink {
65 lines: std::sync::Mutex<Vec<String>>,
66}
67
68impl CapturingSink {
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn lines(&self) -> Vec<String> {
74 self.lines.lock().map(|g| g.clone()).unwrap_or_default()
75 }
76}
77
78impl ObservationSink for CapturingSink {
79 fn emit(&self, observation: &ReliabilityObservation) {
80 if let Ok(line) = serde_json::to_string(observation) {
81 if let Ok(mut g) = self.lines.lock() {
82 g.push(line);
83 }
84 }
85 }
86}
87
88pub type SharedSink = Arc<dyn ObservationSink>;
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn capturing_sink_records() {
97 let sink = CapturingSink::new();
98 let obs = ReliabilityObservation::new("obs-1", "failure.network.timeout");
99 sink.emit(&obs);
100 let lines = sink.lines();
101 assert_eq!(lines.len(), 1);
102 assert!(lines[0].contains("failure.network.timeout"));
103 }
104
105 #[test]
106 fn observation_is_plain_data() {
107 let obs = ReliabilityObservation::new("obs-1", "failure.network.timeout");
108 assert_eq!(obs.event, "failure.network.timeout");
109 assert!(obs.duration_ms.is_none());
110 }
111}