Mercurial > crates > nonstick
comparison src/logging.rs @ 116:a12706e42c9d
Logging, macros, and building:
- Changes logging API to accept the `Location` of the log statement.
Fixes OpenPAM implementation.
- Stops publicly exporting doc macros.
- Uses dlopen to detect the PAM library rather than header jankery.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Sun, 29 Jun 2025 18:27:51 -0400 |
| parents | 1e11a52b4665 |
| children | 39760dfc9b3b |
comparison
equal
deleted
inserted
replaced
| 115:1e11a52b4665 | 116:a12706e42c9d |
|---|---|
| 36 /// The levels are in descending order of importance and correspond roughly | 36 /// The levels are in descending order of importance and correspond roughly |
| 37 /// to the similarly-named levels in the `log` crate. | 37 /// to the similarly-named levels in the `log` crate. |
| 38 /// | 38 /// |
| 39 /// Their values are ordered monotonically, either increasing or decreasing, | 39 /// Their values are ordered monotonically, either increasing or decreasing, |
| 40 /// depending upon the implementation. | 40 /// depending upon the implementation. |
| 41 #[derive(Debug, PartialEq, Eq)] | 41 #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 42 #[repr(i32)] | 42 #[repr(i32)] |
| 43 pub enum Level { | 43 pub enum Level { |
| 44 Error = levels::ERROR, | 44 Error = levels::ERROR, |
| 45 Warning = levels::WARN, | 45 Warning = levels::WARN, |
| 46 Info = levels::INFO, | 46 Info = levels::INFO, |
| 47 Debug = levels::DEBUG, | 47 Debug = levels::DEBUG, |
| 48 } | 48 } |
| 49 | 49 |
| 50 /// The location of a log entry. | |
| 51 #[derive(Clone, Copy, Debug, Default)] | |
| 52 pub struct Location<'a> { | |
| 53 pub file: &'a str, | |
| 54 pub line: u32, | |
| 55 pub function: &'a str, | |
| 56 _more: (), | |
| 57 } | |
| 58 | |
| 59 impl<'a> Location<'a> { | |
| 60 pub fn new(file: &'a str, line: u32, function: &'a str) -> Self { | |
| 61 Self {file, line, function, _more: ()} | |
| 62 } | |
| 63 } | |
| 64 | |
| 65 /// The [`Location`] where this macro is inserted. | |
| 66 #[doc(hidden)] | |
| 67 #[macro_export] | |
| 68 macro_rules! location { | |
| 69 () => { $crate::logging::Location::new(file!(), line!(), $crate::__function!()) } | |
| 70 } | |
| 71 | |
| 50 /// Here's the guts of the logger thingy. You shouldn't be using this! | 72 /// Here's the guts of the logger thingy. You shouldn't be using this! |
| 51 #[doc(hidden)] | 73 #[doc(hidden)] |
| 52 #[macro_export] | 74 #[macro_export] |
| 53 macro_rules! __log_internal { | 75 macro_rules! __log_internal { |
| 54 ($handle:expr, $level:ident, $($arg:tt)+) => { | 76 ($handle:expr, $level:ident, $($arg:tt)+) => { |
| 55 $handle.log($crate::logging::Level::$level, &format!($($arg)+)); | 77 $handle.log($crate::logging::Level::$level, $crate::location!(), &format!($($arg)+)); |
| 56 } | 78 } |
| 79 } | |
| 80 | |
| 81 /// Ugly, hacky macro to get the current function name. | |
| 82 /// | |
| 83 /// [Stolen from Stack Overflow.][https://stackoverflow.com/a/40234666/39808] | |
| 84 #[doc(hidden)] | |
| 85 #[macro_export] | |
| 86 macro_rules! __function { | |
| 87 () => {{ | |
| 88 fn p() {} | |
| 89 fn f<T>(_: T) -> &'static str { | |
| 90 ::std::any::type_name::<T>() | |
| 91 } | |
| 92 f(p).trim_end_matches("::p") | |
| 93 }} | |
| 57 } | 94 } |
| 58 | 95 |
| 59 /// Logs a message at error level via the given PAM handle. | 96 /// Logs a message at error level via the given PAM handle. |
| 60 /// | 97 /// |
| 61 /// This supports `format!`-style formatting. | 98 /// This supports `format!`-style formatting. |
| 123 /// ```no_run | 160 /// ```no_run |
| 124 /// # fn _test(pam_handle: impl nonstick::PamShared) { | 161 /// # fn _test(pam_handle: impl nonstick::PamShared) { |
| 125 /// # let userinfo_url = "https://zombo.com/"; | 162 /// # let userinfo_url = "https://zombo.com/"; |
| 126 /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}"); | 163 /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}"); |
| 127 /// // Will log a message like | 164 /// // Will log a message like |
| 128 /// // "pam_http/lib.rs:39:14: making HTTP GET request to https://zombo.com/" | 165 /// // making HTTP GET request to https://zombo.com/" |
| 129 /// // at DEBUG level on syslog. | 166 /// // at DEBUG level on syslog. |
| 130 /// # } | 167 /// # } |
| 131 /// ``` | 168 /// ``` |
| 132 #[macro_export] | 169 #[macro_export] |
| 133 macro_rules! debug {($handle:expr, $($arg:tt)+) => { | 170 macro_rules! debug { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Debug, $($arg)+);}} |
| 134 $crate::__log_internal!( | |
| 135 $handle, Debug, | |
| 136 "{}:{}:{}: {}", file!(), line!(), column!(), format_args!($($arg)+), | |
| 137 ); | |
| 138 }} | |
| 139 | 171 |
| 140 #[cfg(test)] | 172 #[cfg(test)] |
| 141 mod tests { | 173 mod tests { |
| 142 use super::*; | 174 use super::*; |
| 143 use regex::Regex; | 175 use regex::Regex; |
| 146 #[test] | 178 #[test] |
| 147 fn test_logging() { | 179 fn test_logging() { |
| 148 struct Logger(RefCell<Vec<(Level, String)>>); | 180 struct Logger(RefCell<Vec<(Level, String)>>); |
| 149 | 181 |
| 150 impl Logger { | 182 impl Logger { |
| 151 fn log(&self, level: Level, text: &str) { | 183 fn log(&self, level: Level, loc: Location<'_>, text: &str) { |
| 152 self.0.borrow_mut().push((level, text.to_owned())) | 184 self.0.borrow_mut().push((level, text.to_owned())) |
| 153 } | 185 } |
| 154 } | 186 } |
| 155 | 187 |
| 156 let logger = Logger(Default::default()); | 188 let logger = Logger(Default::default()); |
| 159 error!(logger, "here is another thing: {}", 99); | 191 error!(logger, "here is another thing: {}", 99); |
| 160 warn!(logger, "watch out!"); | 192 warn!(logger, "watch out!"); |
| 161 info!(logger, "here is some info: {info}", info = "information"); | 193 info!(logger, "here is some info: {info}", info = "information"); |
| 162 debug!(logger, "here is something: {something:?}"); | 194 debug!(logger, "here is something: {something:?}"); |
| 163 | 195 |
| 164 let mut logged = logger.0.into_inner(); | 196 let logged = logger.0.into_inner(); |
| 165 | |
| 166 let (last_level, last_string) = logged.pop().unwrap(); | |
| 167 assert_eq!(Level::Debug, last_level); | |
| 168 let expr = Regex::new(r"^[^:]+:\d+:\d+: here is something: Error$").unwrap(); | |
| 169 assert!( | |
| 170 expr.is_match(&last_string), | |
| 171 "{last_string:?} did not match {expr:?}" | |
| 172 ); | |
| 173 | 197 |
| 174 assert_eq!( | 198 assert_eq!( |
| 175 vec![ | 199 vec![ |
| 176 (Level::Error, "here is another thing: 99".to_owned()), | 200 (Level::Error, "here is another thing: 99".to_owned()), |
| 177 (Level::Warning, "watch out!".to_owned()), | 201 (Level::Warning, "watch out!".to_owned()), |
| 178 (Level::Info, "here is some info: information".to_owned()), | 202 (Level::Info, "here is some info: information".to_owned()), |
| 203 (Level::Debug, "here is something: Error".to_owned()), | |
| 179 ], | 204 ], |
| 180 logged | 205 logged |
| 181 ); | 206 ); |
| 182 } | 207 } |
| 183 } | 208 } |
