Skip to main content

clap_logflag/
config.rs

1use std::path::PathBuf;
2
3/// This enum represents the whole logging configuration,
4/// including all logging destinations and their respective log level filters.
5#[derive(Debug, Clone, PartialEq, Eq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub struct LoggingConfig {
8    /// List of destinations to log to.
9    /// If the list of destinations is empty, logging is disabled.
10    destinations: Vec<LogDestinationConfig>,
11}
12
13impl LoggingConfig {
14    /// Create a new [LoggingConfig] with the given destinations.
15    ///
16    /// If the list of destinations is empty, logging is disabled.
17    pub fn new(destinations: Vec<LogDestinationConfig>) -> Self {
18        Self { destinations }
19    }
20
21    /// Create a [LoggingConfig] that disables logging.
22    pub fn disabled() -> Self {
23        Self {
24            destinations: vec![],
25        }
26    }
27
28    /// Get the list of destinations to log to.
29    pub fn destinations(&self) -> &[LogDestinationConfig] {
30        &self.destinations
31    }
32}
33
34/// Configuration for a log destination, containing the destination and the log level.
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct LogDestinationConfig {
38    /// The destination to log to.
39    pub destination: LogDestination,
40
41    /// Only log messages at this level or higher to this destination.
42    ///
43    /// If `None`, the default level is used.
44    pub level: Option<log::LevelFilter>,
45}
46
47/// A destination that can be logged to, e.g. a file or the system log.
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub enum LogDestination {
51    /// Log to stderr
52    Stderr,
53
54    /// Log to the file at the given path
55    File(PathBuf),
56
57    /// Log to the system log
58    Syslog,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn disabled() {
67        let config = LoggingConfig::disabled();
68        assert!(config.destinations().is_empty());
69    }
70
71    #[test]
72    fn new() {
73        let config = LoggingConfig::new(vec![
74            LogDestinationConfig {
75                destination: LogDestination::Stderr,
76                level: Some(log::LevelFilter::Info),
77            },
78            LogDestinationConfig {
79                destination: LogDestination::File(PathBuf::from("/tmp/logfile")),
80                level: Some(log::LevelFilter::Debug),
81            },
82        ]);
83        assert_eq!(
84            vec![
85                LogDestinationConfig {
86                    destination: LogDestination::Stderr,
87                    level: Some(log::LevelFilter::Info),
88                },
89                LogDestinationConfig {
90                    destination: LogDestination::File(PathBuf::from("/tmp/logfile")),
91                    level: Some(log::LevelFilter::Debug),
92                },
93            ],
94            config.destinations()
95        );
96    }
97
98    #[cfg(feature = "serde")]
99    #[test]
100    fn logging_config_round_trips_through_postcard() {
101        let cfg = LoggingConfig::new(vec![
102            LogDestinationConfig {
103                destination: LogDestination::Syslog,
104                level: Some(log::LevelFilter::Info),
105            },
106            LogDestinationConfig {
107                destination: LogDestination::File(PathBuf::from("/tmp/x.log")),
108                level: None,
109            },
110            LogDestinationConfig {
111                destination: LogDestination::Stderr,
112                level: Some(log::LevelFilter::Warn),
113            },
114        ]);
115        let bytes = postcard::to_stdvec(&cfg).unwrap();
116        let restored: LoggingConfig = postcard::from_bytes(&bytes).unwrap();
117        assert_eq!(cfg, restored);
118    }
119}