Skip to main content

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}