diff --git a/doc/src/09020-changelog.md b/doc/src/09020-changelog.md index e7445989..f0105f4b 100644 --- a/doc/src/09020-changelog.md +++ b/doc/src/09020-changelog.md @@ -25,6 +25,7 @@ This section contains the changelog from the last release to the next release. * The codebase was moved to a more tree-ish approach, where several subdirectories were introduced for different types of crates * The documentation got a major overhaul and was partly rewritten + * The logger is now configurable via the config file. * New * `libimagentrygps` was introduced * Fixed bugs diff --git a/imagrc.toml b/imagrc.toml index 5c5598b5..955f5238 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -1,6 +1,35 @@ # This is a example configuration file for the imag suite. # It is written in TOML +[imag.logging] +level = "debug" +destinations = [ "-" ] + +# Valid variables for logging: +# * "level" +# * "module_path" +# * "file" +# * "line" +# * "target" +# * "message" +# +# Valid functions to be applied: +# * "black" +# * "blue" +# * "cyan" +# * "green" +# * "purple" +# * "red" +# * "white" +# * "yellow" + +[imag.logging.format] +trace = "[imag][{{red level}}][{{module_path}}]: {{message}}" +debug = "[imag][{{cyan level}}]: {{message}}" +info = "[imag]: {{message}}" +warn = "[imag][{{bold level}}]: {{yellow message}}" +error = "[imag][{{red level}}]: {{red message}}" + # # Configuration options for the user interface # diff --git a/lib/core/libimagrt/Cargo.toml b/lib/core/libimagrt/Cargo.toml index 25e95ff0..e557ecd4 100644 --- a/lib/core/libimagrt/Cargo.toml +++ b/lib/core/libimagrt/Cargo.toml @@ -23,6 +23,7 @@ itertools = "0.5" ansi_term = "0.9" is-match = "0.1" toml-query = "0.3.0" +handlebars = "0.29.0" libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" } diff --git a/lib/core/libimagrt/src/error.rs b/lib/core/libimagrt/src/error.rs index 388335af..ba2a34bc 100644 --- a/lib/core/libimagrt/src/error.rs +++ b/lib/core/libimagrt/src/error.rs @@ -23,7 +23,20 @@ use std::io::Error as IOError; generate_error_types!(RuntimeError, RuntimeErrorKind, Instantiate => "Could not instantiate", IOError => "IO Error", - ProcessExitFailure => "Process exited with failure" + IOLogFileOpenError => "IO Error: Could not open logfile", + ProcessExitFailure => "Process exited with failure", + ConfigReadError => "Error while reading the configuration", + ConfigTypeError => "Error while reading the configuration: Type Error", + GlobalLogLevelConfigMissing => "Global config 'imag.logging.level' missing", + GlobalDestinationConfigMissing => "Global config 'imag.logging.destinations' missing", + InvalidLogLevelSpec => "Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed", + TomlReadError => "Error while reading in TOML document", + TemplateStringRegistrationError => "Error while registering logging template string", + ConfigMissingLoggingFormatTrace => "Missing config for logging format for trace logging", + ConfigMissingLoggingFormatDebug => "Missing config for logging format for debug logging", + ConfigMissingLoggingFormatInfo => "Missing config for logging format for info logging", + ConfigMissingLoggingFormatWarn => "Missing config for logging format for warn logging", + ConfigMissingLoggingFormatError => "Missing config for logging format for error logging" ); impl From for RuntimeError { diff --git a/lib/core/libimagrt/src/lib.rs b/lib/core/libimagrt/src/lib.rs index ce0e08c1..addce7b0 100644 --- a/lib/core/libimagrt/src/lib.rs +++ b/lib/core/libimagrt/src/lib.rs @@ -38,6 +38,7 @@ extern crate itertools; #[cfg(unix)] extern crate xdg_basedir; extern crate env_logger; extern crate ansi_term; +extern crate handlebars; extern crate clap; extern crate toml; diff --git a/lib/core/libimagrt/src/logger.rs b/lib/core/libimagrt/src/logger.rs index 992a9d94..2dc361d8 100644 --- a/lib/core/libimagrt/src/logger.rs +++ b/lib/core/libimagrt/src/logger.rs @@ -19,67 +19,116 @@ use std::io::Write; use std::io::stderr; +use std::collections::BTreeMap; +use configuration::Configuration; +use error::RuntimeErrorKind as EK; +use error::RuntimeError; +use error::MapErrInto; +use runtime::Runtime; + +use libimagerror::into::IntoError; + +use clap::ArgMatches; use log::{Log, LogLevel, LogRecord, LogMetadata}; +use toml::Value; +use toml_query::read::TomlValueReadExt; +use handlebars::Handlebars; -use ansi_term::Style; -use ansi_term::Colour; -use ansi_term::ANSIString; +type ModuleName = String; +type Result = ::std::result::Result; + +enum LogDestination { + Stderr, + File(::std::fs::File), +} + +impl Default for LogDestination { + fn default() -> LogDestination { + LogDestination::Stderr + } +} + +struct ModuleSettings { + enabled: bool, + level: Option, + + #[allow(unused)] + destinations: Option>, +} /// Logger implementation for `log` crate. pub struct ImagLogger { - prefix: String, - dbg_fileline: bool, - lvl: LogLevel, - color_enabled: bool, + global_loglevel : LogLevel, + + #[allow(unused)] + global_destinations : Vec, + // global_format_trace : , + // global_format_debug : , + // global_format_info : , + // global_format_warn : , + // global_format_error : , + module_settings : BTreeMap, + + handlebars: Handlebars, } impl ImagLogger { /// Create a new ImagLogger object with a certain level - pub fn new(lvl: LogLevel) -> ImagLogger { - ImagLogger { - prefix: "[imag]".to_owned(), - dbg_fileline: true, - lvl: lvl, - color_enabled: true + pub fn new(matches: &ArgMatches, config: Option<&Configuration>) -> Result { + let mut handlebars = Handlebars::new(); + + handlebars.register_helper("black" , Box::new(self::template_helpers::ColorizeBlackHelper)); + handlebars.register_helper("blue" , Box::new(self::template_helpers::ColorizeBlueHelper)); + handlebars.register_helper("cyan" , Box::new(self::template_helpers::ColorizeCyanHelper)); + handlebars.register_helper("green" , Box::new(self::template_helpers::ColorizeGreenHelper)); + handlebars.register_helper("purple" , Box::new(self::template_helpers::ColorizePurpleHelper)); + handlebars.register_helper("red" , Box::new(self::template_helpers::ColorizeRedHelper)); + handlebars.register_helper("white" , Box::new(self::template_helpers::ColorizeWhiteHelper)); + handlebars.register_helper("yellow" , Box::new(self::template_helpers::ColorizeYellowHelper)); + + handlebars.register_helper("underline" , Box::new(self::template_helpers::UnderlineHelper)); + handlebars.register_helper("bold" , Box::new(self::template_helpers::BoldHelper)); + handlebars.register_helper("blink" , Box::new(self::template_helpers::BlinkHelper)); + handlebars.register_helper("strikethrough" , Box::new(self::template_helpers::StrikethroughHelper)); + + { + let fmt = try!(aggregate_global_format_trace(matches, config)); + try!(handlebars.register_template_string("TRACE", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); } - } - - /// Set debugging to include file and line - pub fn with_dbg_file_and_line(mut self, b: bool) -> ImagLogger { - self.dbg_fileline = b; - self - } - - /// Set debugging to include prefix - pub fn with_prefix(mut self, pref: String) -> ImagLogger { - self.prefix = pref; - self - } - - /// Set debugging to have color - pub fn with_color(mut self, b: bool) -> ImagLogger { - self.color_enabled = b; - self - } - - /// Helper function to colorize a string with a certain Style - fn style_or_not(&self, c: Style, s: String) -> ANSIString { - if self.color_enabled { - c.paint(s) - } else { - ANSIString::from(s) + { + let fmt = try!(aggregate_global_format_debug(matches, config)); + try!(handlebars.register_template_string("DEBUG", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); } + { + let fmt = try!(aggregate_global_format_info(matches, config)); + try!(handlebars.register_template_string("INFO", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_warn(matches, config)); + try!(handlebars.register_template_string("WARN", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + { + let fmt = try!(aggregate_global_format_error(matches, config)); + try!(handlebars.register_template_string("ERROR", fmt) // name must be uppercase + .map_err_into(EK::TemplateStringRegistrationError)); + } + + Ok(ImagLogger { + global_loglevel : try!(aggregate_global_loglevel(matches, config)), + global_destinations : try!(aggregate_global_destinations(matches, config)), + module_settings : try!(aggregate_module_settings(matches, config)), + handlebars : handlebars, + }) } - /// Helper function to colorize a string with a certain Color - fn color_or_not(&self, c: Colour, s: String) -> ANSIString { - if self.color_enabled { - c.paint(s) - } else { - ANSIString::from(s) - } + pub fn global_loglevel(&self) -> LogLevel { + self.global_loglevel } } @@ -87,47 +136,432 @@ impl ImagLogger { impl Log for ImagLogger { fn enabled(&self, metadata: &LogMetadata) -> bool { - metadata.level() <= self.lvl + metadata.level() <= self.global_loglevel } fn log(&self, record: &LogRecord) { - use ansi_term::Colour::Red; - use ansi_term::Colour::Yellow; - use ansi_term::Colour::Cyan; + if record.location().module_path().starts_with("handlebars") { + // This is a ugly, yet necessary hack. When logging, we use handlebars for templating. + // But as the handlebars library itselfs logs via a normal logging macro ("debug!()"), + // we have a recursion in our chain. + // + // To prevent this recursion, we return here. + // + // (As of handlebars 0.29.0 - please check whether you can update handlebars if you see + // this. Hopefully the next version has a compiletime flag to disable logging) + return; + } - if self.enabled(record.metadata()) { - // TODO: This is just simple logging. Maybe we can enhance this lateron - let loc = record.location(); - match record.metadata().level() { - LogLevel::Debug => { - let lvl = self.color_or_not(Cyan, format!("{}", record.level())); - let args = self.color_or_not(Cyan, format!("{}", record.args())); - if self.dbg_fileline { - let file = self.color_or_not(Cyan, format!("{}", loc.file())); - let ln = self.color_or_not(Cyan, format!("{}", loc.line())); + let mut data = BTreeMap::new(); - writeln!(stderr(), "{}[{: <5}][{}][{: >5}]: {}", self.prefix, lvl, file, ln, args).ok(); - } else { - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - } - }, - LogLevel::Warn | LogLevel::Error => { - let lvl = self.style_or_not(Red.blink(), format!("{}", record.level())); - let args = self.color_or_not(Red, format!("{}", record.args())); + { + data.insert("level", format!("{}", record.level())); + data.insert("module_path", String::from(record.location().module_path())); + data.insert("file", String::from(record.location().file())); + data.insert("line", format!("{}", record.location().line())); + data.insert("target", String::from(record.target())); + data.insert("message", format!("{}", record.args())); + } - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - }, - LogLevel::Info => { - let lvl = self.color_or_not(Yellow, format!("{}", record.level())); - let args = self.color_or_not(Yellow, format!("{}", record.args())); + let logtext = self + .handlebars + .render(&format!("{}", record.level()), &data) + .unwrap_or_else(|e| format!("Failed rendering logging data: {:?}\n", e)); - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok(); - }, - _ => { - writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, record.level(), record.args()).ok(); - }, + self.module_settings + .get(record.target()) + .map(|module_setting| { + let set = module_setting.enabled && + module_setting.level.unwrap_or(self.global_loglevel) >= record.level(); + + if set { + let _ = write!(stderr(), "{}\n", logtext); + } + }) + .unwrap_or_else(|| { + if self.global_loglevel >= record.level() { + // Yes, we log + let _ = write!(stderr(), "{}\n", logtext); + } + }); + } +} + +fn match_log_level_str(s: &str) -> Result { + match s { + "trace" => Ok(LogLevel::Trace), + "debug" => Ok(LogLevel::Debug), + "info" => Ok(LogLevel::Info), + "warn" => Ok(LogLevel::Warn), + "error" => Ok(LogLevel::Error), + _ => return Err(EK::InvalidLogLevelSpec.into_error()), + } +} + +fn aggregate_global_loglevel(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + match config { + Some(cfg) => match cfg + .read("imag.logging.level") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::String(ref s))) => match_log_level_str(s), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalLogLevelConfigMissing.into_error()), + Err(e) => Err(e) + }, + None => { + if matches.is_present(Runtime::arg_debugging_name()) { + return Ok(LogLevel::Debug) + } + + matches + .value_of(Runtime::arg_verbosity_name()) + .map(match_log_level_str) + .unwrap_or(Ok(LogLevel::Info)) + } + } +} + +fn translate_destination(raw: &str) -> Result { + use std::fs::OpenOptions; + + match raw { + "-" => Ok(LogDestination::Stderr), + other => { + OpenOptions::new() + .append(true) + .create(true) + .open(other) + .map(LogDestination::File) + .map_err_into(EK::IOLogFileOpenError) + } + } +} + + +fn translate_destinations(raw: &Vec) -> Result> { + raw.iter() + .fold(Ok(vec![]), |acc, val| { + acc.and_then(|mut v| { + let dest = match *val { + Value::String(ref s) => try!(translate_destination(s)), + _ => return Err(EK::ConfigTypeError.into_error()), + }; + v.push(dest); + Ok(v) + }) + }) +} + +fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configuration>) + -> Result> +{ + + match config { + Some(cfg) => match cfg + .read("imag.logging.destinations") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::Array(ref a))) => translate_destinations(a), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(EK::GlobalDestinationConfigMissing.into_error()), + Err(e) => Err(e) + }, + None => { + if let Some(values) = matches.value_of(Runtime::arg_logdest_name()) { + // parse logdest specification from commandline + + values.split(",") + .fold(Ok(vec![]), move |acc, dest| { + acc.and_then(|mut v| { + v.push(try!(translate_destination(dest))); + Ok(v) + }) + }) + } else { + Ok(vec![ LogDestination::default() ]) } } } } +#[inline] +fn aggregate_global_format( + read_str: &str, + cli_match_name: &str, + error_kind_if_missing: EK, + matches: &ArgMatches, + config: Option<&Configuration> + ) +-> Result +{ + match config { + Some(cfg) => match cfg + .read(read_str) + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::String(ref s))) => Ok(s.clone()), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => Err(error_kind_if_missing.into_error()), + Err(e) => Err(e) + }, + None => match matches.value_of(cli_match_name).map(String::from) { + Some(s) => Ok(s), + None => Err(error_kind_if_missing.into_error()) + } + } +} + +fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + aggregate_global_format("imag.logging.format.trace", + Runtime::arg_override_trace_logging_format(), + EK::ConfigMissingLoggingFormatTrace, + matches, + config) +} + +fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + aggregate_global_format("imag.logging.format.debug", + Runtime::arg_override_debug_logging_format(), + EK::ConfigMissingLoggingFormatDebug, + matches, + config) +} + +fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + aggregate_global_format("imag.logging.format.info", + Runtime::arg_override_info_logging_format(), + EK::ConfigMissingLoggingFormatInfo, + matches, + config) +} + +fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + aggregate_global_format("imag.logging.format.warn", + Runtime::arg_override_warn_logging_format(), + EK::ConfigMissingLoggingFormatWarn, + matches, + config) +} + +fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>) + -> Result +{ + aggregate_global_format("imag.logging.format.error", + Runtime::arg_override_error_logging_format(), + EK::ConfigMissingLoggingFormatError, + matches, + config) +} + +fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuration>) + -> Result> +{ + match config { + Some(cfg) => match cfg + .read("imag.logging.modules") + .map_err_into(EK::ConfigReadError) + { + Ok(Some(&Value::Table(ref t))) => { + // translate the module settings from the table `t` + let mut settings = BTreeMap::new(); + + for (module_name, v) in t { + let destinations = try!(match v.read("destinations") { + Ok(Some(&Value::Array(ref a))) => translate_destinations(a).map(Some), + Ok(None) => Ok(None), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let level = try!(match v.read("level") { + Ok(Some(&Value::String(ref s))) => match_log_level_str(s).map(Some), + Ok(None) => Ok(None), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let enabled = try!(match v.read("enabled") { + Ok(Some(&Value::Boolean(b))) => Ok(b), + Ok(None) => Ok(false), + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Err(e) => Err(e).map_err_into(EK::TomlReadError), + }); + + let module_settings = ModuleSettings { + enabled: enabled, + level: level, + destinations: destinations, + }; + + // We don't care whether there was a value, we override it. + let _ = settings.insert(module_name.to_owned(), module_settings); + } + + Ok(settings) + }, + Ok(Some(_)) => Err(EK::ConfigTypeError.into_error()), + Ok(None) => { + // No modules configured. This is okay! + Ok(BTreeMap::new()) + }, + Err(e) => Err(e), + }, + None => { + write!(stderr(), "No Configuration.").ok(); + write!(stderr(), "cannot find module-settings for logging.").ok(); + write!(stderr(), "Will use global defaults").ok(); + + Ok(BTreeMap::new()) + } + } +} + +mod template_helpers { + use handlebars::{Handlebars, HelperDef, JsonRender, RenderError, RenderContext, Helper}; + use ansi_term::Colour; + use ansi_term::Style; + + #[derive(Clone, Copy)] + pub struct ColorizeBlackHelper; + + impl HelperDef for ColorizeBlackHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Black, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeBlueHelper; + + impl HelperDef for ColorizeBlueHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Blue, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeCyanHelper; + + impl HelperDef for ColorizeCyanHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Cyan, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeGreenHelper; + + impl HelperDef for ColorizeGreenHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Green, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizePurpleHelper; + + impl HelperDef for ColorizePurpleHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Purple, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeRedHelper; + + impl HelperDef for ColorizeRedHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Red, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeWhiteHelper; + + impl HelperDef for ColorizeWhiteHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::White, h, hb, rc) + } + } + + #[derive(Clone, Copy)] + pub struct ColorizeYellowHelper; + + impl HelperDef for ColorizeYellowHelper { + fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + colorize(Colour::Yellow, h, hb, rc) + } + } + + fn colorize(color: Colour, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + + try!(write!(rc.writer(), "{}", color.paint(p.value().render()))); + Ok(()) + } + + #[derive(Clone, Copy)] + pub struct UnderlineHelper; + + impl HelperDef for UnderlineHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().underline(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct BoldHelper; + + impl HelperDef for BoldHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().bold(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct BlinkHelper; + + impl HelperDef for BlinkHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().blink(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + + #[derive(Clone, Copy)] + pub struct StrikethroughHelper; + + impl HelperDef for StrikethroughHelper { + fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), + RenderError> { + let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments"))); + let s = Style::new().strikethrough(); + try!(write!(rc.writer(), "{}", s.paint(p.value().render()))); + Ok(()) + } + } + +} + diff --git a/lib/core/libimagrt/src/runtime.rs b/lib/core/libimagrt/src/runtime.rs index d30ca3aa..43c12904 100644 --- a/lib/core/libimagrt/src/runtime.rs +++ b/lib/core/libimagrt/src/runtime.rs @@ -20,14 +20,12 @@ use std::path::PathBuf; use std::process::Command; use std::env; -use std::io::stderr; -use std::io::Write; +use std::process::exit; pub use clap::App; use clap::{Arg, ArgMatches}; use log; -use log::LogLevelFilter; use configuration::{Configuration, InternalConfiguration}; use error::RuntimeError; @@ -35,6 +33,7 @@ use error::RuntimeErrorKind; use error::MapErrInto; use logger::ImagLogger; +use libimagerror::trace::*; use libimagstore::store::Store; use libimagstore::file_abstraction::InMemoryFileAbstraction; use spec::CliSpec; @@ -78,8 +77,8 @@ impl<'a> Runtime<'a> { Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound { return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e))); } else { - warn!("No config file found."); - warn!("Continuing without configuration file"); + println!("No config file found."); + println!("Continuing without configuration file"); None }, @@ -112,16 +111,10 @@ impl<'a> Runtime<'a> { where C: Clone + CliSpec<'a> + InternalConfiguration { use std::io::stdout; - use clap::Shell; - let is_debugging = matches.is_present(Runtime::arg_debugging_name()); - if cli_app.enable_logging() { - let is_verbose = matches.is_present(Runtime::arg_verbosity_name()); - let colored = !matches.is_present(Runtime::arg_no_color_output_name()); - - Runtime::init_logger(is_debugging, is_verbose, colored); + Runtime::init_logger(&matches, config.as_ref()) } match matches.value_of(Runtime::arg_generate_compl()) { @@ -151,9 +144,9 @@ impl<'a> Runtime<'a> { None => None, }; - if is_debugging { - write!(stderr(), "Config: {:?}\n", config).ok(); - write!(stderr(), "Store-config: {:?}\n", store_config).ok(); + if matches.is_present(Runtime::arg_debugging_name()) { + debug!("Config: {:?}\n", config); + debug!("Store-config: {:?}\n", store_config); } let store_result = if cli_app.use_inmemory_fs() { @@ -200,9 +193,11 @@ impl<'a> Runtime<'a> { .arg(Arg::with_name(Runtime::arg_verbosity_name()) .short("v") .long("verbose") - .help("Enables verbosity") + .help("Enables verbosity, can be used to set log level to one of 'trace', 'debug', 'info', 'warn' or 'error'") .required(false) - .takes_value(false)) + .takes_value(true) + .possible_values(&["trace", "debug", "info", "warn", "error"]) + .value_name("LOGLEVEL")) .arg(Arg::with_name(Runtime::arg_debugging_name()) .long("debug") @@ -254,6 +249,61 @@ impl<'a> Runtime<'a> { .value_name("SHELL") .possible_values(&["bash", "fish", "zsh"])) + .arg(Arg::with_name(Runtime::arg_logdest_name()) + .long(Runtime::arg_logdest_name()) + .help("Override the logging destinations from the configuration: values can be seperated by ',', a value of '-' marks the stderr output, everything else is expected to be a path") + .required(false) + .takes_value(true) + .value_name("LOGDESTS")) + + .arg(Arg::with_name(Runtime::arg_override_module_logging_setting_name()) + .long(Runtime::arg_override_module_logging_setting_name()) + .help("Override a module logging setting for one module. Format: ==, whereas is either 'enabled', 'level' or 'destinations' - This commandline argument is CURRENTLY NOT IMPLEMENTED") + .multiple(true) + .required(false) + .takes_value(true) + .value_name("SPEC")) + + .arg(Arg::with_name(Runtime::arg_override_trace_logging_format()) + .long(Runtime::arg_override_trace_logging_format()) + .help("Override the logging format for the trace logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_debug_logging_format()) + .long(Runtime::arg_override_debug_logging_format()) + .help("Override the logging format for the debug logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_info_logging_format()) + .long(Runtime::arg_override_info_logging_format()) + .help("Override the logging format for the info logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_warn_logging_format()) + .long(Runtime::arg_override_warn_logging_format()) + .help("Override the logging format for the warn logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + + .arg(Arg::with_name(Runtime::arg_override_error_logging_format()) + .long(Runtime::arg_override_error_logging_format()) + .help("Override the logging format for the error logging") + .multiple(false) + .required(false) + .takes_value(true) + .value_name("FMT")) + } /// Get the argument names of the Runtime which are available @@ -338,26 +388,50 @@ impl<'a> Runtime<'a> { self } + /// Get the argument name for the logging destination + pub fn arg_logdest_name() -> &'static str { + "logging-destinations" + } + + pub fn arg_override_module_logging_setting_name() -> &'static str { + "override-module-log-setting" + } + + pub fn arg_override_trace_logging_format() -> &'static str { + "override-logging-format-trace" + } + + pub fn arg_override_debug_logging_format() -> &'static str { + "override-logging-format-debug" + } + + pub fn arg_override_info_logging_format() -> &'static str { + "override-logging-format-info" + } + + pub fn arg_override_warn_logging_format() -> &'static str { + "override-logging-format-warn" + } + + pub fn arg_override_error_logging_format() -> &'static str { + "override-logging-format-error" + } + /// Initialize the internal logger - fn init_logger(is_debugging: bool, is_verbose: bool, colored: bool) { + fn init_logger(matches: &ArgMatches, config: Option<&Configuration>) { use std::env::var as env_var; use env_logger; if env_var("IMAG_LOG_ENV").is_ok() { env_logger::init().unwrap(); } else { - let lvl = if is_debugging { - LogLevelFilter::Debug - } else if is_verbose { - LogLevelFilter::Info - } else { - LogLevelFilter::Warn - }; - log::set_logger(|max_log_lvl| { - max_log_lvl.set(lvl); - debug!("Init logger with {}", lvl); - Box::new(ImagLogger::new(lvl.to_log_level().unwrap()).with_color(colored)) + let logger = ImagLogger::new(matches, config) + .map_err_trace() + .unwrap_or_else(|_| exit(1)); + max_log_lvl.set(logger.global_loglevel().to_log_level_filter()); + debug!("Init logger with {}", logger.global_loglevel()); + Box::new(logger) }) .map_err(|e| panic!("Could not setup logger: {:?}", e)) .ok();