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}