Mercurial > crates > nonstick
view src/logging.rs @ 98:b87100c5eed4
Start on environment variables, and make pointers nicer.
This starts work on the PAM environment handling, and in so doing,
introduces the CHeapBox and CHeapString structs. These are analogous
to Box and CString, but they're located on the C heap rather than
being Rust-managed memory.
This is because environment variables deal with even more pointers
and it turns out we can lose a lot of manual freeing using homemade
smart pointers.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 24 Jun 2025 04:25:25 -0400 |
parents | 5ddbcada30f2 |
children | dfcd96a74ac4 |
line wrap: on
line source
//! PAM logging variables and macros. //! //! PAM implementations usually include the ability to log to syslog in a way //! that is associated with the log entry itself. This module defines the enums //! and macros for logging. //! //! For more details, see [`PamShared::log`](crate::PamShared::log). //! //! We can't use [the `log` crate](https://docs.rs/log) because that requires //! that any `Log` implementors be `Sync` and `Send`, and a PAM handle //! may be neither. Furthermore, PAM handles are passed to PAM modules in //! dynamic libraries, and `log` doesn't work across dynamic linking boundaries. //! //! A `PamShared` implementation may still use the `log` crate on the backend, //! and may even itself implement `log::Log`, but that interface is not exposed //! to the generic PAM user. #[cfg(feature = "link")] mod levels { pub use internal::*; #[cfg(pam_impl = "linux-pam")] mod internal { use crate::libpam::pam_ffi; pub const ERROR: u32 = pam_ffi::LOG_ERR; pub const WARN: u32 = pam_ffi::LOG_WARNING; pub const INFO: u32 = pam_ffi::LOG_INFO; pub const DEBUG: u32 = pam_ffi::LOG_DEBUG; } #[cfg(pam_impl = "openpam")] mod internal { use crate::libpam::pam_ffi; pub const ERROR: u32 = pam_ffi::PAM_LOG_ERROR; pub const WARN: u32 = pam_ffi::PAM_LOG_NOTICE; pub const INFO: u32 = pam_ffi::PAM_LOG_VERBOSE; pub const DEBUG: u32 = pam_ffi::PAM_LOG_DEBUG; } } #[cfg(not(feature = "link"))] mod levels { pub const ERROR: u32 = 2255887; pub const WARN: u32 = 7265000; pub const INFO: u32 = 7762323; pub const DEBUG: u32 = 8675309; } /// An entry to be added to the log. /// /// The levels are in descending order of importance and correspond roughly /// to the similarly-named levels in the `log` crate. /// /// In all implementations, these are ordered such that `Error`, `Warning`, /// `Info`, and `Debug` are in ascending order. #[derive(Debug, PartialEq, Ord, PartialOrd, Eq)] #[repr(u32)] pub enum Level { Error = levels::ERROR, Warning = levels::WARN, Info = levels::INFO, Debug = levels::DEBUG, } /// Here's the guts of the logger thingy. You shouldn't be using this! #[doc(hidden)] #[macro_export] macro_rules! __log_internal { ($handle:expr, $level:ident, $($arg:tt)+) => { $handle.log($crate::logging::Level::$level, &format!($($arg)+)); } } /// Logs a message at error level via the given PAM handle. /// /// This supports `format!`-style formatting. /// /// # Example /// /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { /// # let load_error = "xxx"; /// nonstick::error!(pam_handle, "error loading data from data source: {load_error}"); /// // Will log a message like "error loading data from data source: timed out" /// // at ERROR level on syslog. /// # } /// ``` #[macro_export] macro_rules! error { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Error, $($arg)+);}} /// Logs a message at warning level via the given PAM handle. /// /// This supports `format!`-style formatting. /// /// # Example /// /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { /// # let latency_ms = "xxx"; /// nonstick::warn!(pam_handle, "loading took too long: {latency_ms} ms"); /// // Will log a message like "loading took too long: 495 ms" /// // at WARN level on syslog. /// # } /// ``` #[macro_export] macro_rules! warn { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Warning, $($arg)+);}} /// Logs a message at info level via the given PAM handle. /// /// This supports `format!`-style formatting. /// /// # Example /// /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { /// nonstick::info!(pam_handle, "using remote backend to load user data"); /// // Will log a message like "using remote backend to load user data" /// // at INFO level on syslog. /// # } /// ``` #[macro_export] macro_rules! info { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Info, $($arg)+);}} /// Logs a message at debug level via the given PAM handle. /// /// This level specially includes file/line/column information. /// This supports `format!`-style formatting. /// /// # Example /// /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { /// # let userinfo_url = "https://zombo.com/"; /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}"); /// // Will log a message like /// // "pam_http/lib.rs:39:14: making HTTP GET request to https://zombo.com/" /// // at DEBUG level on syslog. /// # } /// ``` #[macro_export] macro_rules! debug {($handle:expr, $($arg:tt)+) => { $crate::__log_internal!( $handle, Debug, "{}:{}:{}: {}", file!(), line!(), column!(), format_args!($($arg)+), ); }} #[cfg(test)] mod tests { use super::*; use regex::Regex; use std::cell::RefCell; #[test] fn test_order() { assert!(Level::Error < Level::Warning); assert!(Level::Warning < Level::Info); assert!(Level::Info < Level::Debug); } #[test] fn test_logging() { struct Logger(RefCell<Vec<(Level, String)>>); impl Logger { fn log(&self, level: Level, text: &str) { self.0.borrow_mut().push((level, text.to_owned())) } } let logger = Logger(Default::default()); let something = Level::Error; error!(logger, "here is another thing: {}", 99); warn!(logger, "watch out!"); info!(logger, "here is some info: {info}", info = "information"); debug!(logger, "here is something: {something:?}"); let mut logged = logger.0.into_inner(); let (last_level, last_string) = logged.pop().unwrap(); assert_eq!(Level::Debug, last_level); let expr = Regex::new(r"^[^:]+:\d+:\d+: here is something: Error$").unwrap(); assert!( expr.is_match(&last_string), "{last_string:?} did not match {expr:?}" ); assert_eq!( vec![ (Level::Error, "here is another thing: 99".to_owned()), (Level::Warning, "watch out!".to_owned()), (Level::Info, "here is some info: information".to_owned()), ], logged ); } }