Skip to main content

panopticon_core/hooks/core/
mod.rs

1mod event_log;
2mod profiler;
3mod step_filter;
4mod store_validator;
5mod timeout;
6
7pub use event_log::{EventKind, EventLog, EventRecord};
8pub use profiler::Profiler;
9pub use step_filter::StepFilter;
10pub use store_validator::StoreValidator;
11pub use timeout::Timeout;
12
13use crate::imports::*;
14use std::io::{self, Write};
15
16/// A built-in observer hook that writes a human-readable trace of every
17/// pipeline event to a writer (stderr by default).
18///
19/// Intended for development and debugging: the output is not a stable
20/// format and may change between versions. Configure via the chainable
21/// [`name`](Self::name), [`prefix`](Self::prefix),
22/// [`indent`](Self::indent), and [`writer`](Self::writer) methods, then
23/// pass to [`Pipeline::hook`].
24#[derive(Clone)]
25pub struct Logger {
26    name: String,
27    prefix: String,
28    indent: String,
29    writer: Arc<Mutex<Box<dyn Write + Send>>>,
30}
31
32impl Default for Logger {
33    fn default() -> Self {
34        Logger::new()
35    }
36}
37
38impl Logger {
39    /// Constructs a logger with the default name, `[hook]` prefix,
40    /// two-space indent, and stderr as the writer.
41    pub fn new() -> Self {
42        Logger {
43            name: "logger".into(),
44            prefix: "[hook]".into(),
45            indent: "  ".into(),
46            writer: Arc::new(Mutex::new(Box::new(io::stderr()))),
47        }
48    }
49
50    /// Overrides the hook name used in [`HookEvent`] abort messages.
51    pub fn name(mut self, name: impl Into<String>) -> Self {
52        self.name = name.into();
53        self
54    }
55
56    /// Overrides the per-line prefix (default `[hook]`).
57    pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
58        self.prefix = prefix.into();
59        self
60    }
61
62    /// Sets the leading indent width in spaces.
63    pub fn indent(mut self, spaces: usize) -> Self {
64        self.indent = " ".repeat(spaces);
65        self
66    }
67
68    /// Redirects output to a user-supplied writer instead of stderr.
69    pub fn writer(mut self, writer: impl Write + Send + 'static) -> Self {
70        self.writer = Arc::new(Mutex::new(Box::new(writer)));
71        self
72    }
73}
74
75impl From<Logger> for Hook {
76    fn from(logger: Logger) -> Hook {
77        let prefix: Arc<str> = Arc::from(logger.prefix.as_str());
78        let indent: Arc<str> = Arc::from(logger.indent.as_str());
79        let writer = logger.writer;
80
81        Hook::observer(logger.name, move |event, _store| {
82            let prefix = &prefix;
83            let indent = &indent;
84            let mut w = writer.lock().unwrap();
85
86            // Extra indentation per nesting depth when inside an iteration
87            let depth_indent = |ctx: &Option<&IterContext>| -> String {
88                match ctx {
89                    Some(c) => "  ".repeat(c.depth + 1),
90                    None => String::new(),
91                }
92            };
93            let iter_tag = |ctx: &Option<&IterContext>| -> String {
94                match ctx {
95                    Some(c) => format!(" [{}]", c),
96                    None => String::new(),
97                }
98            };
99
100            #[allow(unreachable_patterns)]
101            match event {
102                HookEvent::BeforeIteration { iter_context } => {
103                    let di = "  ".repeat(iter_context.depth);
104                    let _ = writeln!(w, "{}{}{} >> iter '{}'", indent, di, prefix, iter_context,);
105                }
106                HookEvent::AfterIteration { iter_context } => {
107                    let di = "  ".repeat(iter_context.depth);
108                    let _ = writeln!(w, "{}{}{} << iter '{}'", indent, di, prefix, iter_context,);
109                }
110                HookEvent::BeforeStep {
111                    step_name,
112                    metadata,
113                    iter_context,
114                    ..
115                } => {
116                    let di = depth_indent(iter_context);
117                    let tag = iter_tag(iter_context);
118                    let _ = writeln!(
119                        w,
120                        "{}{}{} -> BeforeStep '{}' (op: {}){}",
121                        indent, di, prefix, step_name, metadata.name, tag,
122                    );
123                }
124                HookEvent::AfterStep {
125                    step_name,
126                    operation_outputs,
127                    global_outputs,
128                    iter_context,
129                    ..
130                } => {
131                    let di = depth_indent(iter_context);
132                    let tag = iter_tag(iter_context);
133                    let op_keys: Vec<&String> = operation_outputs.keys().collect();
134                    let global_keys: Vec<&String> = global_outputs.keys().collect();
135                    let _ = writeln!(
136                        w,
137                        "{}{}{} <- AfterStep '{}' | op outputs: {:?}, global outputs: {:?}{}",
138                        indent, di, prefix, step_name, op_keys, global_keys, tag,
139                    );
140                }
141                HookEvent::BeforeReturns { return_name, .. } => {
142                    let _ = writeln!(w, "{}{} -> BeforeReturns '{}'", indent, prefix, return_name,);
143                }
144                HookEvent::AfterReturns {
145                    return_name,
146                    outputs,
147                    ..
148                } => {
149                    let keys: Vec<&String> = outputs.keys().collect();
150                    let _ = writeln!(
151                        w,
152                        "{}{} <- AfterReturns '{}' | resolved: {:?}",
153                        indent, prefix, return_name, keys,
154                    );
155                }
156                HookEvent::GuardPassed { guard_name } => {
157                    let _ = writeln!(w, "{}{} >> guard '{}' passed", indent, prefix, guard_name,);
158                }
159                HookEvent::GuardFailed { guard_name } => {
160                    let _ = writeln!(w, "{}{} xx guard '{}' failed", indent, prefix, guard_name,);
161                }
162                HookEvent::Complete => {
163                    let _ = writeln!(w, "{}{} == Complete", indent, prefix);
164                }
165                HookEvent::Error { error } => {
166                    let _ = writeln!(w, "{}{} !! Error: {}", indent, prefix, error);
167                }
168                _ => {}
169            }
170        })
171    }
172}