1use std::cell::Cell;
6
7use crate::time::sim_time_ns;
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub enum Level {
11 Debug = 0,
12 Info = 1,
13 Warning = 2,
14 Error = 3,
15 Critical = 4,
16 Off = 5,
19}
20
21impl Level {
22 fn as_str(self) -> &'static str {
23 match self {
24 Level::Debug => "DEBUG",
25 Level::Info => "INFO",
26 Level::Warning => "WARNING",
27 Level::Error => "ERROR",
28 Level::Critical => "CRITICAL",
29 Level::Off => "OFF",
30 }
31 }
32}
33
34thread_local! {
35 static THRESHOLD: Cell<Level> = const { Cell::new(Level::Info) };
36}
37
38pub fn set_level(l: Level) {
39 THRESHOLD.with(|t| t.set(l));
40}
41
42pub fn log(level: Level, msg: &str) {
43 let enabled = THRESHOLD.with(|t| level >= t.get());
44 if enabled {
45 emit(&format!(
48 "{:>10.2}ns {:<8} {}",
49 sim_time_ns(),
50 level.as_str(),
51 msg
52 ));
53 }
54}
55
56pub fn debug(msg: &str) {
57 log(Level::Debug, msg);
58}
59pub fn info(msg: &str) {
60 log(Level::Info, msg);
61}
62pub fn warning(msg: &str) {
63 log(Level::Warning, msg);
64}
65pub fn error(msg: &str) {
66 log(Level::Error, msg);
67}
68
69pub fn critical(msg: &str) {
70 log(Level::Critical, msg);
71}
72
73use std::cell::RefCell;
78use std::io::Write;
79use std::rc::Rc;
80
81fn under(path: &str, prefix: &str) -> bool {
84 crate::path::str_is_under(path, prefix)
85}
86
87thread_local! {
88 static TARGET_LEVELS: RefCell<Vec<(String, Level)>> = const { RefCell::new(Vec::new()) };
92 static LOG_FILE: RefCell<Option<std::fs::File>> = const { RefCell::new(None) };
94 static TARGET_FILES: RefCell<Vec<(String, Rc<RefCell<std::fs::File>>)>> =
97 const { RefCell::new(Vec::new()) };
98 static TARGET_CONSOLE: RefCell<Vec<(String, bool)>> = const { RefCell::new(Vec::new()) };
101}
102
103pub fn set_level_for(path_prefix: &str, l: Level) {
106 TARGET_LEVELS.with(|t| {
107 let mut v = t.borrow_mut();
108 v.retain(|(p, _)| p != path_prefix);
109 v.push((path_prefix.to_string(), l));
110 });
111}
112
113pub fn log_to_file(path: &str, append: bool) -> std::io::Result<()> {
116 let file = open_log(path, append)?;
117 LOG_FILE.with(|f| *f.borrow_mut() = Some(file));
118 Ok(())
119}
120
121pub fn remove_log_file() {
123 LOG_FILE.with(|f| *f.borrow_mut() = None);
124}
125
126fn open_log(path: &str, append: bool) -> std::io::Result<std::fs::File> {
127 std::fs::OpenOptions::new()
128 .create(true)
129 .write(true)
130 .append(append)
131 .truncate(!append)
132 .open(path)
133}
134
135pub fn add_file_for(path_prefix: &str, path: &str, append: bool) -> std::io::Result<()> {
138 let file = Rc::new(RefCell::new(open_log(path, append)?));
139 TARGET_FILES.with(|t| t.borrow_mut().push((path_prefix.to_string(), file)));
140 Ok(())
141}
142
143pub fn set_console_for(path_prefix: &str, enabled: bool) {
146 TARGET_CONSOLE.with(|t| {
147 let mut v = t.borrow_mut();
148 v.retain(|(p, _)| p != path_prefix);
149 v.push((path_prefix.to_string(), enabled));
150 });
151}
152
153pub fn reset_config() {
160 THRESHOLD.with(|t| t.set(Level::Info));
161 TARGET_LEVELS.with(|t| t.borrow_mut().clear());
162 TARGET_FILES.with(|t| t.borrow_mut().clear());
163 TARGET_CONSOLE.with(|t| t.borrow_mut().clear());
164 LOG_FILE.with(|f| *f.borrow_mut() = None);
165}
166
167fn console_enabled_for(path: &str) -> bool {
168 TARGET_CONSOLE
169 .with(|t| {
170 t.borrow()
171 .iter()
172 .filter(|(p, _)| under(path, p))
173 .max_by_key(|(p, _)| p.len())
174 .map(|(_, on)| *on)
175 })
176 .unwrap_or(true)
177}
178
179fn emit_for(path: &str, line: &str) {
182 if console_enabled_for(path) {
183 println!("{line}");
184 }
185 TARGET_FILES.with(|t| {
186 for (prefix, file) in t.borrow().iter() {
187 if under(path, prefix) {
188 let _ = writeln!(file.borrow_mut(), "{line}");
189 }
190 }
191 });
192 LOG_FILE.with(|f| {
193 if let Some(file) = f.borrow_mut().as_mut() {
194 let _ = writeln!(file, "{line}");
195 }
196 });
197}
198
199fn emit(line: &str) {
200 println!("{line}");
201 LOG_FILE.with(|f| {
202 if let Some(file) = f.borrow_mut().as_mut() {
203 let _ = writeln!(file, "{line}");
204 }
205 });
206}
207
208#[derive(Clone)]
213pub struct Logger {
214 path: crate::path::RustdvPath,
218}
219
220impl Logger {
221 pub fn new(path: &str) -> Logger {
225 let mut p = crate::path::RustdvPath::empty();
226 if !path.is_empty() {
227 for seg in path.split('.') {
228 p = p.child(seg);
229 }
230 }
231 Logger { path: p }
232 }
233
234 pub fn at(path: crate::path::RustdvPath) -> Logger {
236 Logger { path }
237 }
238
239 pub fn path(&self) -> &str {
240 self.path.as_str()
241 }
242
243 pub fn rustdv_path(&self) -> &crate::path::RustdvPath {
246 &self.path
247 }
248
249 fn enabled(&self, level: Level) -> bool {
250 let per_target = TARGET_LEVELS.with(|t| {
251 t.borrow()
252 .iter()
253 .filter(|(p, _)| under(self.path.as_str(), p))
254 .max_by_key(|(p, _)| p.len())
255 .map(|(_, l)| *l)
256 });
257 match per_target {
258 Some(l) => level >= l,
259 None => THRESHOLD.with(|t| level >= t.get()),
260 }
261 }
262
263 pub fn log(&self, level: Level, msg: &str) {
264 if self.enabled(level) {
265 emit_for(
266 self.path.as_str(),
267 &format!(
268 "{:>10.2}ns {:<8} [{}]: {}",
269 sim_time_ns(),
270 level.as_str(),
271 self.path,
272 msg
273 ),
274 );
275 }
276 }
277
278 pub fn debug(&self, msg: &str) {
279 self.log(Level::Debug, msg);
280 }
281 pub fn info(&self, msg: &str) {
282 self.log(Level::Info, msg);
283 }
284 pub fn warning(&self, msg: &str) {
285 self.log(Level::Warning, msg);
286 }
287 pub fn error(&self, msg: &str) {
288 self.log(Level::Error, msg);
289 }
290 pub fn critical(&self, msg: &str) {
291 self.log(Level::Critical, msg);
292 }
293}