log_terminal/
log.rs

1use {
2    crate::{
3        draw::{DrawEvent, MAX_LINES, draw_thread},
4        inputs::inputs_thread,
5    },
6    std::{
7        collections::VecDeque,
8        io::{self, Write},
9        marker::PhantomData,
10        sync::{Arc, Mutex, Once, mpsc},
11        thread,
12    },
13    tracing::Subscriber,
14    tracing_subscriber::{
15        Layer, Registry,
16        fmt::{
17            Layer as FmtLayer, MakeWriter,
18            format::{DefaultFields, Format},
19        },
20        layer::{Layered, SubscriberExt},
21    },
22};
23
24static TRACING: Once = Once::new();
25
26const NAME_NOT_FOUND: &str = "undefined";
27
28pub struct LogTerminal<N, E, V, S> {
29    rl: RedirectLayer<V, S>,
30    fmt_layer: FmtLayer<Layered<RedirectLayer<V, S>, Registry>, N, E, ChannelWriter>,
31}
32
33impl<V, S> LogTerminal<DefaultFields, Format, V, S>
34where
35    S: PartialEq<String>,
36    V: AsRef<[S]>,
37{
38    pub fn new(split_by: SplitBy<V, S>) -> LogTerminal<DefaultFields, Format, V, S> {
39        let (rl, cw) = RedirectLayer::new(split_by);
40        let fmt_layer = FmtLayer::new().with_writer(cw);
41        LogTerminal { rl, fmt_layer }
42    }
43}
44
45impl<N, E, V, S> LogTerminal<N, E, V, S>
46where
47    N: Send + Sync + 'static,
48    E: Send + Sync + 'static,
49    S: PartialEq<String>,
50    V: AsRef<[S]>,
51    RedirectLayer<V, S>: Send + Sync + 'static,
52    FmtLayer<Layered<RedirectLayer<V, S>, Registry>, N, E, ChannelWriter>:
53        Layer<Layered<RedirectLayer<V, S>, Registry>>,
54{
55    pub fn customize_fmt_layer<N1, E1>(
56        self,
57        closure: impl FnOnce(
58            FmtLayer<Layered<RedirectLayer<V, S>, Registry>, N, E, ChannelWriter>,
59        )
60            -> FmtLayer<Layered<RedirectLayer<V, S>, Registry>, N1, E1, ChannelWriter>,
61    ) -> LogTerminal<N1, E1, V, S> {
62        LogTerminal {
63            rl: self.rl,
64            fmt_layer: closure(self.fmt_layer),
65        }
66    }
67
68    pub fn with_max_level(mut self, level: tracing::Level) -> LogTerminal<N, E, V, S> {
69        self.rl.max_level = level;
70        self
71    }
72
73    pub fn with_max_lines(self, lines: usize) -> LogTerminal<N, E, V, S> {
74        *MAX_LINES.lock().unwrap() = lines;
75        self
76    }
77
78    pub fn finish(self) {
79        TRACING.call_once(|| {
80            let subscriber = tracing_subscriber::registry()
81                .with(self.rl)
82                .with(self.fmt_layer);
83
84            tracing::subscriber::set_global_default(subscriber)
85                .expect("global subscriber already set");
86        });
87    }
88}
89
90pub enum SplitBy<V, S> {
91    /// Tabs are splitted by the `target`
92    Target(SplitFilter<V, S>),
93    /// Tabs are splitted by the `target` prefix
94    TargetPrefix(SplitFilter<V, S>),
95    /// Tabs are splitted by the `span` prefix
96    SpanPrefix(SplitFilter<V, S>),
97}
98
99#[non_exhaustive]
100pub enum SplitFilter<V, S> {
101    /// Based on [`SplitBy`], show only the tabs that are in the whitelist
102    WhiteList(V, PhantomData<S>),
103    /// Based on [`SplitBy`], show only the tabs that are not in the blacklist
104    BlackList(V, PhantomData<S>),
105    /// Show all the tabs
106    None,
107}
108
109impl<V, S> SplitFilter<V, S>
110where
111    S: PartialEq<String>,
112    V: AsRef<[S]>,
113{
114    /// Based on [`SplitBy`], show only the tabs that are in the whitelist
115    pub fn whitelist(items: V) -> Self {
116        Self::WhiteList(items, PhantomData)
117    }
118
119    /// Based on [`SplitBy`], show only the tabs that are not in the blacklist
120    pub fn blacklist(items: V) -> Self {
121        Self::BlackList(items, PhantomData)
122    }
123}
124
125impl SplitFilter<Vec<String>, String> {
126    /// Show all the tabs
127    pub fn none() -> Self {
128        Self::None
129    }
130}
131
132impl<V, S> SplitFilter<V, S>
133where
134    S: PartialEq<String>,
135    V: AsRef<[S]>,
136{
137    pub fn filter<'a>(&self, target: String) -> Option<String> {
138        match self {
139            SplitFilter::WhiteList(items, _) => {
140                if items.as_ref().iter().any(|item| item == &target) {
141                    Some(target)
142                } else {
143                    None
144                }
145            },
146            SplitFilter::BlackList(items, _) => {
147                if items.as_ref().iter().any(|item| item == &target) {
148                    None
149                } else {
150                    Some(target)
151                }
152            },
153            SplitFilter::None => Some(target),
154        }
155    }
156}
157
158pub struct RedirectLayer<V, S> {
159    max_level: tracing::Level,
160    split_by: SplitBy<V, S>,
161    events: Arc<Mutex<VecDeque<Option<String>>>>,
162}
163
164impl<V, S> RedirectLayer<V, S>
165where
166    S: PartialEq<String>,
167    V: AsRef<[S]>,
168{
169    pub fn new(split_by: SplitBy<V, S>) -> (Self, ChannelWriter) {
170        let (tx, rx) = mpsc::channel();
171
172        let events: Arc<Mutex<VecDeque<Option<String>>>> = Default::default();
173
174        let _events = events.clone();
175        let _tx = tx.clone();
176
177        thread::spawn(move || inputs_thread(_tx));
178        thread::spawn(move || draw_thread(_events, rx));
179
180        (
181            Self {
182                max_level: tracing::Level::DEBUG,
183                events,
184                split_by,
185            },
186            ChannelWriter { tx },
187        )
188    }
189
190    fn filter<Sub>(
191        &self,
192        event: &tracing::Event<'_>,
193        _ctx: tracing_subscriber::layer::Context<'_, Sub>,
194    ) -> Option<String>
195    where
196        Sub: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
197    {
198        match &self.split_by {
199            SplitBy::Target(filter) => filter.filter(event.metadata().target().to_string()),
200            SplitBy::TargetPrefix(filter) => {
201                let full_target = event.metadata().target();
202
203                let target = full_target
204                    .split("::")
205                    .next()
206                    .unwrap_or(full_target)
207                    .to_string();
208                filter.filter(target)
209            },
210            SplitBy::SpanPrefix(filter) => {
211                let str = if let Some(scope) = _ctx.event_scope(event) {
212                    if let Some(span) = scope.from_root().next() {
213                        span.name().to_string()
214                    } else {
215                        NAME_NOT_FOUND.to_string()
216                    }
217                } else {
218                    NAME_NOT_FOUND.to_string()
219                };
220                filter.filter(str)
221            },
222        }
223    }
224}
225
226impl<S, V, Sub> Layer<Sub> for RedirectLayer<V, S>
227where
228    Self: 'static,
229    S: PartialEq<String>,
230    V: AsRef<[S]>,
231    Sub: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
232{
233    fn enabled(
234        &self,
235        metadata: &tracing::Metadata<'_>,
236        _ctx: tracing_subscriber::layer::Context<'_, Sub>,
237    ) -> bool {
238        metadata.level() <= &self.max_level
239    }
240
241    fn on_event(
242        &self,
243        event: &tracing::Event<'_>,
244        _ctx: tracing_subscriber::layer::Context<'_, Sub>,
245    ) {
246        let name = self.filter(event, _ctx);
247
248        self.events.lock().unwrap().push_back(name);
249    }
250}
251
252pub struct ChannelWriter {
253    tx: mpsc::Sender<DrawEvent>,
254}
255
256impl<'a> MakeWriter<'a> for ChannelWriter {
257    type Writer = &'a Self;
258
259    fn make_writer(&'a self) -> Self::Writer {
260        self
261    }
262}
263
264impl<'a> Write for &'a ChannelWriter {
265    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
266        self.tx.send(DrawEvent::Trace(buf.to_vec())).unwrap();
267        Ok(buf.len())
268    }
269
270    fn flush(&mut self) -> io::Result<()> {
271        Ok(())
272    }
273}