tracing_egui/
panel.rs

1use crate::archive::LOG_ENTRIES;
2use tracing::{level_filters::STATIC_MAX_LEVEL, Level};
3
4#[derive(Debug)]
5pub struct LogPanel;
6
7#[derive(Debug, Clone)]
8struct LogPanelState {
9    trace: bool,
10    debug: bool,
11    info: bool,
12    warn: bool,
13    error: bool,
14}
15
16impl Default for LogPanelState {
17    fn default() -> Self {
18        Self {
19            trace: true,
20            debug: true,
21            info: true,
22            warn: true,
23            error: true,
24        }
25    }
26}
27
28impl egui::Widget for LogPanel {
29    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
30        let id = ui.make_persistent_id("tracing-egui::LogPanel");
31        let mut state = ui
32            .memory()
33            .id_data
34            .get_or_default::<LogPanelState>(id)
35            .clone();
36
37        let mut response = ui
38            .centered_and_justified(|ui| {
39                ui.horizontal(|ui| {
40                    if Level::TRACE < STATIC_MAX_LEVEL {
41                        ui.checkbox(&mut state.trace, "trace");
42                    }
43                    if Level::DEBUG < STATIC_MAX_LEVEL {
44                        ui.checkbox(&mut state.debug, "debug");
45                    }
46                    if Level::INFO < STATIC_MAX_LEVEL {
47                        ui.checkbox(&mut state.info, "info");
48                    }
49                    if Level::WARN < STATIC_MAX_LEVEL {
50                        ui.checkbox(&mut state.warn, "warn");
51                    }
52                    if Level::ERROR < STATIC_MAX_LEVEL {
53                        ui.checkbox(&mut state.error, "error");
54                    }
55                });
56            })
57            .response;
58
59        let log_entries = LOG_ENTRIES.lock();
60        for (log_ix, log) in log_entries.iter().enumerate().rev() {
61            let filtered_out = match *log.meta.level() {
62                Level::TRACE => !state.trace,
63                Level::DEBUG => !state.debug,
64                Level::INFO => !state.info,
65                Level::WARN => !state.warn,
66                Level::ERROR => !state.error,
67            };
68            if filtered_out {
69                continue;
70            }
71
72            let log_id = id.with(log_ix);
73            let r = match log.fields.get("message") {
74                Some(message) => egui::CollapsingHeader::new(format_args!(
75                    "[{}] [{}] {}",
76                    log.timestamp.format("%H:%M:%S%.3f"),
77                    log.meta.level(),
78                    message,
79                )),
80                None => egui::CollapsingHeader::new(format_args!(
81                    "[{}] [{}]",
82                    log.timestamp.format("%H:%M:%S%.3f"),
83                    log.meta.level(),
84                )),
85            }
86            .id_source(log_id)
87            .show(ui, |ui| {
88                let r = egui::CollapsingHeader::new(format_args!(
89                    "{} {}",
90                    log.meta.target(),
91                    log.meta.name(),
92                ))
93                .id_source(log_id.with(0usize))
94                .text_style(egui::TextStyle::Monospace)
95                .show(ui, |ui| {
96                    log.show_fields(ui);
97                });
98                response |= r.header_response;
99                if let Some(r) = r.body_response {
100                    response |= r;
101                }
102
103                for (span_ix, span) in
104                    std::iter::successors(log.span.as_deref(), |span| span.parent.as_deref())
105                        .enumerate()
106                {
107                    let span_id = log_id.with(span_ix + 1);
108                    let r = egui::CollapsingHeader::new(format_args!(
109                        "{}::{}",
110                        span.meta.map_or("{unknown}", |meta| meta.target()),
111                        span.meta.map_or("{unknown}", |meta| meta.name()),
112                    ))
113                    .id_source(span_id)
114                    .text_style(egui::TextStyle::Monospace)
115                    .show(ui, |ui| {
116                        span.show_fields(ui);
117                    });
118                    response |= r.header_response;
119                    if let Some(r) = r.body_response {
120                        response |= r;
121                    }
122                }
123            });
124            response |= r.header_response;
125            if let Some(r) = r.body_response {
126                response |= r;
127            }
128        }
129
130        ui.memory().id_data.insert(id, state);
131        response
132    }
133}