1 // Copyright 2022 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Facilities for sending log message to syslog.
6 //!
7 //! Every function exported by this module is thread-safe. Each function will silently fail until
8 //! `syslog::init()` is called and returns `Ok`.
9 //!
10 //! # Examples
11 //!
12 //! ```
13 //! use crate::platform::{error, syslog, warn};
14 //!
15 //! if let Err(e) = syslog::init() {
16 //! println!("failed to initiailize syslog: {}", e);
17 //! return;
18 //! }
19 //! warn!("this is your {} warning", "final");
20 //! error!("something went horribly wrong: {}", "out of RAMs");
21 //! ```
22
23 pub use super::win::syslog::PlatformSyslog;
24 use super::RawDescriptor;
25 use crate::descriptor::AsRawDescriptor;
26 use crate::{syslog_lock, CHRONO_TIMESTAMP_FIXED_FMT};
27 use serde::{Deserialize, Serialize};
28 use std::{
29 convert::{From, Into, TryFrom},
30 env,
31 ffi::{OsStr, OsString},
32 fmt::{
33 Display, {self},
34 },
35 fs::File,
36 io,
37 io::{stderr, Cursor, Write},
38 path::{Path, PathBuf},
39 sync::{MutexGuard, Once},
40 };
41
42 use remain::sorted;
43 use sync::Mutex;
44 use thiserror::Error as ThisError;
45
46 /// The priority (i.e. severity) of a syslog message.
47 ///
48 /// See syslog man pages for information on their semantics.
49 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
50 pub enum Priority {
51 Emergency = 0,
52 Alert = 1,
53 Critical = 2,
54 Error = 3,
55 Warning = 4,
56 Notice = 5,
57 Info = 6,
58 Debug = 7,
59 }
60
61 impl Display for Priority {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 use self::Priority::*;
64
65 let string = match self {
66 Emergency => "EMERGENCY",
67 Alert => "ALERT",
68 Critical => "CRITICAL",
69 Error => "ERROR",
70 Warning => "WARNING",
71 Notice => "NOTICE",
72 Info => "INFO",
73 Debug => "DEBUG",
74 };
75
76 write!(f, "{}", string)
77 }
78 }
79
80 impl TryFrom<&str> for Priority {
81 type Error = &'static str;
82
try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error>83 fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
84 match value {
85 "0" | "EMERGENCY" => Ok(Priority::Emergency),
86 "1" | "ALERT" => Ok(Priority::Alert),
87 "2" | "CRITICAL" => Ok(Priority::Critical),
88 "3" | "ERROR" => Ok(Priority::Error),
89 "4" | "WARNING" => Ok(Priority::Warning),
90 "5" | "NOTICE" => Ok(Priority::Notice),
91 "6" | "INFO" => Ok(Priority::Info),
92 "7" | "DEBUG" => Ok(Priority::Debug),
93 _ => Err("Priority can only be parsed from 0-7 and given variant names"),
94 }
95 }
96 }
97
98 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
99 pub enum PriorityFilter {
100 Silent,
101 Priority(Priority),
102 ShowAll,
103 }
104
105 impl From<Priority> for PriorityFilter {
from(pri: Priority) -> Self106 fn from(pri: Priority) -> Self {
107 PriorityFilter::Priority(pri)
108 }
109 }
110
111 impl TryFrom<&str> for PriorityFilter {
112 type Error = &'static str;
113
try_from(value: &str) -> Result<Self, Self::Error>114 fn try_from(value: &str) -> Result<Self, Self::Error> {
115 match value.to_uppercase().as_str() {
116 "S" | "SILENT" => Ok(PriorityFilter::Silent),
117 "*" => Ok(PriorityFilter::ShowAll),
118 value => match Priority::try_from(value) {
119 Ok(pri) => Ok(PriorityFilter::Priority(pri)),
120 Err(_) => Err("PriorityFilter can only be parsed from valid Priority
121 value, S, *, or SILENT"),
122 },
123 }
124 }
125 }
126
127 /// The facility of a syslog message.
128 ///
129 /// See syslog man pages for information on their semantics.
130 #[derive(Copy, Clone)]
131 pub enum Facility {
132 Kernel = 0,
133 User = 1 << 3,
134 Mail = 2 << 3,
135 Daemon = 3 << 3,
136 Auth = 4 << 3,
137 Syslog = 5 << 3,
138 Lpr = 6 << 3,
139 News = 7 << 3,
140 Uucp = 8 << 3,
141 Local0 = 16 << 3,
142 Local1 = 17 << 3,
143 Local2 = 18 << 3,
144 Local3 = 19 << 3,
145 Local4 = 20 << 3,
146 Local5 = 21 << 3,
147 Local6 = 22 << 3,
148 Local7 = 23 << 3,
149 }
150
151 /// Errors returned by `syslog::init()`.
152 #[sorted]
153 #[derive(ThisError, Debug)]
154 pub enum Error {
155 /// Error while attempting to connect socket.
156 #[error("failed to connect socket: {0}")]
157 Connect(io::Error),
158 /// There was an error using `open` to get the lowest file descriptor.
159 #[error("failed to get lowest file descriptor: {0}")]
160 GetLowestFd(io::Error),
161 /// The guess of libc's file descriptor for the syslog connection was invalid.
162 #[error("guess of fd for syslog connection was invalid")]
163 InvalidFd,
164 /// Initialization was never attempted.
165 #[error("initialization was never attempted")]
166 NeverInitialized,
167 /// Initialization has previously failed and can not be retried.
168 #[error("initialization previously failed and cannot be retried")]
169 Poisoned,
170 /// Error while creating socket.
171 #[error("failed to create socket: {0}")]
172 Socket(io::Error),
173 }
174
175 /// Defines a log level override for a set of source files with the given path_prefix
176 #[derive(Debug)]
177 struct PathFilter {
178 path_prefix: PathBuf,
179 level: PriorityFilter,
180 }
181
get_proc_name() -> Option<String>182 fn get_proc_name() -> Option<String> {
183 env::args_os()
184 .next()
185 .map(PathBuf::from)
186 .and_then(|s| s.file_name().map(OsStr::to_os_string))
187 .map(OsString::into_string)
188 .and_then(Result::ok)
189 }
190
191 struct State {
192 stderr: bool,
193 file: Option<File>,
194 proc_name: Option<String>,
195 syslog: PlatformSyslog,
196 log_level: PriorityFilter, // This is the default global log level
197 path_log_levels: Vec<PathFilter>, // These are sorted with longest path prefixes first
198 }
199
200 impl State {
new() -> Result<State, Error>201 fn new() -> Result<State, Error> {
202 Ok(State {
203 stderr: true,
204 file: None,
205 proc_name: get_proc_name(),
206 syslog: PlatformSyslog::new()?,
207 log_level: PriorityFilter::Priority(Priority::Info),
208 path_log_levels: Vec::new(),
209 })
210 }
211 }
212
213 /// Set the log level filter.
214 ///
215 /// Does nothing if syslog was never initialized. Set log level filter with the given priority filter level.
set_log_level<T: Into<PriorityFilter>>(log_level: T)216 pub fn set_log_level<T: Into<PriorityFilter>>(log_level: T) {
217 let mut state = syslog_lock!();
218 state.log_level = log_level.into();
219 }
220
221 /// Adds a new per-path log level filter.
add_path_log_level<T: Into<PriorityFilter>>(path_prefix: &str, log_level: T)222 pub fn add_path_log_level<T: Into<PriorityFilter>>(path_prefix: &str, log_level: T) {
223 // Insert filter so that path_log_levels is always sorted with longer prefixes first
224 let mut state = syslog_lock!();
225 let index = state
226 .path_log_levels
227 .binary_search_by_key(&path_prefix.len(), |p| {
228 std::usize::MAX - p.path_prefix.as_os_str().len()
229 })
230 .unwrap_or_else(|e| e);
231 state.path_log_levels.insert(
232 index,
233 PathFilter {
234 path_prefix: PathBuf::from(path_prefix),
235 level: log_level.into(),
236 },
237 );
238 }
239
240 static STATE_ONCE: Once = Once::new();
241 static mut STATE: *const Mutex<State> = 0 as *const _;
242
new_mutex_ptr<T>(inner: T) -> *const Mutex<T>243 fn new_mutex_ptr<T>(inner: T) -> *const Mutex<T> {
244 Box::into_raw(Box::new(Mutex::new(inner)))
245 }
246
247 /// Initialize the syslog connection and internal variables.
248 ///
249 /// This should only be called once per process before any other threads have been spawned or any
250 /// signal handlers have been registered. Every call made after the first will have no effect
251 /// besides return `Ok` or `Err` appropriately.
init() -> Result<(), Error>252 pub fn init() -> Result<(), Error> {
253 let mut err = Error::Poisoned;
254 STATE_ONCE.call_once(|| match State::new() {
255 // Safe because STATE mutation is guarded by `Once`.
256 Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
257 Err(e) => err = e,
258 });
259
260 if unsafe { STATE.is_null() } {
261 Err(err)
262 } else {
263 Ok(())
264 }
265 }
266
lock() -> Result<MutexGuard<'static, State>, Error>267 fn lock() -> Result<MutexGuard<'static, State>, Error> {
268 // Safe because we assume that STATE is always in either a valid or NULL state.
269 let state_ptr = unsafe { STATE };
270 if state_ptr.is_null() {
271 return Err(Error::NeverInitialized);
272 }
273 // Safe because STATE only mutates once and we checked for NULL.
274 let state = unsafe { &*state_ptr };
275 let guard = state.lock();
276 Ok(guard)
277 }
278
279 // Attempts to lock and retrieve the state. Returns from the function silently on failure.
280 #[macro_export]
281 macro_rules! syslog_lock {
282 () => {
283 match lock() {
284 Ok(s) => s,
285 _ => return,
286 }
287 };
288 }
289
log_enabled(pri: Priority, file_path: &str) -> Option<bool>290 pub fn log_enabled(pri: Priority, file_path: &str) -> Option<bool> {
291 let log_level = match lock() {
292 Ok(state) => {
293 let parsed_path = Path::new(file_path);
294 // Since path_log_levels is sorted with longest prefixes first, this will yield the
295 // longest matching prefix if one exists.
296 state
297 .path_log_levels
298 .iter()
299 .find_map(|filter| {
300 if parsed_path.starts_with(filter.path_prefix.as_path()) {
301 Some(filter.level)
302 } else {
303 None
304 }
305 })
306 .unwrap_or(state.log_level)
307 }
308 _ => return None,
309 };
310 match log_level {
311 PriorityFilter::ShowAll => Some(true),
312 PriorityFilter::Silent => Some(false),
313 PriorityFilter::Priority(log_level) => Some((pri as u8) <= (log_level as u8)),
314 }
315 }
316
317 /// A macro for logging at an arbitrary priority level.
318 ///
319 /// Note that this will fail silently if syslog was not initialized.
320 #[macro_export]
321 macro_rules! log {
322 ($pri:expr, $($args:tt)+) => ({
323 if let Some(true) = $crate::platform::syslog::log_enabled($pri, file!()) {
324 $crate::platform::syslog::log($pri, $crate::platform::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
325 }
326 })
327 }
328
329 /// Replaces the process name reported in each syslog message.
330 ///
331 /// The default process name is the _file name_ of `argv[0]`. For example, if this program was
332 /// invoked as
333 ///
334 /// ```bash
335 /// $ path/to/app --delete everything
336 /// ```
337 ///
338 /// the default process name would be _app_.
339 ///
340 /// Does nothing if syslog was never initialized.
set_proc_name<T: Into<String>>(proc_name: T)341 pub fn set_proc_name<T: Into<String>>(proc_name: T) {
342 let mut state = syslog_lock!();
343 state.proc_name = Some(proc_name.into());
344 }
345
346 pub(crate) trait Syslog {
new() -> Result<Self, Error> where Self: Sized347 fn new() -> Result<Self, Error>
348 where
349 Self: Sized;
350
351 /// Enables or disables echoing log messages to the syslog.
352 ///
353 /// The default behavior is **enabled**.
354 ///
355 /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
356 /// reopened if `enable` is set to `true` after it became `false`.
357 ///
358 /// Returns an error if syslog was never initialized or the syslog connection failed to be
359 /// established.
360 ///
361 /// # Arguments
362 /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
enable(&mut self, enable: bool) -> Result<(), Error>363 fn enable(&mut self, enable: bool) -> Result<(), Error>;
364
log( &self, proc_name: Option<&str>, pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments, )365 fn log(
366 &self,
367 proc_name: Option<&str>,
368 pri: Priority,
369 fac: Facility,
370 file_line: Option<(&str, u32)>,
371 args: fmt::Arguments,
372 );
373
push_descriptors(&self, fds: &mut Vec<RawDescriptor>)374 fn push_descriptors(&self, fds: &mut Vec<RawDescriptor>);
375 }
376
377 /// Enables or disables echoing log messages to the syslog.
378 ///
379 /// The default behavior is **enabled**.
380 ///
381 /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
382 /// reopened if `enable` is set to `true` after it became `false`.
383 ///
384 /// Returns an error if syslog was never initialized or the syslog connection failed to be
385 /// established.
386 ///
387 /// # Arguments
388 /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
echo_syslog(enable: bool) -> Result<(), Error>389 pub fn echo_syslog(enable: bool) -> Result<(), Error> {
390 let state_ptr = unsafe { STATE };
391 if state_ptr.is_null() {
392 return Err(Error::NeverInitialized);
393 }
394 let mut state = lock().map_err(|_| Error::Poisoned)?;
395
396 state.syslog.enable(enable)
397 }
398
399 /// Replaces the optional `File` to echo log messages to.
400 ///
401 /// The default behavior is to not echo to a file. Passing `None` to this function restores that
402 /// behavior.
403 ///
404 /// Does nothing if syslog was never initialized.
405 ///
406 /// # Arguments
407 /// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
echo_file(file: Option<File>)408 pub fn echo_file(file: Option<File>) {
409 let mut state = syslog_lock!();
410 state.file = file;
411 }
412
413 /// Enables or disables echoing log messages to the `std::io::stderr()`.
414 ///
415 /// The default behavior is **enabled**.
416 ///
417 /// Does nothing if syslog was never initialized.
418 ///
419 /// # Arguments
420 /// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
echo_stderr(enable: bool)421 pub fn echo_stderr(enable: bool) {
422 let mut state = syslog_lock!();
423 state.stderr = enable;
424 }
425
426 /// Retrieves the file descriptors owned by the global syslogger.
427 ///
428 /// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
429 /// pushed into `fds`.
430 ///
431 /// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
push_descriptors(fds: &mut Vec<RawDescriptor>)432 pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
433 let state = syslog_lock!();
434 state.syslog.push_descriptors(fds);
435 fds.extend(state.file.iter().map(|f| f.as_raw_descriptor()));
436 }
437
438 /// Compatability function for callers using the old naming
push_fds(fds: &mut Vec<RawDescriptor>)439 pub fn push_fds(fds: &mut Vec<RawDescriptor>) {
440 push_descriptors(fds)
441 }
442
443 /// Records a log message with the given details.
444 ///
445 /// Note that this will fail silently if syslog was not initialized.
446 ///
447 /// # Arguments
448 /// * `pri` - The `Priority` (i.e. severity) of the log message.
449 /// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
450 /// * `file_line` - Optional tuple of the name of the file that generated the
451 /// log and the line number within that file.
452 /// * `args` - The log's message to record, in the form of `format_args!()` return value
453 ///
454 /// # Examples
455 ///
456 /// ```
457 /// # use crate::platform::syslog;
458 /// # if let Err(e) = syslog::init() {
459 /// # println!("failed to initiailize syslog: {}", e);
460 /// # return;
461 /// # }
462 /// syslog::log(syslog::Priority::Error,
463 /// syslog::Facility::User,
464 /// Some((file!(), line!())),
465 /// format_args!("hello syslog"));
466 /// ```
log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments)467 pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
468 let mut state = syslog_lock!();
469 let mut buf = [0u8; 2048];
470
471 state.syslog.log(
472 state.proc_name.as_ref().map(|s| s.as_ref()),
473 pri,
474 fac,
475 file_line,
476 args,
477 );
478
479 let res = {
480 let mut buf_cursor = Cursor::new(&mut buf[..]);
481 let now = chrono::Local::now()
482 .format(CHRONO_TIMESTAMP_FIXED_FMT!())
483 .to_string();
484 if let Some((file_name, line)) = &file_line {
485 write!(&mut buf_cursor, "[{}:{}:{}:{}] ", now, pri, file_name, line)
486 } else {
487 write!(&mut buf_cursor, "[{}]", now)
488 }
489 .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
490 .map(|()| buf_cursor.position() as usize)
491 };
492 if let Ok(len) = &res {
493 write_to_file(&buf, *len, &mut state);
494 } else if let Err(e) = &res {
495 // Don't use warn macro to avoid potential recursion issues after macro expansion.
496 let err_buf = [0u8; 1024];
497 let mut buf_cursor = Cursor::new(&mut buf[..]);
498 if writeln!(
499 &mut buf_cursor,
500 "[{}]:WARNING: Failed to log with err: {:?}",
501 chrono::Local::now().format(CHRONO_TIMESTAMP_FIXED_FMT!()),
502 e
503 )
504 .is_ok()
505 {
506 write_to_file(&err_buf, buf_cursor.position() as usize, &mut state);
507 }
508 }
509 }
510
write_to_file(buf: &[u8], len: usize, state: &mut State)511 fn write_to_file(buf: &[u8], len: usize, state: &mut State) {
512 if let Some(file) = state.file.as_mut() {
513 let _ = file.write_all(&buf[..len]);
514 }
515 if state.stderr {
516 let _ = stderr().write_all(&buf[..len]);
517 }
518 }
519
520 /// A macro for logging an error.
521 ///
522 /// Note that this will fail silently if syslog was not initialized.
523 #[macro_export]
524 macro_rules! error {
525 ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Error, $($args)*))
526 }
527
528 /// A macro for logging a warning.
529 ///
530 /// Note that this will fail silently if syslog was not initialized.
531 #[macro_export]
532 macro_rules! warn {
533 ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Warning, $($args)*))
534 }
535
536 /// A macro for logging info.
537 ///
538 /// Note that this will fail silently if syslog was not initialized.
539 #[macro_export]
540 macro_rules! info {
541 ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Info, $($args)*))
542 }
543
544 /// A macro for logging debug information.
545 ///
546 /// Note that this will fail silently if syslog was not initialized.
547 #[macro_export]
548 macro_rules! debug {
549 ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Debug, $($args)*))
550 }
551
552 // Struct that implements io::Write to be used for writing directly to the syslog
553 pub struct Syslogger {
554 buf: String,
555 priority: Priority,
556 facility: Facility,
557 }
558
559 impl Syslogger {
new(p: Priority, f: Facility) -> Syslogger560 pub fn new(p: Priority, f: Facility) -> Syslogger {
561 Syslogger {
562 buf: String::new(),
563 priority: p,
564 facility: f,
565 }
566 }
567 }
568
569 impl io::Write for Syslogger {
write(&mut self, buf: &[u8]) -> io::Result<usize>570 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
571 let parsed_str = String::from_utf8_lossy(buf);
572 self.buf.push_str(&parsed_str);
573
574 if let Some(last_newline_idx) = self.buf.rfind('\n') {
575 for line in self.buf[..last_newline_idx].lines() {
576 log(self.priority, self.facility, None, format_args!("{}", line));
577 }
578
579 self.buf.drain(..=last_newline_idx);
580 }
581
582 Ok(buf.len())
583 }
584
flush(&mut self) -> io::Result<()>585 fn flush(&mut self) -> io::Result<()> {
586 Ok(())
587 }
588 }
589
590 // TODO(b/223733375): Enable ignored flaky tests.
591 #[cfg(test)]
592 mod tests {
593 use super::*;
594
595 use super::super::{BlockingMode, FramingMode, StreamChannel};
596 use regex::Regex;
597 use std::{convert::TryInto, io::Read, os::windows::io::FromRawHandle};
598
599 #[test]
600 #[ignore]
init_syslog()601 fn init_syslog() {
602 init().unwrap();
603 }
604
605 #[test]
606 #[ignore]
syslog_log()607 fn syslog_log() {
608 init().unwrap();
609 log(
610 Priority::Error,
611 Facility::User,
612 Some((file!(), line!())),
613 format_args!("hello syslog"),
614 );
615 }
616
617 #[test]
618 #[ignore]
proc_name()619 fn proc_name() {
620 init().unwrap();
621 log(
622 Priority::Error,
623 Facility::User,
624 Some((file!(), line!())),
625 format_args!("before proc name"),
626 );
627 set_proc_name("win_sys_util-test");
628 log(
629 Priority::Error,
630 Facility::User,
631 Some((file!(), line!())),
632 format_args!("after proc name"),
633 );
634 }
635
636 #[test]
637 #[ignore]
macros()638 fn macros() {
639 init().unwrap();
640 error!("this is an error {}", 3);
641 warn!("this is a warning {}", "uh oh");
642 info!("this is info {}", true);
643 debug!("this is debug info {:?}", Some("helpful stuff"));
644 }
645
646 #[test]
647 #[ignore]
syslogger_char()648 fn syslogger_char() {
649 init().unwrap();
650 let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
651
652 let string = "Writing chars to syslog";
653 for c in string.chars() {
654 syslogger.write_all(&[c as u8]).expect("error writing char");
655 }
656
657 syslogger
658 .write_all(&[b'\n'])
659 .expect("error writing newline char");
660 }
661
662 #[test]
663 #[ignore]
syslogger_line()664 fn syslogger_line() {
665 init().unwrap();
666 let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
667
668 let s = "Writing string to syslog\n";
669 syslogger
670 .write_all(s.as_bytes())
671 .expect("error writing string");
672 }
673
674 #[test]
675 #[ignore]
syslogger_partial()676 fn syslogger_partial() {
677 init().unwrap();
678 let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
679
680 let s = "Writing partial string";
681 // Should not log because there is no newline character
682 syslogger
683 .write_all(s.as_bytes())
684 .expect("error writing string");
685 }
686
clear_path_log_levels()687 fn clear_path_log_levels() {
688 syslog_lock!().path_log_levels.clear();
689 }
690
691 #[test]
692 #[ignore]
syslogger_verify_line_written()693 fn syslogger_verify_line_written() {
694 init().unwrap();
695 clear_path_log_levels();
696 let (mut reader, writer) =
697 StreamChannel::pair(BlockingMode::Blocking, FramingMode::Byte).unwrap();
698
699 // Safe because writer is guaranteed to exist, and we forget the StreamChannel that used
700 // to own the StreamChannel.
701 let writer_file = unsafe { File::from_raw_handle(writer.as_raw_descriptor()) };
702 std::mem::forget(writer);
703
704 echo_file(Some(writer_file));
705 error!("Test message.");
706
707 // Ensure the message we wrote actually was written to the supplied "file" (which is
708 // really a named pipe here).
709 let mut buf: [u8; 1024] = [0; 1024];
710 let bytes_read = reader.read(&mut buf).unwrap();
711 assert!(bytes_read > 0);
712 let log_msg = String::from_utf8(buf.to_vec()).unwrap();
713 let re = Regex::new(r"^\[.+:ERROR:.+:[0-9]+\] Test message.").unwrap();
714 assert!(re.is_match(&log_msg));
715 }
716
717 #[test]
718 #[ignore]
log_priority_try_from_number()719 fn log_priority_try_from_number() {
720 assert_eq!("0".try_into(), Ok(Priority::Emergency));
721 assert!(Priority::try_from("100").is_err());
722 }
723
724 #[test]
725 #[ignore]
log_priority_try_from_words()726 fn log_priority_try_from_words() {
727 assert_eq!("EMERGENCY".try_into(), Ok(Priority::Emergency));
728 assert!(Priority::try_from("_EMERGENCY").is_err());
729 }
730
731 #[test]
732 #[ignore]
log_priority_filter_try_from_star()733 fn log_priority_filter_try_from_star() {
734 assert_eq!("*".try_into(), Ok(PriorityFilter::ShowAll));
735 }
736
737 #[test]
738 #[ignore]
log_priority_filter_try_from_silence()739 fn log_priority_filter_try_from_silence() {
740 assert_eq!("S".try_into(), Ok(PriorityFilter::Silent));
741 assert_eq!("s".try_into(), Ok(PriorityFilter::Silent));
742 assert_eq!("SILENT".try_into(), Ok(PriorityFilter::Silent));
743 }
744
745 #[test]
746 #[ignore]
log_priority_filter_try_from_priority_str()747 fn log_priority_filter_try_from_priority_str() {
748 assert_eq!(
749 "DEBUG".try_into(),
750 Ok(PriorityFilter::Priority(Priority::Debug))
751 );
752 assert_eq!(
753 "debug".try_into(),
754 Ok(PriorityFilter::Priority(Priority::Debug))
755 );
756 assert!(PriorityFilter::try_from("_DEBUG").is_err());
757 }
758
759 #[test]
760 #[ignore]
log_should_always_be_enabled_for_level_show_all()761 fn log_should_always_be_enabled_for_level_show_all() {
762 init().unwrap();
763 clear_path_log_levels();
764 set_log_level(PriorityFilter::ShowAll);
765 assert!(log_enabled(Priority::Debug, "").unwrap());
766 }
767
768 #[test]
769 #[ignore]
log_should_always_be_disabled_for_level_silent()770 fn log_should_always_be_disabled_for_level_silent() {
771 init().unwrap();
772 clear_path_log_levels();
773 set_log_level(PriorityFilter::Silent);
774 let enabled = log_enabled(Priority::Emergency, "");
775 set_log_level(PriorityFilter::ShowAll);
776 assert!(!enabled.unwrap());
777 }
778
779 #[test]
780 #[ignore]
log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority()781 fn log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority() {
782 init().unwrap();
783 clear_path_log_levels();
784 set_log_level(Priority::Info);
785 let info_enabled = log_enabled(Priority::Info, "");
786 let warn_enabled = log_enabled(Priority::Warning, "");
787 set_log_level(PriorityFilter::ShowAll);
788 assert!(info_enabled.unwrap());
789 assert!(warn_enabled.unwrap());
790 }
791
792 #[test]
793 #[ignore]
log_should_be_disabled_if_filter_level_has_a_higher_priority()794 fn log_should_be_disabled_if_filter_level_has_a_higher_priority() {
795 init().unwrap();
796 clear_path_log_levels();
797 set_log_level(Priority::Info);
798 let enabled = log_enabled(Priority::Debug, "");
799 set_log_level(PriorityFilter::ShowAll);
800 assert!(!enabled.unwrap());
801 }
802
803 #[test]
804 #[ignore]
path_overides_should_apply_to_logs()805 fn path_overides_should_apply_to_logs() {
806 init().unwrap();
807 clear_path_log_levels();
808 set_log_level(Priority::Info);
809 add_path_log_level("fake/debug/src", Priority::Debug);
810
811 assert!(!log_enabled(Priority::Debug, "test.rs").unwrap());
812 assert!(log_enabled(Priority::Debug, "fake/debug/src/test.rs").unwrap());
813 }
814
815 #[test]
816 #[ignore]
longest_path_prefix_match_should_apply_if_multiple_filters_match()817 fn longest_path_prefix_match_should_apply_if_multiple_filters_match() {
818 init().unwrap();
819 clear_path_log_levels();
820 set_log_level(Priority::Info);
821 add_path_log_level("fake/debug/src", Priority::Debug);
822 add_path_log_level("fake/debug/src/silence", PriorityFilter::Silent);
823
824 assert!(!log_enabled(Priority::Info, "fake/debug/src/silence/test.rs").unwrap());
825 }
826
827 #[test]
828 #[ignore]
syslogger_log_level_filter()829 fn syslogger_log_level_filter() {
830 init().unwrap();
831 clear_path_log_levels();
832 set_log_level(Priority::Error);
833
834 let (mut reader, writer) =
835 StreamChannel::pair(BlockingMode::Blocking, FramingMode::Byte).unwrap();
836
837 // Safe because writer is guaranteed to exist, and we forget the StreamChannel that used
838 // to own the StreamChannel.
839 let writer_file = unsafe { File::from_raw_handle(writer.as_raw_descriptor()) };
840 std::mem::forget(writer);
841
842 echo_file(Some(writer_file));
843 error!("Test message.");
844 debug!("Test message.");
845
846 add_path_log_level(file!(), Priority::Debug);
847 debug!("Test with file filter.");
848
849 set_log_level(PriorityFilter::ShowAll);
850
851 // Ensure the message we wrote actually was written to the supplied "file" (which is
852 // really a named pipe here).
853 let mut buf: [u8; 1024] = [0; 1024];
854 let bytes_read = reader.read(&mut buf).unwrap();
855 assert!(bytes_read > 0);
856 let log_msg = String::from_utf8(buf.to_vec()).unwrap();
857 let re_error = Regex::new(r"(?m)^\[.*ERROR:.+:[0-9]+\] Test message.").unwrap();
858 let re_debug = Regex::new(r"(?m)^\[.*DEBUG:.+:[0-9]+\] Test message.").unwrap();
859 let filter_debug =
860 Regex::new(r"(?m)^\[.*DEBUG:.+:[0-9]+\] Test with file filter.").unwrap();
861 assert!(re_error.is_match(&log_msg));
862 assert!(!re_debug.is_match(&log_msg));
863 assert!(filter_debug.is_match(&log_msg));
864 }
865 }
866