brush_shell/
events.rs

1//! Facilities for configuring event tracing in the brush shell.
2
3use std::{collections::HashSet, fmt::Display};
4
5use brush_core::Error;
6use tracing_subscriber::{
7    Layer, Registry, filter::Targets, layer::SubscriberExt, reload::Handle, util::SubscriberInitExt,
8};
9
10/// Type of event to trace.
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
12pub enum TraceEvent {
13    /// Traces parsing and evaluation of arithmetic expressions.
14    #[clap(name = "arithmetic")]
15    Arithmetic,
16    /// Traces command execution.
17    #[clap(name = "commands")]
18    Commands,
19    /// Traces command completion generation.
20    #[clap(name = "complete")]
21    Complete,
22    /// Traces word expansion.
23    #[clap(name = "expand")]
24    Expand,
25    /// Traces functions.
26    #[clap(name = "functions")]
27    Functions,
28    /// Traces input controls.
29    #[clap(name = "input")]
30    Input,
31    /// Traces job management.
32    #[clap(name = "jobs")]
33    Jobs,
34    /// Traces the process of parsing tokens into an abstract syntax tree.
35    #[clap(name = "parse")]
36    Parse,
37    /// Traces pattern matching.
38    #[clap(name = "pattern")]
39    Pattern,
40    /// Traces the process of tokenizing input text.
41    #[clap(name = "tokenize")]
42    Tokenize,
43    /// Traces usage of unimplemented functionality.
44    #[clap(name = "unimplemented", alias = "unimp")]
45    Unimplemented,
46}
47
48impl Display for TraceEvent {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::Arithmetic => write!(f, "arithmetic"),
52            Self::Commands => write!(f, "commands"),
53            Self::Complete => write!(f, "complete"),
54            Self::Expand => write!(f, "expand"),
55            Self::Functions => write!(f, "functions"),
56            Self::Input => write!(f, "input"),
57            Self::Jobs => write!(f, "jobs"),
58            Self::Parse => write!(f, "parse"),
59            Self::Pattern => write!(f, "pattern"),
60            Self::Tokenize => write!(f, "tokenize"),
61            Self::Unimplemented => write!(f, "unimplemented"),
62        }
63    }
64}
65
66#[derive(Default)]
67pub(crate) struct TraceEventConfig {
68    enabled_debug_events: HashSet<TraceEvent>,
69    disabled_events: HashSet<TraceEvent>,
70    handle: Option<Handle<Targets, Registry>>,
71}
72
73impl TraceEventConfig {
74    pub fn init(enabled_debug_events: &[TraceEvent], disabled_events: &[TraceEvent]) -> Self {
75        let enabled_debug_events: HashSet<TraceEvent> =
76            enabled_debug_events.iter().copied().collect();
77        let disabled_events: HashSet<TraceEvent> = disabled_events.iter().copied().collect();
78
79        let mut config = Self {
80            enabled_debug_events,
81            disabled_events,
82            ..Default::default()
83        };
84
85        let filter = config.compose_filter();
86
87        // Make the filter reloadable so that we can change the log level at runtime.
88        let (reload_filter, handle) = tracing_subscriber::reload::Layer::new(filter);
89
90        let layer = tracing_subscriber::fmt::layer()
91            .with_writer(std::io::stderr)
92            .without_time()
93            .with_target(false)
94            .with_filter(reload_filter);
95
96        if tracing_subscriber::registry()
97            .with(layer)
98            .try_init()
99            .is_ok()
100        {
101            config.handle = Some(handle);
102        } else {
103            // Something went wrong; proceed on anyway but complain audibly.
104            eprintln!("warning: failed to initialize tracing.");
105        }
106
107        config
108    }
109
110    fn compose_filter(&self) -> tracing_subscriber::filter::Targets {
111        let mut filter = tracing_subscriber::filter::Targets::new()
112            .with_default(tracing_subscriber::filter::LevelFilter::INFO);
113
114        for event in &self.enabled_debug_events {
115            let targets = Self::event_to_tracing_targets(event);
116            filter = filter.with_targets(
117                targets
118                    .into_iter()
119                    .map(|target| (target, tracing::Level::DEBUG)),
120            );
121        }
122
123        for event in &self.disabled_events {
124            let targets = Self::event_to_tracing_targets(event);
125            filter = filter.with_targets(
126                targets
127                    .into_iter()
128                    .map(|target| (target, tracing::level_filters::LevelFilter::OFF)),
129            );
130        }
131
132        filter
133    }
134
135    fn event_to_tracing_targets(event: &TraceEvent) -> Vec<&str> {
136        match event {
137            TraceEvent::Arithmetic => vec!["arithmetic"],
138            TraceEvent::Commands => vec!["commands"],
139            TraceEvent::Complete => vec!["completion"],
140            TraceEvent::Expand => vec!["expansion"],
141            TraceEvent::Functions => vec!["functions"],
142            TraceEvent::Input => vec!["input"],
143            TraceEvent::Jobs => vec!["jobs"],
144            TraceEvent::Parse => vec!["parse"],
145            TraceEvent::Pattern => vec!["pattern"],
146            TraceEvent::Tokenize => vec!["tokenize"],
147            TraceEvent::Unimplemented => vec!["unimplemented"],
148        }
149    }
150
151    pub const fn get_enabled_events(&self) -> &HashSet<TraceEvent> {
152        &self.enabled_debug_events
153    }
154
155    pub fn enable(&mut self, event: TraceEvent) -> Result<(), Error> {
156        // Don't bother to reload config if nothing has changed.
157        if !self.enabled_debug_events.insert(event) {
158            return Ok(());
159        }
160
161        self.reload_filter()
162    }
163
164    pub fn disable(&mut self, event: TraceEvent) -> Result<(), Error> {
165        // Don't bother to reload config if nothing has changed.
166        if !self.enabled_debug_events.remove(&event) {
167            return Ok(());
168        }
169
170        self.reload_filter()
171    }
172
173    fn reload_filter(&self) -> Result<(), Error> {
174        if let Some(handle) = &self.handle {
175            if handle.reload(self.compose_filter()).is_ok() {
176                Ok(())
177            } else {
178                Err(brush_core::ErrorKind::Unimplemented("failed to enable tracing events").into())
179            }
180        } else {
181            Err(brush_core::ErrorKind::Unimplemented("tracing not initialized").into())
182        }
183    }
184}