Mercurial > crates > nonstick
comparison src/logging.rs @ 92:5ddbcada30f2
Add the ability to log against a PAM handle.
PAM impls provide a way to log to syslog. This exposes it via nonstick.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Sun, 22 Jun 2025 19:29:32 -0400 |
| parents | |
| children | b87100c5eed4 |
comparison
equal
deleted
inserted
replaced
| 91:039aae9a01f7 | 92:5ddbcada30f2 |
|---|---|
| 1 //! PAM logging variables and macros. | |
| 2 //! | |
| 3 //! PAM implementations usually include the ability to log to syslog in a way | |
| 4 //! that is associated with the log entry itself. This module defines the enums | |
| 5 //! and macros for logging. | |
| 6 //! | |
| 7 //! For more details, see [`PamShared::log`](crate::PamShared::log). | |
| 8 //! | |
| 9 //! We can't use [the `log` crate](https://docs.rs/log) because that requires | |
| 10 //! that any `Log` implementors be `Sync` and `Send`, and a PAM handle | |
| 11 //! may be neither. Furthermore, PAM handles are passed to PAM modules in | |
| 12 //! dynamic libraries, and `log` doesn't work across dynamic linking boundaries. | |
| 13 //! | |
| 14 //! A `PamShared` implementation may still use the `log` crate on the backend, | |
| 15 //! and may even itself implement `log::Log`, but that interface is not exposed | |
| 16 //! to the generic PAM user. | |
| 17 | |
| 18 #[cfg(feature = "link")] | |
| 19 mod levels { | |
| 20 pub use internal::*; | |
| 21 #[cfg(pam_impl = "linux-pam")] | |
| 22 mod internal { | |
| 23 use crate::libpam::pam_ffi; | |
| 24 pub const ERROR: u32 = pam_ffi::LOG_ERR; | |
| 25 pub const WARN: u32 = pam_ffi::LOG_WARNING; | |
| 26 pub const INFO: u32 = pam_ffi::LOG_INFO; | |
| 27 pub const DEBUG: u32 = pam_ffi::LOG_DEBUG; | |
| 28 } | |
| 29 #[cfg(pam_impl = "openpam")] | |
| 30 mod internal { | |
| 31 use crate::libpam::pam_ffi; | |
| 32 pub const ERROR: u32 = pam_ffi::PAM_LOG_ERROR; | |
| 33 pub const WARN: u32 = pam_ffi::PAM_LOG_NOTICE; | |
| 34 pub const INFO: u32 = pam_ffi::PAM_LOG_VERBOSE; | |
| 35 pub const DEBUG: u32 = pam_ffi::PAM_LOG_DEBUG; | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 #[cfg(not(feature = "link"))] | |
| 40 mod levels { | |
| 41 pub const ERROR: u32 = 2255887; | |
| 42 pub const WARN: u32 = 7265000; | |
| 43 pub const INFO: u32 = 7762323; | |
| 44 pub const DEBUG: u32 = 8675309; | |
| 45 } | |
| 46 | |
| 47 /// An entry to be added to the log. | |
| 48 /// | |
| 49 /// The levels are in descending order of importance and correspond roughly | |
| 50 /// to the similarly-named levels in the `log` crate. | |
| 51 /// | |
| 52 /// In all implementations, these are ordered such that `Error`, `Warning`, | |
| 53 /// `Info`, and `Debug` are in ascending order. | |
| 54 #[derive(Debug, PartialEq, Ord, PartialOrd, Eq)] | |
| 55 #[repr(u32)] | |
| 56 pub enum Level { | |
| 57 Error = levels::ERROR, | |
| 58 Warning = levels::WARN, | |
| 59 Info = levels::INFO, | |
| 60 Debug = levels::DEBUG, | |
| 61 } | |
| 62 | |
| 63 /// Here's the guts of the logger thingy. You shouldn't be using this! | |
| 64 #[doc(hidden)] | |
| 65 #[macro_export] | |
| 66 macro_rules! __log_internal { | |
| 67 ($handle:expr, $level:ident, $($arg:tt)+) => { | |
| 68 $handle.log($crate::logging::Level::$level, &format!($($arg)+)); | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 /// Logs a message at error level via the given PAM handle. | |
| 73 /// | |
| 74 /// This supports `format!`-style formatting. | |
| 75 /// | |
| 76 /// # Example | |
| 77 /// | |
| 78 /// ```no_run | |
| 79 /// # let pam_handle: Box<dyn nonstick::PamShared> = unimplemented!(); | |
| 80 /// # let load_error = "xxx"; | |
| 81 /// nonstick::error!(pam_handle, "error loading data from data source: {load_error}"); | |
| 82 /// // Will log a message like "error loading data from data source: timed out" | |
| 83 /// // at ERROR level on syslog. | |
| 84 /// ``` | |
| 85 #[macro_export] | |
| 86 macro_rules! error { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Error, $($arg)+);}} | |
| 87 | |
| 88 /// Logs a message at warning level via the given PAM handle. | |
| 89 /// | |
| 90 /// This supports `format!`-style formatting. | |
| 91 /// | |
| 92 /// # Example | |
| 93 /// | |
| 94 /// ```no_run | |
| 95 /// # let pam_handle: Box<dyn nonstick::PamShared> = unimplemented!(); | |
| 96 /// # let latency_ms = "xxx"; | |
| 97 /// nonstick::warn!(pam_handle, "loading took too long: {latency_ms} ms"); | |
| 98 /// // Will log a message like "loading took too long: 495 ms" | |
| 99 /// // at WARN level on syslog. | |
| 100 /// ``` | |
| 101 #[macro_export] | |
| 102 macro_rules! warn { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Warning, $($arg)+);}} | |
| 103 | |
| 104 /// Logs a message at info level via the given PAM handle. | |
| 105 /// | |
| 106 /// This supports `format!`-style formatting. | |
| 107 /// | |
| 108 /// # Example | |
| 109 /// | |
| 110 /// ```no_run | |
| 111 /// # let pam_handle: Box<dyn nonstick::PamShared> = unimplemented!(); | |
| 112 /// nonstick::info!(pam_handle, "using remote backend to load user data"); | |
| 113 /// // Will log a message like "using remote backend to load user data" | |
| 114 /// // at INFO level on syslog. | |
| 115 /// ``` | |
| 116 #[macro_export] | |
| 117 macro_rules! info { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Info, $($arg)+);}} | |
| 118 | |
| 119 /// Logs a message at debug level via the given PAM handle. | |
| 120 /// | |
| 121 /// This level specially includes file/line/column information. | |
| 122 /// This supports `format!`-style formatting. | |
| 123 /// | |
| 124 /// # Example | |
| 125 /// | |
| 126 /// ```no_run | |
| 127 /// # let pam_handle: Box<dyn nonstick::PamShared> = unimplemented!(); | |
| 128 /// # let userinfo_url = "https://zombo.com/"; | |
| 129 /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}"); | |
| 130 /// // Will log a message like | |
| 131 /// // "pam_http/lib.rs:39:14: making HTTP GET request to https://zombo.com/" | |
| 132 /// // at DEBUG level on syslog. | |
| 133 /// ``` | |
| 134 #[macro_export] | |
| 135 macro_rules! debug {($handle:expr, $($arg:tt)+) => { | |
| 136 $crate::__log_internal!( | |
| 137 $handle, Debug, | |
| 138 "{}:{}:{}: {}", file!(), line!(), column!(), format_args!($($arg)+), | |
| 139 ); | |
| 140 }} | |
| 141 | |
| 142 #[cfg(test)] | |
| 143 mod tests { | |
| 144 use super::*; | |
| 145 use regex::Regex; | |
| 146 use std::cell::RefCell; | |
| 147 | |
| 148 #[test] | |
| 149 fn test_order() { | |
| 150 assert!(Level::Error < Level::Warning); | |
| 151 assert!(Level::Warning < Level::Info); | |
| 152 assert!(Level::Info < Level::Debug); | |
| 153 } | |
| 154 | |
| 155 #[test] | |
| 156 fn test_logging() { | |
| 157 struct Logger(RefCell<Vec<(Level, String)>>); | |
| 158 | |
| 159 impl Logger { | |
| 160 fn log(&self, level: Level, text: &str) { | |
| 161 self.0.borrow_mut().push((level, text.to_owned())) | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 let logger = Logger(Default::default()); | |
| 166 | |
| 167 let something = Level::Error; | |
| 168 error!(logger, "here is another thing: {}", 99); | |
| 169 warn!(logger, "watch out!"); | |
| 170 info!(logger, "here is some info: {info}", info = "information"); | |
| 171 debug!(logger, "here is something: {something:?}"); | |
| 172 | |
| 173 let mut logged = logger.0.into_inner(); | |
| 174 | |
| 175 let (last_level, last_string) = logged.pop().unwrap(); | |
| 176 assert_eq!(Level::Debug, last_level); | |
| 177 let expr = Regex::new(r"^[^:]+:\d+:\d+: here is something: Error$").unwrap(); | |
| 178 assert!( | |
| 179 expr.is_match(&last_string), | |
| 180 "{last_string:?} did not match {expr:?}" | |
| 181 ); | |
| 182 | |
| 183 assert_eq!( | |
| 184 vec![ | |
| 185 (Level::Error, "here is another thing: 99".to_owned()), | |
| 186 (Level::Warning, "watch out!".to_owned()), | |
| 187 (Level::Info, "here is some info: information".to_owned()), | |
| 188 ], | |
| 189 logged | |
| 190 ); | |
| 191 } | |
| 192 } |
