1use std::collections::HashSet;
14use std::sync::Arc;
15
16use async_trait::async_trait;
17use chrono::{DateTime, Utc};
18use serde::{Deserialize, Serialize};
19use thiserror::Error;
20
21mod ring;
22pub use ring::RingBufferTelemetry;
23
24#[async_trait]
25pub trait TelemetrySink: Send + Sync {
26 async fn record(&self, event: TelemetryEvent) -> Result<(), TelemetryError>;
27 async fn query(&self, filter: &TelemetryFilter) -> Result<Vec<TelemetryEvent>, TelemetryError>;
28}
29
30pub type SharedTelemetrySink = Arc<dyn TelemetrySink>;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TelemetryEvent {
34 pub at: DateTime<Utc>,
35 pub kind: TelemetryKind,
36 #[serde(skip_serializing_if = "Option::is_none", default)]
37 pub mediator_did: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none", default)]
39 pub sender_did: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none", default)]
41 pub message_type: Option<String>,
42 #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
43 pub fields: serde_json::Map<String, serde_json::Value>,
44}
45
46impl TelemetryEvent {
47 pub fn new(kind: TelemetryKind) -> Self {
48 Self {
49 at: Utc::now(),
50 kind,
51 mediator_did: None,
52 sender_did: None,
53 message_type: None,
54 fields: serde_json::Map::new(),
55 }
56 }
57
58 pub fn with_mediator(mut self, did: impl Into<String>) -> Self {
59 self.mediator_did = Some(did.into());
60 self
61 }
62
63 pub fn with_sender(mut self, did: impl Into<String>) -> Self {
64 self.sender_did = Some(did.into());
65 self
66 }
67
68 pub fn with_message_type(mut self, ty: impl Into<String>) -> Self {
69 self.message_type = Some(ty.into());
70 self
71 }
72
73 pub fn with_field(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
74 self.fields.insert(key.into(), value);
75 self
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum TelemetryKind {
82 DidcommInbound,
83 DidcommResponseDropped,
84 MediatorHandshakeOk,
85 MediatorHandshakeFailed,
86 MediatorHandshakeBypassed,
87 ServicesDidcommUpdate,
88 MediatorDrainStart,
89 MediatorDrainCancel,
90 MediatorDrainExpire,
91 ServicesDidcommEnable,
92 ServicesDidcommDisable,
93 ServicesRestEnable,
94 ServicesRestUpdate,
95 ServicesRestDisable,
96 ServicesWebauthnEnable,
97 ServicesWebauthnUpdate,
98 ServicesWebauthnDisable,
99 ServicesTspEnable,
100 ServicesTspUpdate,
101 ServicesTspDisable,
102}
103
104#[derive(Debug, Clone, Default)]
105pub struct TelemetryFilter {
106 pub since: Option<DateTime<Utc>>,
107 pub until: Option<DateTime<Utc>>,
108 pub kinds: Option<HashSet<TelemetryKind>>,
109 pub mediator_did: Option<String>,
110 pub sender_did: Option<String>,
111}
112
113impl TelemetryFilter {
114 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn since(mut self, t: DateTime<Utc>) -> Self {
119 self.since = Some(t);
120 self
121 }
122
123 pub fn until(mut self, t: DateTime<Utc>) -> Self {
124 self.until = Some(t);
125 self
126 }
127
128 pub fn kind(mut self, k: TelemetryKind) -> Self {
129 self.kinds.get_or_insert_with(HashSet::new).insert(k);
130 self
131 }
132
133 pub fn mediator(mut self, did: impl Into<String>) -> Self {
134 self.mediator_did = Some(did.into());
135 self
136 }
137
138 pub fn sender(mut self, did: impl Into<String>) -> Self {
139 self.sender_did = Some(did.into());
140 self
141 }
142
143 pub fn matches(&self, ev: &TelemetryEvent) -> bool {
144 if let Some(t) = self.since
145 && ev.at < t
146 {
147 return false;
148 }
149 if let Some(t) = self.until
150 && ev.at > t
151 {
152 return false;
153 }
154 if let Some(ref ks) = self.kinds
155 && !ks.contains(&ev.kind)
156 {
157 return false;
158 }
159 if let Some(ref m) = self.mediator_did
160 && ev.mediator_did.as_deref() != Some(m.as_str())
161 {
162 return false;
163 }
164 if let Some(ref s) = self.sender_did
165 && ev.sender_did.as_deref() != Some(s.as_str())
166 {
167 return false;
168 }
169 true
170 }
171}
172
173#[derive(Debug, Error)]
174pub enum TelemetryError {
175 #[error("telemetry sink backend failed: {0}")]
176 Backend(String),
177}
178
179#[cfg(test)]
180mod swappability_tests {
181 use super::*;
187 use std::sync::Mutex;
188
189 struct VecMutexSink {
193 events: Mutex<Vec<TelemetryEvent>>,
194 }
195
196 impl VecMutexSink {
197 fn new() -> Self {
198 Self {
199 events: Mutex::new(Vec::new()),
200 }
201 }
202 }
203
204 #[async_trait]
205 impl TelemetrySink for VecMutexSink {
206 async fn record(&self, event: TelemetryEvent) -> Result<(), TelemetryError> {
207 self.events.lock().unwrap().push(event);
208 Ok(())
209 }
210
211 async fn query(
212 &self,
213 filter: &TelemetryFilter,
214 ) -> Result<Vec<TelemetryEvent>, TelemetryError> {
215 let events = self.events.lock().unwrap();
216 Ok(events
217 .iter()
218 .rev()
219 .filter(|e| filter.matches(e))
220 .cloned()
221 .collect())
222 }
223 }
224
225 async fn exercise(sink: &dyn TelemetrySink) -> Vec<TelemetryEvent> {
228 sink.record(
229 TelemetryEvent::new(TelemetryKind::DidcommInbound)
230 .with_mediator("did:m:A")
231 .with_sender("did:peer:alice"),
232 )
233 .await
234 .unwrap();
235 sink.record(
236 TelemetryEvent::new(TelemetryKind::ServicesDidcommUpdate).with_mediator("did:m:B"),
237 )
238 .await
239 .unwrap();
240 sink.record(
241 TelemetryEvent::new(TelemetryKind::DidcommInbound)
242 .with_mediator("did:m:B")
243 .with_sender("did:peer:alice"),
244 )
245 .await
246 .unwrap();
247 sink.query(&TelemetryFilter::new().kind(TelemetryKind::DidcommInbound))
248 .await
249 .unwrap()
250 }
251
252 #[tokio::test]
253 async fn ring_buffer_and_vec_mutex_produce_same_observable_results() {
254 let ring = RingBufferTelemetry::new();
255 let stub = VecMutexSink::new();
256 let from_ring = exercise(&ring).await;
257 let from_stub = exercise(&stub).await;
258 assert_eq!(from_ring.len(), 2);
262 assert_eq!(from_stub.len(), 2);
263 for (a, b) in from_ring.iter().zip(from_stub.iter()) {
264 assert_eq!(a.kind, b.kind);
265 assert_eq!(a.mediator_did, b.mediator_did);
266 assert_eq!(a.sender_did, b.sender_did);
267 }
268 assert_eq!(from_ring[0].mediator_did.as_deref(), Some("did:m:B"));
270 assert_eq!(from_stub[0].mediator_did.as_deref(), Some("did:m:B"));
271 }
272
273 #[tokio::test]
274 async fn arc_dyn_dispatch_swap_at_runtime() {
275 let sinks: Vec<SharedTelemetrySink> = vec![
276 Arc::new(RingBufferTelemetry::new()),
277 Arc::new(VecMutexSink::new()),
278 ];
279 for sink in &sinks {
280 sink.record(TelemetryEvent::new(TelemetryKind::DidcommInbound))
281 .await
282 .unwrap();
283 let out = sink.query(&TelemetryFilter::new()).await.unwrap();
284 assert_eq!(out.len(), 1);
285 }
286 }
287}