panopticon_core/hooks/core/event_log.rs
1use crate::imports::*;
2use std::time::Instant;
3
4/// A single record captured by an [`EventLog`] hook.
5///
6/// Records one observed [`HookEvent`] with enough context to identify
7/// which step or iteration it came from and when it occurred relative to
8/// the start of the run.
9#[derive(Debug, Clone)]
10pub struct EventRecord {
11 /// The category of event observed.
12 pub kind: EventKind,
13 /// The step or return-block name, if the event originated from one.
14 pub step_name: Option<String>,
15 /// The iteration context as rendered by [`IterContext`]'s `Display`
16 /// impl, if the event fired inside an iteration.
17 pub iter_info: Option<String>,
18 /// How long after the [`EventLog`] was attached the event fired.
19 pub elapsed: std::time::Duration,
20}
21
22/// The category of a captured [`EventRecord`].
23///
24/// Mirrors the variants of [`HookEvent`] but without the borrowed
25/// references, so records can be stored and inspected after the
26/// pipeline has finished.
27#[derive(Debug, Clone, PartialEq)]
28pub enum EventKind {
29 /// A step is about to execute.
30 BeforeStep,
31 /// A step has just executed.
32 AfterStep,
33 /// An iteration body is about to start.
34 BeforeIteration,
35 /// An iteration body has just finished.
36 AfterIteration,
37 /// A return block is about to resolve.
38 BeforeReturns,
39 /// A return block has just resolved.
40 AfterReturns,
41 /// A guard's predicate evaluated to true.
42 GuardPassed,
43 /// A guard's predicate evaluated to false.
44 GuardFailed,
45 /// The pipeline finished successfully.
46 Complete,
47 /// The pipeline produced an error.
48 Error,
49}
50
51/// A built-in hook that captures every observed event into an in-memory
52/// log for post-hoc inspection.
53///
54/// Useful in tests that assert on the sequence of steps and iterations
55/// a pipeline produced. Attach the `EventLog` with
56/// `Pipeline::hook(event_log)` and retain a clone of the `log()` handle
57/// to read entries once the pipeline is complete.
58pub struct EventLog {
59 name: String,
60 log: Arc<Mutex<Vec<EventRecord>>>,
61}
62
63impl Default for EventLog {
64 fn default() -> Self {
65 EventLog::new()
66 }
67}
68
69impl EventLog {
70 /// Constructs a new event log with the default hook name.
71 pub fn new() -> Self {
72 EventLog {
73 name: "event_log".into(),
74 log: Arc::new(Mutex::new(Vec::new())),
75 }
76 }
77
78 /// Overrides the hook name used in [`HookEvent`] abort messages.
79 pub fn name(mut self, name: impl Into<String>) -> Self {
80 self.name = name.into();
81 self
82 }
83
84 /// Returns a cloned handle to the underlying log. Retain this before
85 /// moving the `EventLog` into [`Pipeline::hook`] so the records can
86 /// be inspected once the pipeline finishes.
87 pub fn log(&self) -> Arc<Mutex<Vec<EventRecord>>> {
88 Arc::clone(&self.log)
89 }
90}
91
92impl From<EventLog> for Hook {
93 fn from(event_log: EventLog) -> Hook {
94 let log = event_log.log;
95 let start = Instant::now();
96
97 Hook::observer(event_log.name, move |event, _store| {
98 let elapsed = start.elapsed();
99
100 let iter_info_from =
101 |ctx: &Option<&IterContext>| -> Option<String> { ctx.map(|c| c.to_string()) };
102
103 let (kind, step_name, iter_info) = match event {
104 HookEvent::BeforeStep {
105 step_name,
106 iter_context,
107 ..
108 } => (
109 EventKind::BeforeStep,
110 Some(step_name.to_string()),
111 iter_info_from(iter_context),
112 ),
113 HookEvent::AfterStep {
114 step_name,
115 iter_context,
116 ..
117 } => (
118 EventKind::AfterStep,
119 Some(step_name.to_string()),
120 iter_info_from(iter_context),
121 ),
122 HookEvent::BeforeIteration { iter_context } => (
123 EventKind::BeforeIteration,
124 None,
125 Some(iter_context.to_string()),
126 ),
127 HookEvent::AfterIteration { iter_context } => (
128 EventKind::AfterIteration,
129 None,
130 Some(iter_context.to_string()),
131 ),
132 HookEvent::BeforeReturns { return_name, .. } => (
133 EventKind::BeforeReturns,
134 Some(return_name.to_string()),
135 None,
136 ),
137 HookEvent::AfterReturns { return_name, .. } => {
138 (EventKind::AfterReturns, Some(return_name.to_string()), None)
139 }
140 HookEvent::GuardPassed { guard_name } => {
141 (EventKind::GuardPassed, Some(guard_name.to_string()), None)
142 }
143 HookEvent::GuardFailed { guard_name } => {
144 (EventKind::GuardFailed, Some(guard_name.to_string()), None)
145 }
146 HookEvent::Complete => (EventKind::Complete, None, None),
147 HookEvent::Error { .. } => (EventKind::Error, None, None),
148 #[allow(unreachable_patterns)]
149 _ => return,
150 };
151
152 let mut l = log.lock().unwrap();
153 l.push(EventRecord {
154 kind,
155 step_name,
156 iter_info,
157 elapsed,
158 });
159 })
160 }
161}