Skip to main content

brooks_lib/
logging.rs

1// brooks, Copyright 2026, Will Hawkins
2//
3// This file is part of brooks.
4
5// This file is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18use std::fmt::{Debug, Display};
19
20use crate::common::{Formatter, Location, Pretty};
21
22#[derive(Default, Debug, Clone)]
23pub struct LogMsgFormatter {
24    pub newline: bool,
25    pub show_level: bool,
26}
27
28impl Formatter<LogMsg> for LogMsgFormatter {
29    fn format(&self, value: &LogMsg) -> String {
30        let msg = if let Some(location) = &value.location {
31            format!("{location}: {}", value.msg)
32        } else {
33            value.msg.clone()
34        };
35
36        if self.show_level {
37            value.level().to_string() + ": " + &msg
38        } else {
39            msg
40        }
41    }
42}
43
44#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
45pub enum LogLevel {
46    Trace,
47    Debug,
48    Warn,
49    #[default]
50    Error,
51}
52
53impl Display for LogLevel {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            LogLevel::Trace => write!(f, "Trace"),
57            LogLevel::Debug => write!(f, "Debug"),
58            LogLevel::Warn => write!(f, "Warn"),
59            LogLevel::Error => write!(f, "Error"),
60        }
61    }
62}
63
64impl From<LogLevel> for log::Level {
65    fn from(value: LogLevel) -> Self {
66        match value {
67            LogLevel::Trace => log::Level::Trace,
68            LogLevel::Debug => log::Level::Debug,
69            LogLevel::Warn => log::Level::Warn,
70            LogLevel::Error => log::Level::Error,
71        }
72    }
73}
74
75#[derive(Debug, Default)]
76pub struct LogMsg {
77    msg: String,
78    location: Option<Box<dyn Location>>,
79    level: LogLevel,
80}
81
82impl LogMsg {
83    pub fn new(msg: &str, level: LogLevel) -> Self {
84        Self::new_with_location(msg, level, None)
85    }
86
87    pub fn new_with_location(
88        msg: &str,
89        level: LogLevel,
90        location: Option<Box<dyn Location>>,
91    ) -> Self {
92        LogMsg {
93            msg: msg.to_string(),
94            level,
95            location,
96        }
97    }
98
99    pub fn level(&self) -> LogLevel {
100        self.level.clone()
101    }
102
103    pub fn msg(&self) -> String {
104        self.msg.clone()
105    }
106
107    pub fn location(&self) -> &Option<Box<dyn Location>> {
108        &self.location
109    }
110}
111
112#[derive(Debug, Default)]
113pub struct LogMsgs {
114    msgs: Vec<LogMsg>,
115    level: LogLevel,
116    prefix: Option<String>,
117}
118
119impl LogMsgs {
120    pub fn new(level: LogLevel) -> Self {
121        LogMsgs {
122            msgs: vec![],
123            level,
124            prefix: None,
125        }
126    }
127
128    pub fn new_with_prefix(prefix: &str, level: LogLevel) -> Self {
129        LogMsgs {
130            msgs: vec![],
131            level,
132            prefix: Some(prefix.to_string()),
133        }
134    }
135
136    pub fn update_level(self, new_level: LogLevel) -> Self {
137        LogMsgs {
138            msgs: self.msgs,
139            level: new_level,
140            prefix: self.prefix.clone(),
141        }
142    }
143
144    pub fn log(self, msg: LogMsg) -> Self {
145        if msg.level >= self.level {
146            let msg = if let Some(prefix) = &self.prefix {
147                LogMsg {
148                    msg: format!("{prefix}: {}", msg.msg),
149                    location: msg.location,
150                    level: msg.level,
151                }
152            } else {
153                msg
154            };
155            let mut ns = self;
156            ns.msgs.push(msg);
157            ns
158        } else {
159            self
160        }
161    }
162
163    pub fn count(&self) -> usize {
164        self.msgs.len()
165    }
166
167    pub fn msgs(&self, formatter: &LogMsgFormatter) -> String {
168        self.msgs
169            .iter()
170            .map(|msg| msg.pretty(formatter))
171            .collect::<Vec<_>>()
172            .join(if formatter.newline { "\n" } else { ";" })
173    }
174
175    pub fn use_msgs(&self) -> &Vec<LogMsg> {
176        &self.msgs
177    }
178}
179
180macro_rules! emit_ {
181    ($nameloc:ident, $name:ident, $level:path) => {
182        #[allow(unused_macros)]
183        macro_rules! $nameloc {
184            ($log:expr, $loc:expr, $msg:expr ) => {
185                $log.log(LogMsg::new_with_location(
186                    $msg,
187                    $level,
188                    Some(Box::new($loc)),
189                ))
190            };
191        }
192
193        #[allow(unused_macros)]
194        macro_rules! $name {
195            ($log:expr, $msg:expr ) => {
196                $log.log(LogMsg::new($msg, $level))
197            };
198        }
199    };
200}
201
202emit_!(trace_with_loc, trace, LogLevel::Trace);
203emit_!(debug_with_loc, debug, LogLevel::Debug);
204emit_!(warn_with_loc, warn, LogLevel::Warn);
205emit_!(error_with_loc, error, LogLevel::Error);