1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::LoggerError;
use std::str::FromStr;
use structopt::StructOpt;

/// Defines the Logger configuration.
#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct LoggerConfig {
    /// Sets the logger [`EnvFilter`].
    /// Valid values: trace, debug, info, warn, error
    /// Example of a valid filter: "warn,my_crate=info,my_crate::my_mod=debug,[my_span]=trace"
    #[structopt(long, env = "LS_LOGGER_LEVEL", default_value = "debug")]
    pub env_filter: String,

    #[structopt(flatten)]
    pub stdout_output: StandardOutputConfig,

    #[structopt(flatten)]
    pub file_output: FileOutputConfig,
}

impl Default for LoggerConfig {
    fn default() -> Self {
        Self {
            env_filter: "debug".to_owned(),
            stdout_output: StandardOutputConfig::default(),
            file_output: FileOutputConfig::default(),
        }
    }
}

#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct StandardOutputConfig {
    /// Determines whether the Logger should print to standard output.
    /// Valid values: true, false
    #[structopt(long, env = "LS_LOGGER_STDOUT_OUTPUT_ENABLED", parse(try_from_str), default_value = "true")]
    pub stdout_enabled: bool,

    #[structopt(long, env = "LS_LOGGER_STDOUT_OUTPUT_USE_ANSI_COLORS", parse(try_from_str), default_value = "true")]
    pub stdout_use_ansi_colors: bool,
}

impl Default for StandardOutputConfig {
    fn default() -> Self {
        Self { stdout_use_ansi_colors: true, stdout_enabled: true }
    }
}

#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct FileOutputConfig {
    /// Determines whether the Logger should print to a file.
    /// Valid values: true, false
    #[structopt(long, env = "LS_LOGGER_FILE_OUTPUT_ENABLED", parse(try_from_str), default_value = "false")]
    pub file_output_enabled: bool,

    /// The log file location
    #[structopt(long, env = "LS_LOGGER_FILE_OUTPUT_DIR", default_value = "/tmp")]
    pub file_output_directory: String,

    /// The log file name's _prefix_
    #[structopt(long, env = "LS_LOGGER_FILE_OUTPUT_NAME_PREFIX", default_value = "output.log")]
    pub file_output_name_prefix: String,

    /// The log file rotation strategy
    #[structopt(long, env = "LS_LOGGER_FILE_OUTPUT_ROTATION", default_value = "daily")]
    pub file_output_rotation: Rotation,
}

impl Default for FileOutputConfig {
    fn default() -> Self {
        Self {
            file_output_enabled: false,
            file_output_directory: "".to_owned(),
            file_output_name_prefix: "".to_owned(),
            file_output_rotation: Rotation::Daily,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Rotation {
    Minutely,
    Hourly,
    Daily,
    Never,
}

impl FromStr for Rotation {
    type Err = LoggerError;

    fn from_str(val: &str) -> Result<Self, Self::Err> {
        match val.to_lowercase().as_ref() {
            "minutely" => Ok(Rotation::Minutely),
            "hourly" => Ok(Rotation::Hourly),
            "daily" => Ok(Rotation::Daily),
            "never" => Ok(Rotation::Never),
            _ => Err(LoggerError::LoggerConfigurationError { message: format!("Could not parse rotation [{}]", val) }),
        }
    }
}

impl Rotation {
    pub fn to_tracing_appender_rotation(&self) -> tracing_appender::rolling::Rotation {
        match self {
            Rotation::Minutely => tracing_appender::rolling::Rotation::MINUTELY,
            Rotation::Hourly => tracing_appender::rolling::Rotation::HOURLY,
            Rotation::Daily => tracing_appender::rolling::Rotation::DAILY,
            Rotation::Never => tracing_appender::rolling::Rotation::NEVER,
        }
    }
}

impl LoggerConfig {
    pub fn build() -> Self {
        let app = Self::clap().setting(structopt::clap::AppSettings::AllowExternalSubcommands);
        Self::from_clap(&app.get_matches())
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn should_build_config() {
        let config = LoggerConfig::build();
        assert!(config.stdout_output.stdout_enabled);
    }
}