1use crate::{CircularBuffer, LevelConfig, TuiLoggerFile};
2use chrono::{DateTime, Local};
3use log::{Level, LevelFilter, Log, Metadata, Record};
4use parking_lot::Mutex;
5use std::collections::HashMap;
6use std::io::Write;
7use std::mem;
8use std::thread;
9
10#[derive(Debug, Clone, Copy, PartialEq, Hash)]
13pub enum TuiLoggerLevelOutput {
14 Abbreviated,
15 Long,
16}
17pub struct HotSelect {
19 pub hashtable: HashMap<u64, LevelFilter>,
20 pub default: LevelFilter,
21}
22pub struct HotLog {
23 pub events: CircularBuffer<ExtLogRecord>,
24 pub mover_thread: Option<thread::JoinHandle<()>>,
25}
26pub struct ExtLogRecord {
27 pub timestamp: DateTime<Local>,
28 pub level: Level,
29 pub target: String,
30 pub file: String,
31 pub line: u32,
32 pub msg: String,
33}
34pub struct TuiLoggerInner {
35 pub hot_depth: usize,
36 pub events: CircularBuffer<ExtLogRecord>,
37 pub dump: Option<TuiLoggerFile>,
38 pub total_events: usize,
39 pub default: LevelFilter,
40 pub targets: LevelConfig,
41}
42pub struct TuiLogger {
43 pub hot_select: Mutex<HotSelect>,
44 pub hot_log: Mutex<HotLog>,
45 pub inner: Mutex<TuiLoggerInner>,
46}
47impl TuiLogger {
48 pub fn move_events(&self) {
49 if self.hot_log.lock().events.total_elements() == 0 {
51 return;
52 }
53 let mut received_events = {
55 let hot_depth = self.inner.lock().hot_depth;
56 let new_circular = CircularBuffer::new(hot_depth);
57 let mut hl = self.hot_log.lock();
58 mem::replace(&mut hl.events, new_circular)
59 };
60 let mut tli = self.inner.lock();
61 let total = received_events.total_elements();
62 let elements = received_events.len();
63 tli.total_events += total;
64 let mut consumed = received_events.take();
65 let mut reversed = Vec::with_capacity(consumed.len() + 1);
66 while let Some(log_entry) = consumed.pop() {
67 reversed.push(log_entry);
68 }
69 if total > elements {
70 let new_log_entry = ExtLogRecord {
72 timestamp: reversed[reversed.len() - 1].timestamp,
73 level: Level::Warn,
74 target: "TuiLogger".to_string(),
75 file: "?".to_string(),
76 line: 0,
77 msg: format!(
78 "There have been {} events lost, {} recorded out of {}",
79 total - elements,
80 elements,
81 total
82 ),
83 };
84 reversed.push(new_log_entry);
85 }
86 let default_level = tli.default;
87 while let Some(log_entry) = reversed.pop() {
88 if tli.targets.get(&log_entry.target).is_none() {
89 tli.targets.set(&log_entry.target, default_level);
90 }
91 if let Some(ref mut file_options) = tli.dump {
92 let mut output = String::new();
93 let (lev_long, lev_abbr, with_loc) = match log_entry.level {
94 log::Level::Error => ("ERROR", "E", true),
95 log::Level::Warn => ("WARN ", "W", true),
96 log::Level::Info => ("INFO ", "I", false),
97 log::Level::Debug => ("DEBUG", "D", true),
98 log::Level::Trace => ("TRACE", "T", true),
99 };
100 if let Some(fmt) = file_options.timestamp_fmt.as_ref() {
101 output.push_str(&format!("{}", log_entry.timestamp.format(fmt)));
102 output.push(file_options.format_separator);
103 }
104 match file_options.format_output_level {
105 None => {}
106 Some(TuiLoggerLevelOutput::Abbreviated) => {
107 output.push_str(lev_abbr);
108 output.push(file_options.format_separator);
109 }
110 Some(TuiLoggerLevelOutput::Long) => {
111 output.push_str(lev_long);
112 output.push(file_options.format_separator);
113 }
114 }
115 if file_options.format_output_target {
116 output.push_str(&log_entry.target);
117 output.push(file_options.format_separator);
118 }
119 if with_loc {
120 if file_options.format_output_file {
121 output.push_str(&log_entry.file);
122 output.push(file_options.format_separator);
123 }
124 if file_options.format_output_line {
125 output.push_str(&format!("{}", log_entry.line));
126 output.push(file_options.format_separator);
127 }
128 }
129 output.push_str(&log_entry.msg);
130 if let Err(_e) = writeln!(file_options.dump, "{}", output) {
131 }
133 }
134 tli.events.push(log_entry);
135 }
136 }
137}
138lazy_static! {
139 pub static ref TUI_LOGGER: TuiLogger = {
140 let hs = HotSelect {
141 hashtable: HashMap::with_capacity(1000),
142 default: LevelFilter::Info,
143 };
144 let hl = HotLog {
145 events: CircularBuffer::new(1000),
146 mover_thread: None,
147 };
148 let tli = TuiLoggerInner {
149 hot_depth: 1000,
150 events: CircularBuffer::new(10000),
151 total_events: 0,
152 dump: None,
153 default: LevelFilter::Info,
154 targets: LevelConfig::new(),
155 };
156 TuiLogger {
157 hot_select: Mutex::new(hs),
158 hot_log: Mutex::new(hl),
159 inner: Mutex::new(tli),
160 }
161 };
162}
163
164impl Log for TuiLogger {
165 fn enabled(&self, metadata: &Metadata) -> bool {
166 let h = fxhash::hash64(metadata.target());
167 let hs = self.hot_select.lock();
168 if let Some(&levelfilter) = hs.hashtable.get(&h) {
169 metadata.level() <= levelfilter
170 } else {
171 metadata.level() <= hs.default
172 }
173 }
174
175 fn log(&self, record: &Record) {
176 if self.enabled(record.metadata()) {
177 self.raw_log(record)
178 }
179 }
180
181 fn flush(&self) {}
182}
183
184impl TuiLogger {
185 pub fn raw_log(&self, record: &Record) {
186 let log_entry = ExtLogRecord {
187 timestamp: chrono::Local::now(),
188 level: record.level(),
189 target: record.target().to_string(),
190 file: record.file().unwrap_or("?").to_string(),
191 line: record.line().unwrap_or(0),
192 msg: format!("{}", record.args()),
193 };
194 let mut events_lock = self.hot_log.lock();
195 events_lock.events.push(log_entry);
196 let need_signal =
197 (events_lock.events.total_elements() % (events_lock.events.capacity() / 2)) == 0;
198 if need_signal {
199 events_lock
200 .mover_thread
201 .as_ref()
202 .map(|jh| thread::Thread::unpark(jh.thread()));
203 }
204 }
205}