moonpool_sim/observability/
layer.rs1use std::cell::Cell;
26use std::collections::{BTreeMap, HashMap};
27use std::sync::Arc;
28
29use parking_lot::Mutex;
30use tracing::Subscriber;
31use tracing::field::{Field, Visit};
32use tracing_subscriber::Layer;
33use tracing_subscriber::layer::{Context, SubscriberExt};
34use tracing_subscriber::registry::LookupSpan;
35
36use crate::chaos::{SIM_FAULT_EVENT_NAME, SimFaultEvent};
37
38use super::event::{FieldValue, TraceEvent};
39use super::invariant::Invariant;
40use super::query::TraceQuery;
41
42struct EventStore {
44 seq_counter: u64,
46 by_name: HashMap<String, Vec<TraceEvent>>,
48 last_sim_time_ms: u64,
51}
52
53impl EventStore {
54 fn new() -> Self {
55 Self {
56 seq_counter: 0,
57 by_name: HashMap::new(),
58 last_sim_time_ms: 0,
59 }
60 }
61
62 fn push(
63 &mut self,
64 time_ms: u64,
65 source: String,
66 target: String,
67 level: tracing::Level,
68 name: String,
69 fields: BTreeMap<String, FieldValue>,
70 ) {
71 let seq = self.seq_counter;
72 self.seq_counter += 1;
73 let event = TraceEvent {
74 seq,
75 time_ms,
76 source,
77 target,
78 level,
79 name: name.clone(),
80 fields,
81 };
82 self.by_name.entry(name).or_default().push(event);
83 }
84}
85
86pub struct SimulationLayer {
89 events: Arc<Mutex<EventStore>>,
90 invariants: Arc<Mutex<Vec<Box<dyn Invariant + Send>>>>,
91}
92
93impl SimulationLayer {
94 #[must_use]
96 pub fn new() -> Self {
97 Self {
98 events: Arc::new(Mutex::new(EventStore::new())),
99 invariants: Arc::new(Mutex::new(Vec::new())),
100 }
101 }
102
103 #[must_use]
105 pub fn handle(&self) -> SimulationLayerHandle {
106 SimulationLayerHandle {
107 events: self.events.clone(),
108 invariants: self.invariants.clone(),
109 }
110 }
111
112 #[must_use]
115 pub fn install(self) -> (SimulationLayerHandle, InstallGuard) {
116 let handle = self.handle();
117 let interest_anchor = tracing::Dispatch::new(tracing_subscriber::registry());
128 let subscriber = tracing_subscriber::registry().with(self);
129 let guard = tracing::subscriber::set_default(subscriber);
130 tracing::callsite::rebuild_interest_cache();
134 (
135 handle,
136 InstallGuard {
137 _guard: guard,
138 _interest_anchor: interest_anchor,
139 },
140 )
141 }
142}
143
144impl Default for SimulationLayer {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150pub struct InstallGuard {
153 _guard: tracing::subscriber::DefaultGuard,
154 _interest_anchor: tracing::Dispatch,
158}
159
160#[derive(Clone)]
162pub struct SimulationLayerHandle {
163 events: Arc<Mutex<EventStore>>,
164 invariants: Arc<Mutex<Vec<Box<dyn Invariant + Send>>>>,
165}
166
167impl SimulationLayerHandle {
168 pub fn register(&self, inv: Box<dyn Invariant + Send>) {
170 self.invariants.lock().push(inv);
171 }
172
173 pub fn reset_for_seed(&self) {
178 {
179 let mut store = self.events.lock();
180 store.by_name.clear();
181 store.seq_counter = 0;
182 store.last_sim_time_ms = 0;
183 }
184 let mut invs = self.invariants.lock();
185 for inv in invs.iter_mut() {
186 inv.reset();
187 }
188 }
189
190 #[must_use]
195 pub fn current_sim_time_ms(&self) -> u64 {
196 self.events.lock().last_sim_time_ms
197 }
198
199 pub fn set_sim_time_ms(&self, ms: u64) {
202 self.events.lock().last_sim_time_ms = ms;
203 }
204
205 pub fn record_sim_fault(&self, time_ms: u64, fault: &SimFaultEvent) {
212 let mut fields = fault.to_fields();
213 fields.insert("kind".to_owned(), FieldValue::Str(fault.kind().to_owned()));
214 self.events.lock().push(
215 time_ms,
216 "sim".to_owned(),
217 "moonpool_sim::fault".to_owned(),
218 tracing::Level::INFO,
219 SIM_FAULT_EVENT_NAME.to_owned(),
220 fields,
221 );
222 }
223
224 pub fn run_invariants(&self) {
227 let sim_time_ms = self.current_sim_time_ms();
228 let invariants = self.invariants.lock();
229 for inv in invariants.iter() {
230 inv.observe(self, sim_time_ms);
231 }
232 }
233}
234
235impl TraceQuery for SimulationLayerHandle {
236 fn len(&self, name: &str) -> usize {
237 self.events
238 .lock()
239 .by_name
240 .get(name)
241 .map_or(0, std::vec::Vec::len)
242 }
243
244 fn since(&self, name: &str, cursor: &Cell<usize>) -> Vec<TraceEvent> {
245 let store = self.events.lock();
246 let Some(entries) = store.by_name.get(name) else {
247 return Vec::new();
248 };
249 let len = entries.len();
250 let from = cursor.get();
251 if from >= len {
252 return Vec::new();
253 }
254 let result: Vec<TraceEvent> = entries[from..].to_vec();
255 cursor.set(len);
256 result
257 }
258
259 fn snapshot(&self, name: &str) -> Vec<TraceEvent> {
260 self.events
261 .lock()
262 .by_name
263 .get(name)
264 .cloned()
265 .unwrap_or_default()
266 }
267}
268
269struct SourceIp(String);
272
273struct SpanIpVisitor {
275 ip: Option<String>,
276}
277
278impl Visit for SpanIpVisitor {
279 fn record_str(&mut self, field: &Field, value: &str) {
280 if field.name() == "ip" {
281 self.ip = Some(value.to_owned());
282 }
283 }
284
285 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
286 if field.name() == "ip" {
287 self.ip = Some(format!("{value:?}"));
288 }
289 }
290}
291
292struct EventVisitor {
295 name: Option<String>,
296 fields: BTreeMap<String, FieldValue>,
297}
298
299impl EventVisitor {
300 fn new() -> Self {
301 Self {
302 name: None,
303 fields: BTreeMap::new(),
304 }
305 }
306}
307
308impl Visit for EventVisitor {
309 fn record_bool(&mut self, field: &Field, value: bool) {
310 self.fields
311 .insert(field.name().to_owned(), FieldValue::Bool(value));
312 }
313
314 fn record_i64(&mut self, field: &Field, value: i64) {
315 self.fields
316 .insert(field.name().to_owned(), FieldValue::I64(value));
317 }
318
319 fn record_u64(&mut self, field: &Field, value: u64) {
320 self.fields
321 .insert(field.name().to_owned(), FieldValue::U64(value));
322 }
323
324 fn record_f64(&mut self, field: &Field, value: f64) {
325 self.fields
326 .insert(field.name().to_owned(), FieldValue::F64(value));
327 }
328
329 fn record_str(&mut self, field: &Field, value: &str) {
330 self.fields
331 .insert(field.name().to_owned(), FieldValue::Str(value.to_owned()));
332 }
333
334 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
335 let formatted = format!("{value:?}");
338 if field.name() == "message" {
339 self.name = Some(formatted);
340 } else {
341 self.fields
342 .insert(field.name().to_owned(), FieldValue::Str(formatted));
343 }
344 }
345}
346
347fn nearest_source<S>(ctx: &Context<'_, S>, event: &tracing::Event<'_>) -> Option<String>
350where
351 S: Subscriber + for<'a> LookupSpan<'a>,
352{
353 let scope = ctx.event_scope(event)?;
354 for span in scope {
355 if let Some(ip) = span.extensions().get::<SourceIp>() {
356 return Some(ip.0.clone());
357 }
358 }
359 None
360}
361
362impl<S> Layer<S> for SimulationLayer
363where
364 S: Subscriber + for<'a> LookupSpan<'a>,
365{
366 fn register_callsite(
372 &self,
373 _metadata: &'static tracing::Metadata<'static>,
374 ) -> tracing::subscriber::Interest {
375 tracing::subscriber::Interest::sometimes()
376 }
377
378 fn on_new_span(
379 &self,
380 attrs: &tracing::span::Attributes<'_>,
381 id: &tracing::span::Id,
382 ctx: Context<'_, S>,
383 ) {
384 let mut visitor = SpanIpVisitor { ip: None };
385 attrs.record(&mut visitor);
386 if let Some(ip) = visitor.ip
387 && let Some(span) = ctx.span(id)
388 {
389 span.extensions_mut().insert(SourceIp(ip));
390 }
391 }
392
393 fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
394 if *event.metadata().level() > tracing::Level::INFO {
397 return;
398 }
399 let Some(source) = nearest_source(&ctx, event) else {
401 return;
402 };
403 let mut visitor = EventVisitor::new();
405 event.record(&mut visitor);
406 let Some(name) = visitor.name.filter(|n| !n.is_empty()) else {
407 return;
408 };
409
410 let mut store = self.events.lock();
411 let time_ms = store.last_sim_time_ms;
412 store.push(
413 time_ms,
414 source,
415 event.metadata().target().to_owned(),
416 *event.metadata().level(),
417 name,
418 visitor.fields,
419 );
420 }
421}