• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The ChromiumOS Authors
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 //! This implements and sets logger up for logging facade exposed by the [`log`
11 //! crate][log-crate-url].
12 //!
13 //! # Examples
14 //!
15 //! ```
16 //! use log::{error, warn};
17 //! use base::syslog;
18 //!
19 //! if let Err(e) = syslog::init() {
20 //!     println!("failed to initiailize syslog: {}", e);
21 //!     return;
22 //! }
23 //! warn!("this is your {} warning", "final");
24 //! error!("something went horribly wrong: {}", "out of RAMs");
25 //! ```
26 //!
27 //! ```
28 //! use log::{error, warn};
29 //! use base::syslog::{init_with, LogConfig, fmt};
30 //! use std::io::Write;
31 //!
32 //! let mut cfg = LogConfig::default();
33 //! cfg.pipe_formatter = Some(|buf, rec| {
34 //!     let mut level_style = buf.style();
35 //!     level_style.set_color(fmt::Color::Green);
36 //!     let mut style = buf.style();
37 //!     style.set_color(fmt::Color::Red).set_bold(true);
38 //!     writeln!(buf, "{}:{}", level_style.value(rec.level()), style.value(rec.args()))
39 //! });
40 //! cfg.stderr = true;
41 //! cfg.filter = "info,base=debug,base::syslog=error,serial_console=false";
42 //!
43 //! init_with(cfg).unwrap();
44 //! error!("something went horribly wrong: {}", "out of RAMs");
45 //!
46 //!
47 //! ```
48 //!
49 //!
50 //! [log-crate-url]: https://docs.rs/log/
51 
52 use std::fmt::Display;
53 use std::io;
54 use std::io::Write;
55 use std::sync::MutexGuard;
56 
57 use chrono::Local;
58 pub use env_logger::fmt;
59 pub use env_logger::{self};
60 pub use log::*;
61 use once_cell::sync::Lazy;
62 use once_cell::sync::OnceCell;
63 use remain::sorted;
64 use serde::Deserialize;
65 use serde::Serialize;
66 use sync::Mutex;
67 use thiserror::Error as ThisError;
68 
69 use crate::descriptor::AsRawDescriptor;
70 use crate::platform::syslog::PlatformSyslog;
71 use crate::platform::RawDescriptor;
72 
73 /// The priority (i.e. severity) of a syslog message.
74 ///
75 /// See syslog man pages for information on their semantics.
76 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
77 pub(crate) enum Priority {
78     Emergency = 0,
79     Alert = 1,
80     Critical = 2,
81     Error = 3,
82     Warning = 4,
83     Notice = 5,
84     Info = 6,
85     Debug = 7,
86 }
87 
88 impl Display for Priority {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result89     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
90         use self::Priority::*;
91 
92         let string = match self {
93             Emergency => "EMERGENCY",
94             Alert => "ALERT",
95             Critical => "CRITICAL",
96             Error => "ERROR",
97             Warning => "WARNING",
98             Notice => "NOTICE",
99             Info => "INFO",
100             Debug => "DEBUG",
101         };
102 
103         write!(f, "{}", string)
104     }
105 }
106 
107 impl From<log::Level> for Priority {
from(level: log::Level) -> Self108     fn from(level: log::Level) -> Self {
109         match level {
110             log::Level::Error => Priority::Error,
111             log::Level::Warn => Priority::Warning,
112             log::Level::Info => Priority::Info,
113             log::Level::Debug => Priority::Debug,
114             log::Level::Trace => Priority::Debug,
115         }
116     }
117 }
118 
119 pub const FORMATTER_NONE: Option<fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()>> =
120     None;
121 
122 impl TryFrom<&str> for Priority {
123     type Error = &'static str;
124 
try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error>125     fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
126         match value {
127             "0" | "EMERGENCY" => Ok(Priority::Emergency),
128             "1" | "ALERT" => Ok(Priority::Alert),
129             "2" | "CRITICAL" => Ok(Priority::Critical),
130             "3" | "ERROR" => Ok(Priority::Error),
131             "4" | "WARNING" => Ok(Priority::Warning),
132             "5" | "NOTICE" => Ok(Priority::Notice),
133             "6" | "INFO" => Ok(Priority::Info),
134             "7" | "DEBUG" => Ok(Priority::Debug),
135             _ => Err("Priority can only be parsed from 0-7 and given variant names"),
136         }
137     }
138 }
139 /// The facility of a syslog message.
140 ///
141 /// See syslog man pages for information on their semantics.
142 #[derive(Copy, Clone)]
143 pub enum Facility {
144     Kernel = 0,
145     User = 1 << 3,
146     Mail = 2 << 3,
147     Daemon = 3 << 3,
148     Auth = 4 << 3,
149     Syslog = 5 << 3,
150     Lpr = 6 << 3,
151     News = 7 << 3,
152     Uucp = 8 << 3,
153     Local0 = 16 << 3,
154     Local1 = 17 << 3,
155     Local2 = 18 << 3,
156     Local3 = 19 << 3,
157     Local4 = 20 << 3,
158     Local5 = 21 << 3,
159     Local6 = 22 << 3,
160     Local7 = 23 << 3,
161 }
162 
163 /// Errors returned by `syslog::init()`.
164 #[sorted]
165 #[derive(ThisError, Debug)]
166 pub enum Error {
167     /// Error while attempting to connect socket.
168     #[error("failed to connect socket: {0}")]
169     Connect(io::Error),
170     /// There was an error using `open` to get the lowest file descriptor.
171     #[error("failed to get lowest file descriptor: {0}")]
172     GetLowestFd(io::Error),
173     /// The guess of libc's file descriptor for the syslog connection was invalid.
174     #[error("guess of fd for syslog connection was invalid")]
175     InvalidFd,
176     /// Initialization was never attempted.
177     #[error("initialization was never attempted")]
178     NeverInitialized,
179     /// Initialization has previously failed and can not be retried.
180     #[error("initialization previously failed and cannot be retried")]
181     Poisoned,
182     /// Error while creating socket.
183     #[error("failed to create socket: {0}")]
184     Socket(io::Error),
185 }
186 
187 pub(crate) trait Syslog {
new( proc_name: String, facility: Facility, ) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), Error>188     fn new(
189         proc_name: String,
190         facility: Facility,
191     ) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), Error>;
192 }
193 
194 pub struct State {
195     /// Record filter
196     filter: env_logger::filter::Filter,
197     /// All the loggers we have
198     loggers: Vec<Box<dyn Log + Send>>,
199     /// Raw Descriptors to preserve
200     descriptors: Vec<RawDescriptor>,
201     /// True if we have just been initialized with safe startup defaults (stderr logging), false
202     /// after detailed initialization has occurred.
203     early_init: bool,
204 }
205 
206 /// The logger that is provided to the `log` crate. Wraps our State struct so that we can
207 /// reconfigure logging sinks on the fly.
208 struct LoggingFacade {}
209 
210 impl Log for LoggingFacade {
enabled(&self, metadata: &log::Metadata) -> bool211     fn enabled(&self, metadata: &log::Metadata) -> bool {
212         STATE.lock().enabled(metadata)
213     }
214 
log(&self, record: &log::Record)215     fn log(&self, record: &log::Record) {
216         STATE.lock().log(record)
217     }
218 
flush(&self)219     fn flush(&self) {
220         STATE.lock().flush()
221     }
222 }
223 
224 pub struct LogConfig<'a, F: 'static>
225 where
226     F: Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,
227 {
228     /// A filter for log messages. Please see
229     /// module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
230     ///
231     /// Example: `off`, `trace`, `trace,crosvm=error,base::syslog=debug`
232     pub filter: &'a str,
233     /// If set to true will duplicate output to stderr
234     pub stderr: bool,
235     /// If specified will output to given Sink
236     pub pipe: Option<Box<dyn io::Write + Send>>,
237     /// descriptor to preserve on forks (intended to be used with pipe)
238     pub pipe_fd: Option<RawDescriptor>,
239     /// A formatter to use with the pipe. (Syslog has hardcoded format)
240     /// see module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
241     pub pipe_formatter: Option<F>,
242     /// TAG to use for syslog output
243     pub proc_name: String,
244     /// Enable/disable platform's "syslog"
245     pub syslog: bool,
246     /// Facility to use for syslog output
247     pub syslog_facility: Facility,
248 }
249 
250 impl<'a> Default
251     for LogConfig<'a, fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()>>
252 {
default() -> Self253     fn default() -> Self {
254         Self {
255             filter: "info",
256             stderr: true,
257             pipe: None,
258             proc_name: String::from("crosvm"),
259             syslog: true,
260             syslog_facility: Facility::User,
261             pipe_formatter: FORMATTER_NONE,
262             pipe_fd: None,
263         }
264     }
265 }
266 
267 impl State {
new<F: 'static>(cfg: LogConfig<'_, F>) -> Result<Self, Error> where F: Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,268     pub fn new<F: 'static>(cfg: LogConfig<'_, F>) -> Result<Self, Error>
269     where
270         F: Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,
271     {
272         let mut loggers: Vec<Box<dyn Log + Send>> = vec![];
273         let mut descriptors = vec![];
274         let mut builder = env_logger::filter::Builder::new();
275         builder.parse(cfg.filter);
276         let filter = builder.build();
277 
278         let create_formatted_builder = || {
279             let mut builder = env_logger::Builder::new();
280 
281             // Output log lines w/ local ISO 8601 timestamps.
282             builder.format(|buf, record| {
283                 writeln!(
284                     buf,
285                     "[{} {:5} {}] {}",
286                     Local::now().format("%Y-%m-%dT%H:%M:%S%.9f%:z"),
287                     record.level(),
288                     record.module_path().unwrap_or("<missing module path>"),
289                     record.args()
290                 )
291             });
292             builder
293         };
294 
295         if cfg.stderr {
296             let mut builder = create_formatted_builder();
297             builder.filter_level(log::LevelFilter::Trace);
298             builder.target(env_logger::Target::Stderr);
299             loggers.push(Box::new(builder.build()));
300             descriptors.push(std::io::stderr().as_raw_descriptor());
301         }
302 
303         if let Some(fd) = cfg.pipe_fd {
304             descriptors.push(fd);
305         }
306 
307         if let Some(file) = cfg.pipe {
308             let mut builder = create_formatted_builder();
309             builder.filter_level(log::LevelFilter::Trace);
310             builder.target(env_logger::Target::Pipe(Box::new(file)));
311             // https://github.com/env-logger-rs/env_logger/issues/208
312             builder.is_test(true);
313 
314             if let Some(format) = cfg.pipe_formatter {
315                 builder.format(format);
316             }
317             loggers.push(Box::new(builder.build()));
318         }
319 
320         if cfg.syslog {
321             match PlatformSyslog::new(cfg.proc_name, cfg.syslog_facility) {
322                 Ok((mut logger, fd)) => {
323                     if let Some(fd) = fd {
324                         descriptors.push(fd);
325                     }
326                     if let Some(logger) = logger.take() {
327                         loggers.push(logger);
328                     }
329                 }
330                 Err(e) => {
331                     // The default log configuration used in early_init() enables syslog, so we
332                     // don't want to terminate the program if syslog can't be initialized. Warn the
333                     // user but continue running.
334                     eprintln!("syslog init failed: {}", e);
335                 }
336             }
337         }
338 
339         Ok(State {
340             filter,
341             loggers,
342             descriptors,
343             early_init: false,
344         })
345     }
346 }
347 
348 static STATE: Lazy<Mutex<State>> = Lazy::new(|| {
349     let mut state = State::new(LogConfig::default()).expect("failed to configure minimal logging");
350     state.early_init = true;
351     Mutex::new(state)
352 });
353 static LOGGING_FACADE: LoggingFacade = LoggingFacade {};
354 static EARLY_INIT_CALLED: OnceCell<()> = OnceCell::new();
355 
356 /// Initialize the syslog connection and internal variables.
357 ///
358 /// This should only be called once per process before any other threads have been spawned or any
359 /// signal handlers have been registered. Every call made after the first will panic.
360 ///
361 /// Use `init_with_filter` to initialize with filtering
init() -> Result<(), Error>362 pub fn init() -> Result<(), Error> {
363     init_with(Default::default())
364 }
365 
366 /// Initialize the syslog connection and internal variables.
367 ///
368 /// This should only be called once per process before any other threads have been spawned or any
369 /// signal handlers have been registered. Every call made after the first will
370 /// panic.
371 ///
372 /// Arguments:
373 /// * filter: See <https://docs.rs/env_logger/0.9/env_logger/index.html> for example filter
374 ///     specifications
375 /// * stderr: If set will output to stderr (in addition)
376 /// * file:  If set will output to this file (in addition)
377 /// * proc_name: proc name for Syslog implementation
378 /// * syslog_facility: syslog facility
379 /// * file_formatter: custom formatter for file output. See env_logger docs
init_with<F: 'static>(cfg: LogConfig<'_, F>) -> Result<(), Error> where F: Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,380 pub fn init_with<F: 'static>(cfg: LogConfig<'_, F>) -> Result<(), Error>
381 where
382     F: Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,
383 {
384     let mut state = STATE.lock();
385     if !state.early_init {
386         panic!("double-init of the logging system is not permitted.");
387     }
388     *state = State::new(cfg)?;
389 
390     // This has no effect if the logging facade was already set.
391     apply_logging_state(&LOGGING_FACADE);
392 
393     Ok(())
394 }
395 
396 /// Performs early (as in, moment of process start) logging initialization. Any logging prior to
397 /// this call will be SILENTLY discarded. Calling more than once per process will panic.
early_init()398 pub fn early_init() {
399     let mut first_init = false;
400     let _ = EARLY_INIT_CALLED
401         .get_or_try_init(|| -> Result<(), ()> {
402             first_init = true;
403             Ok(())
404         })
405         .unwrap();
406     if first_init {
407         apply_logging_state(&LOGGING_FACADE);
408     } else {
409         panic!("double early init of the logging system is not permitted.");
410     }
411 }
412 
413 /// Test only function that ensures logging has been configured. Since tests
414 /// share module state, we need a way to make sure it has been initialized
415 /// with *some* configuration.
416 #[cfg(test)]
ensure_inited() -> Result<(), Error>417 pub(crate) fn ensure_inited() -> Result<(), Error> {
418     let mut first_init = false;
419     let _ = EARLY_INIT_CALLED
420         .get_or_try_init(|| -> Result<(), ()> {
421             first_init = true;
422             Ok(())
423         })
424         .unwrap();
425     if first_init {
426         apply_logging_state(&LOGGING_FACADE);
427     }
428     Ok(())
429 }
430 
apply_logging_state(facade: &'static LoggingFacade)431 fn apply_logging_state(facade: &'static LoggingFacade) {
432     let _ = log::set_logger(facade);
433     log::set_max_level(log::LevelFilter::Trace);
434 }
435 
436 /// Retrieves the file descriptors owned by the global syslogger.
437 ///
438 /// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
439 /// pushed into `fds`.
440 ///
441 /// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
push_descriptors(fds: &mut Vec<RawDescriptor>)442 pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
443     let state = STATE.lock();
444     fds.extend(state.descriptors.iter());
445 }
446 
447 impl Log for State {
enabled(&self, metadata: &log::Metadata) -> bool448     fn enabled(&self, metadata: &log::Metadata) -> bool {
449         self.filter.enabled(metadata)
450     }
451 
log(&self, record: &log::Record)452     fn log(&self, record: &log::Record) {
453         if self.filter.matches(record) {
454             for logger in self.loggers.iter() {
455                 logger.log(record)
456             }
457         }
458     }
459 
flush(&self)460     fn flush(&self) {
461         for logger in self.loggers.iter() {
462             logger.flush()
463         }
464     }
465 }
466 
467 // Struct that implements io::Write to be used for writing directly to the syslog
468 pub struct Syslogger<'a> {
469     buf: String,
470     level: log::Level,
471     get_state_fn: Box<dyn Fn() -> MutexGuard<'a, State> + Send + 'a>,
472 }
473 
474 impl<'a> Syslogger<'a> {
new(level: log::Level) -> Syslogger<'a>475     pub fn new(level: log::Level) -> Syslogger<'a> {
476         Syslogger {
477             buf: String::new(),
478             level,
479             get_state_fn: Box::new(|| STATE.lock()),
480         }
481     }
482     #[cfg(test)]
from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>( level: log::Level, get_state_fn: F, ) -> Syslogger<'a>483     fn from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>(
484         level: log::Level,
485         get_state_fn: F,
486     ) -> Syslogger<'a> {
487         Syslogger {
488             buf: String::new(),
489             level,
490             get_state_fn: Box::new(get_state_fn),
491         }
492     }
493 }
494 
495 impl<'a> io::Write for Syslogger<'a> {
write(&mut self, buf: &[u8]) -> io::Result<usize>496     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
497         let state = (self.get_state_fn)();
498         let parsed_str = String::from_utf8_lossy(buf);
499         self.buf.push_str(&parsed_str);
500 
501         if let Some(last_newline_idx) = self.buf.rfind('\n') {
502             for line in self.buf[..last_newline_idx].lines() {
503                 // Match is to explicitly limit lifetime of args
504                 // https://github.com/rust-lang/rust/issues/92698
505                 // https://github.com/rust-lang/rust/issues/15023
506                 #[allow(clippy::match_single_binding)]
507                 match format_args!("{}", line) {
508                     args => {
509                         let mut record_builder = log::Record::builder();
510                         record_builder.level(self.level);
511                         record_builder.target("syslogger");
512                         record_builder.args(args);
513                         let record = record_builder.build();
514                         state.log(&record);
515                     }
516                 }
517             }
518 
519             self.buf.drain(..=last_newline_idx);
520         }
521         Ok(buf.len())
522     }
523 
flush(&mut self) -> io::Result<()>524     fn flush(&mut self) -> io::Result<()> {
525         STATE.lock().flush();
526         Ok(())
527     }
528 }
529 
530 #[cfg(test)]
531 mod tests {
532     #![allow(clippy::field_reassign_with_default)]
533     use std::io::Write;
534 
535     use super::*;
536 
537     impl Default for State {
default() -> Self538         fn default() -> Self {
539             Self::new(Default::default()).unwrap()
540         }
541     }
542 
543     use std::sync::Arc;
544     #[derive(Clone)]
545     struct MockWrite {
546         buffer: Arc<Mutex<Vec<u8>>>,
547     }
548 
549     impl MockWrite {
new() -> Self550         fn new() -> Self {
551             Self {
552                 buffer: Arc::new(Mutex::new(vec![])),
553             }
554         }
555 
into_inner(self) -> Vec<u8>556         fn into_inner(self) -> Vec<u8> {
557             Arc::try_unwrap(self.buffer).unwrap().into_inner()
558         }
559     }
560 
561     impl Write for MockWrite {
write(&mut self, buf: &[u8]) -> io::Result<usize>562         fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
563             self.buffer.lock().write(buf)
564         }
565 
flush(&mut self) -> io::Result<()>566         fn flush(&mut self) -> io::Result<()> {
567             Ok(())
568         }
569     }
570 
571     #[test]
syslog_log()572     fn syslog_log() {
573         let state = State::default();
574         state.log(
575             &log::RecordBuilder::new()
576                 .level(Level::Error)
577                 .file(Some(file!()))
578                 .line(Some(line!()))
579                 .args(format_args!("hello syslog"))
580                 .build(),
581         );
582     }
583 
584     #[test]
proc_name()585     fn proc_name() {
586         let state = State::new(LogConfig {
587             proc_name: String::from("syslog-test"),
588             ..Default::default()
589         })
590         .unwrap();
591         state.log(
592             &log::RecordBuilder::new()
593                 .level(Level::Error)
594                 .file(Some(file!()))
595                 .line(Some(line!()))
596                 .args(format_args!("hello syslog"))
597                 .build(),
598         );
599     }
600 
601     #[test]
macros()602     fn macros() {
603         ensure_inited().unwrap();
604         log::error!("this is an error {}", 3);
605         log::warn!("this is a warning {}", "uh oh");
606         log::info!("this is info {}", true);
607         log::debug!("this is debug info {:?}", Some("helpful stuff"));
608     }
609 
pipe_formatter(buf: &mut fmt::Formatter, record: &Record<'_>) -> io::Result<()>610     fn pipe_formatter(buf: &mut fmt::Formatter, record: &Record<'_>) -> io::Result<()> {
611         writeln!(buf, "{}", record.args())
612     }
613 
614     #[test]
syslogger_char()615     fn syslogger_char() {
616         let output = MockWrite::new();
617         let mut cfg = LogConfig::default();
618         cfg.pipe_formatter = Some(pipe_formatter);
619         cfg.pipe = Some(Box::new(output.clone()));
620         let state = Mutex::new(State::new(cfg).unwrap());
621 
622         let mut syslogger = Syslogger::from_state(Level::Info, || state.lock());
623 
624         let string = "chars";
625         for c in string.chars() {
626             syslogger.write_all(&[c as u8]).expect("error writing char");
627         }
628 
629         syslogger
630             .write_all(&[b'\n'])
631             .expect("error writing newline char");
632 
633         std::mem::drop(syslogger);
634         std::mem::drop(state);
635         assert_eq!(
636             format!("{}\n", string),
637             String::from_utf8_lossy(&output.into_inner()[..])
638         );
639     }
640 
641     #[test]
syslogger_line()642     fn syslogger_line() {
643         let output = MockWrite::new();
644         let mut cfg = LogConfig::default();
645         cfg.pipe_formatter = Some(pipe_formatter);
646         cfg.pipe = Some(Box::new(output.clone()));
647         let state = Mutex::new(State::new(cfg).unwrap());
648 
649         let mut syslogger = Syslogger::from_state(Level::Info, || state.lock());
650 
651         let s = "Writing string to syslog\n";
652         syslogger
653             .write_all(s.as_bytes())
654             .expect("error writing string");
655 
656         std::mem::drop(syslogger);
657         std::mem::drop(state);
658         assert_eq!(s, String::from_utf8_lossy(&output.into_inner()[..]));
659     }
660 
661     #[test]
syslogger_partial()662     fn syslogger_partial() {
663         let output = MockWrite::new();
664         let state = Mutex::new(
665             State::new(LogConfig {
666                 pipe: Some(Box::new(output.clone())),
667                 ..Default::default()
668             })
669             .unwrap(),
670         );
671 
672         let mut syslogger = Syslogger::from_state(Level::Info, || state.lock());
673 
674         let s = "Writing partial string";
675         // Should not log because there is no newline character
676         syslogger
677             .write_all(s.as_bytes())
678             .expect("error writing string");
679 
680         std::mem::drop(syslogger);
681         std::mem::drop(state);
682         assert_eq!(Vec::<u8>::new(), output.into_inner());
683     }
684 
685     #[test]
log_priority_try_from_number()686     fn log_priority_try_from_number() {
687         assert_eq!("0".try_into(), Ok(Priority::Emergency));
688         assert!(Priority::try_from("100").is_err());
689     }
690 
691     #[test]
log_priority_try_from_words()692     fn log_priority_try_from_words() {
693         assert_eq!("EMERGENCY".try_into(), Ok(Priority::Emergency));
694         assert!(Priority::try_from("_EMERGENCY").is_err());
695     }
696 
697     #[test]
log_should_always_be_enabled_for_level_show_all()698     fn log_should_always_be_enabled_for_level_show_all() {
699         let state = State::new(LogConfig {
700             filter: "trace",
701             ..Default::default()
702         })
703         .unwrap();
704 
705         assert!(state.enabled(
706             log::RecordBuilder::new()
707                 .level(Level::Debug)
708                 .build()
709                 .metadata(),
710         ));
711     }
712 
713     #[test]
log_should_always_be_disabled_for_level_silent()714     fn log_should_always_be_disabled_for_level_silent() {
715         let state = State::new(LogConfig {
716             filter: "off",
717             ..Default::default()
718         })
719         .unwrap();
720 
721         assert!(!state.enabled(
722             log::RecordBuilder::new()
723                 .level(Level::Debug)
724                 .build()
725                 .metadata(),
726         ));
727     }
728 
729     #[test]
log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority()730     fn log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority() {
731         let state = State::new(LogConfig {
732             filter: "info",
733             ..Default::default()
734         })
735         .unwrap();
736 
737         assert!(state.enabled(
738             log::RecordBuilder::new()
739                 .level(Level::Info)
740                 .build()
741                 .metadata(),
742         ));
743         assert!(state.enabled(
744             log::RecordBuilder::new()
745                 .level(Level::Warn)
746                 .build()
747                 .metadata(),
748         ));
749     }
750 
751     #[test]
log_should_be_disabled_if_filter_level_has_a_higher_priority()752     fn log_should_be_disabled_if_filter_level_has_a_higher_priority() {
753         let state = State::new(LogConfig {
754             filter: "info",
755             ..Default::default()
756         })
757         .unwrap();
758 
759         assert!(!state.enabled(
760             log::RecordBuilder::new()
761                 .level(Level::Debug)
762                 .build()
763                 .metadata(),
764         ));
765     }
766 
767     #[test]
path_overides_should_apply_to_logs()768     fn path_overides_should_apply_to_logs() {
769         let state = State::new(LogConfig {
770             filter: "info,test=debug",
771             ..Default::default()
772         })
773         .unwrap();
774 
775         assert!(!state.enabled(
776             log::RecordBuilder::new()
777                 .level(Level::Debug)
778                 .build()
779                 .metadata(),
780         ));
781         assert!(state.enabled(
782             log::RecordBuilder::new()
783                 .level(Level::Debug)
784                 .target("test")
785                 .build()
786                 .metadata(),
787         ));
788     }
789 
790     #[test]
longest_path_prefix_match_should_apply_if_multiple_filters_match()791     fn longest_path_prefix_match_should_apply_if_multiple_filters_match() {
792         let state = State::new(LogConfig {
793             filter: "info,test=debug,test::silence=off",
794             ..Default::default()
795         })
796         .unwrap();
797 
798         assert!(!state.enabled(
799             log::RecordBuilder::new()
800                 .level(Level::Debug)
801                 .build()
802                 .metadata(),
803         ));
804 
805         assert!(state.enabled(
806             log::RecordBuilder::new()
807                 .level(Level::Debug)
808                 .target("test")
809                 .build()
810                 .metadata(),
811         ));
812         assert!(!state.enabled(
813             log::RecordBuilder::new()
814                 .level(Level::Error)
815                 .target("test::silence")
816                 .build()
817                 .metadata(),
818         ));
819     }
820 }
821