use std::fs::File; use std::io::Read; use failure::Fallible; use serde::Deserialize; use serde::Serialize; const CONFIG_FILE: &str = "manabu.toml"; const SOFTWARE_NAME: &str = env!("CARGO_PKG_NAME"); /// 3rd-party libraries that produce log spam - we set these to a fixed higher level /// to allow using e.g. TRACE without drowing our custom messages const SPAMMY_LIBS: [&str; 5] = ["tokio_reactor", "hyper", "reqwest", "mio", "want"]; #[derive(SmartDefault,Serialize,Deserialize,Debug)] #[serde(default)] pub struct Config { #[default="info"] logging: String, pub instance: String, #[default="manabu_store.json"] pub store: String, } const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"]; /// Load the shared config file fn load_config(file: &str) -> Fallible { let mut file = File::open(file)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; let config: Config = toml::from_str(&buf)?; // Validations if !LOG_LEVELS.contains(&config.logging.as_str()) { bail!("Invalid value for \"logging\""); } Ok(config) } pub(crate) fn init() -> Fallible { let version = format!("{}, built from {}", env!("CARGO_PKG_VERSION"), env!("GIT_REV")); let argv = clap::App::new(SOFTWARE_NAME) .version(version.as_str()) .arg( clap::Arg::with_name("config") .short("c") .long("config") .value_name("FILE") .help("Sets a custom config file (JSON)") .takes_value(true), ) .arg(clap::Arg::with_name("v").short("v").multiple(true).help( "Sets the level of verbosity (adds to the level configured in the config file)", )) .arg( clap::Arg::with_name("log") .short("l") .long("log") .value_name("LEVEL") .help("Set custom logging level (error,warning,info,debug,trace)") .takes_value(true), ) .get_matches(); let confile = argv.value_of("config").unwrap_or(CONFIG_FILE); println!("{}\nrun with -h for help", SOFTWARE_NAME); println!("config file: {}", confile); let mut config = load_config(confile).unwrap_or_else(|e| { println!("Error loading config file: {}", e); Default::default() }); if let Some(l) = argv.value_of("log") { if !LOG_LEVELS.contains(&config.logging.as_str()) { bail!("Invalid value for \"logging\""); } config.logging = l.to_owned(); } if argv.is_present("v") { // bump verbosity if -v's are present let pos = LOG_LEVELS .iter() .position(|x| x == &config.logging) .unwrap(); config.logging = match LOG_LEVELS .iter() .nth(pos + argv.occurrences_of("v") as usize) { Some(new_level) => new_level.to_string(), None => "trace".to_owned(), }; } println!("log level: {}", config.logging); let env = env_logger::Env::default().default_filter_or(&config.logging); let mut builder = env_logger::Builder::from_env(env); builder.format_timestamp_millis(); // set logging level for spammy libs. Ensure the configured log level is not exceeded let mut lib_level = log::LevelFilter::Info; if config.logging == "warn" { lib_level = log::LevelFilter::Warn; } else if config.logging == "error" { lib_level = log::LevelFilter::Error; } for lib in &SPAMMY_LIBS { builder.filter_module(lib, lib_level); } builder.init(); Ok(config) }