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!("{:>10.2}ns {:<8} {}", sim_time_ns(), level.as_str(), msg));
48 }
49}
50
51pub fn debug(msg: &str) {
52 log(Level::Debug, msg);
53}
54pub fn info(msg: &str) {
55 log(Level::Info, msg);
56}
57pub fn warning(msg: &str) {
58 log(Level::Warning, msg);
59}
60pub fn error(msg: &str) {
61 log(Level::Error, msg);
62}
63
64pub fn critical(msg: &str) {
65 log(Level::Critical, msg);
66}
67
68use std::cell::RefCell;
73use std::io::Write;
74use std::rc::Rc;
75
76fn under(path: &str, prefix: &str) -> bool {
79 crate::path::str_is_under(path, prefix)
80}
81
82thread_local! {
83 static TARGET_LEVELS: RefCell<Vec<(String, Level)>> = const { RefCell::new(Vec::new()) };
87 static LOG_FILE: RefCell<Option<std::fs::File>> = const { RefCell::new(None) };
89 static TARGET_FILES: RefCell<Vec<(String, Rc<RefCell<std::fs::File>>)>> =
92 const { RefCell::new(Vec::new()) };
93 static TARGET_CONSOLE: RefCell<Vec<(String, bool)>> = const { RefCell::new(Vec::new()) };
96}
97
98pub fn set_level_for(path_prefix: &str, l: Level) {
101 TARGET_LEVELS.with(|t| {
102 let mut v = t.borrow_mut();
103 v.retain(|(p, _)| p != path_prefix);
104 v.push((path_prefix.to_string(), l));
105 });
106}
107
108pub fn log_to_file(path: &str, append: bool) -> std::io::Result<()> {
111 let file = open_log(path, append)?;
112 LOG_FILE.with(|f| *f.borrow_mut() = Some(file));
113 Ok(())
114}
115
116pub fn remove_log_file() {
118 LOG_FILE.with(|f| *f.borrow_mut() = None);
119}
120
121fn open_log(path: &str, append: bool) -> std::io::Result<std::fs::File> {
122 std::fs::OpenOptions::new()
123 .create(true)
124 .write(true)
125 .append(append)
126 .truncate(!append)
127 .open(path)
128}
129
130pub fn add_file_for(path_prefix: &str, path: &str, append: bool) -> std::io::Result<()> {
133 let file = Rc::new(RefCell::new(open_log(path, append)?));
134 TARGET_FILES.with(|t| t.borrow_mut().push((path_prefix.to_string(), file)));
135 Ok(())
136}
137
138pub fn set_console_for(path_prefix: &str, enabled: bool) {
141 TARGET_CONSOLE.with(|t| {
142 let mut v = t.borrow_mut();
143 v.retain(|(p, _)| p != path_prefix);
144 v.push((path_prefix.to_string(), enabled));
145 });
146}
147
148pub fn reset_config() {
155 THRESHOLD.with(|t| t.set(Level::Info));
156 TARGET_LEVELS.with(|t| t.borrow_mut().clear());
157 TARGET_FILES.with(|t| t.borrow_mut().clear());
158 TARGET_CONSOLE.with(|t| t.borrow_mut().clear());
159 LOG_FILE.with(|f| *f.borrow_mut() = None);
160}
161
162fn console_enabled_for(path: &str) -> bool {
163 TARGET_CONSOLE
164 .with(|t| {
165 t.borrow()
166 .iter()
167 .filter(|(p, _)| under(path, p))
168 .max_by_key(|(p, _)| p.len())
169 .map(|(_, on)| *on)
170 })
171 .unwrap_or(true)
172}
173
174fn emit_for(path: &str, line: &str) {
177 if console_enabled_for(path) {
178 println!("{line}");
179 }
180 TARGET_FILES.with(|t| {
181 for (prefix, file) in t.borrow().iter() {
182 if under(path, prefix) {
183 let _ = writeln!(file.borrow_mut(), "{line}");
184 }
185 }
186 });
187 LOG_FILE.with(|f| {
188 if let Some(file) = f.borrow_mut().as_mut() {
189 let _ = writeln!(file, "{line}");
190 }
191 });
192}
193
194fn emit(line: &str) {
195 println!("{line}");
196 LOG_FILE.with(|f| {
197 if let Some(file) = f.borrow_mut().as_mut() {
198 let _ = writeln!(file, "{line}");
199 }
200 });
201}
202
203#[derive(Clone)]
208pub struct Logger {
209 path: crate::path::RustdvPath,
213}
214
215impl Logger {
216 pub fn new(path: &str) -> Logger {
220 let mut p = crate::path::RustdvPath::empty();
221 if !path.is_empty() {
222 for seg in path.split('.') {
223 p = p.child(seg);
224 }
225 }
226 Logger { path: p }
227 }
228
229 pub fn at(path: crate::path::RustdvPath) -> Logger {
231 Logger { path }
232 }
233
234 pub fn path(&self) -> &str {
235 self.path.as_str()
236 }
237
238 pub fn rustdv_path(&self) -> &crate::path::RustdvPath {
241 &self.path
242 }
243
244 fn enabled(&self, level: Level) -> bool {
245 let per_target = TARGET_LEVELS.with(|t| {
246 t.borrow()
247 .iter()
248 .filter(|(p, _)| under(self.path.as_str(), p))
249 .max_by_key(|(p, _)| p.len())
250 .map(|(_, l)| *l)
251 });
252 match per_target {
253 Some(l) => level >= l,
254 None => THRESHOLD.with(|t| level >= t.get()),
255 }
256 }
257
258 pub fn log(&self, level: Level, msg: &str) {
259 if self.enabled(level) {
260 emit_for(
261 self.path.as_str(),
262 &format!(
263 "{:>10.2}ns {:<8} [{}]: {}",
264 sim_time_ns(),
265 level.as_str(),
266 self.path,
267 msg
268 ),
269 );
270 }
271 }
272
273 pub fn debug(&self, msg: &str) {
274 self.log(Level::Debug, msg);
275 }
276 pub fn info(&self, msg: &str) {
277 self.log(Level::Info, msg);
278 }
279 pub fn warning(&self, msg: &str) {
280 self.log(Level::Warning, msg);
281 }
282 pub fn error(&self, msg: &str) {
283 self.log(Level::Error, msg);
284 }
285 pub fn critical(&self, msg: &str) {
286 self.log(Level::Critical, msg);
287 }
288}