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 } |