Skip to content

Commit

Permalink
Refactor cli logger (paritytech#185)
Browse files Browse the repository at this point in the history
* Refactor cli logger

* Update docs

* .

* Renaming

* Naming

* Nits
  • Loading branch information
liuchengxu authored Aug 12, 2020
1 parent 9acc737 commit 22e34b1
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 84 deletions.
35 changes: 25 additions & 10 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,42 @@ pub struct Cli {
#[structopt(flatten)]
pub run: RunCmd,

/// Enable the log4rs feature, including the log rotation function.
// Use `log4rs` as `env_logger` can't print the message into file directly.
#[structopt(long = "log4rs")]
/// Use `env_logger`, not `log4rs`. notice `env_logger` can't print into file directly
pub log4rs: bool,

/// Specify the path of directory which will contain the log files.
///
/// The directory will be created if it does not exist.
#[structopt(long = "log-dir", default_value = "./log")]
/// When use `log4rs`, assign the path of log dir, notice this would create the dir directly. Default dir is `./log`
pub log_dir: String,
#[structopt(long = "log-name", default_value = "chainx.log")]
/// When use `log4rs`, assign the log file name. Default dir is `chainx.log`, thus when use default config, the log would create in ./log/chainx.log
pub log_name: String,

/// Specify the name of log file.
///
/// The latest log file would be created at ./log/chainx.log.
#[structopt(long = "log-filename", default_value = "chainx.log")]
pub log_filename: String,

/// Rotate the log file when it exceeds this size (in MB).
#[structopt(long = "log-size", default_value = "300")]
/// When use `log4rs`, the default log size in log rotation. The unit is MB, default is 300 MB
pub log_size: u64,

/// The maximum number of log rorations.
///
/// By default the generated log files would be like `chainx.log.0`,
/// `chainx.log.1`, ... `chainx.log.10`. Once the number of generated log files
/// are larger than this variable, the oldest one will be removed.
#[structopt(long = "log-roll-count", default_value = "10")]
/// When use `log4rs`, the max log rotation. Default is 10. If the log is more then the number, would delete old file.
/// The log would like `chainx.log.0`, `chainx.log.1` ... `chainx.log.10`
pub log_roll_count: u32,

/// Print the log message to the console aside from the log file.
#[structopt(long = "log-console")]
/// When use `log4rs`, print log into console. Default is false
pub log_console: bool,

/// Compress the old log file to save some disk space.
///
/// The compressed log file would be like `chainx.log.gz.0` by default.
#[structopt(long = "log-compression")]
/// When use `log4rs`, compress the old log to save space. the log would like `chainx.log.gz.0`
pub log_compression: bool,
}
3 changes: 1 addition & 2 deletions cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ pub fn run() -> sc_cli::Result<()> {
let cli = Cli::from_args();

if cli.log4rs {
let s = cli.run.log_filters()?;
crate::logger::init_logger_log4rs(&s, &cli)?;
crate::logger::init(&cli.run.log_filters()?, &cli)?;
}

match &cli.subcommand {
Expand Down
148 changes: 76 additions & 72 deletions cli/src/logger.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
use std::str::FromStr;

use log::LevelFilter;
use log4rs::{
append::{
console::ConsoleAppender,
rolling_file::{
policy::{
self,
compound::{roll, trigger::size::SizeTrigger},
},
RollingFileAppender,
},
},
config,
encode::pattern::PatternEncoder,
};

use crate::cli::Cli;

Expand All @@ -9,23 +23,31 @@ struct Directive {
name: Option<String>,
level: LevelFilter,
}
/// and return a vector with log directives.
fn parse_spec(spec: &str) -> (Vec<Directive>, Option<LevelFilter>) {
let mut dirs = Vec::new();

let mut parts = spec.split('/');
/// Parses the log filters and returns a vector of `Directive`.
///
/// The log filters should be a list of comma-separated values.
/// Example: `foo=trace,bar=debug,baz=info`
fn parse_log_filters(log_filters: &str) -> (Vec<Directive>, Option<LevelFilter>) {
let mut directives = Vec::new();

let mut parts = log_filters.split('/');
let mods = parts.next();
let filter = parts.next().and_then(|s| FromStr::from_str(s).ok());
if parts.next().is_some() {
eprintln!(
"warning: invalid logging spec '{}', ignoring it (too many '/'s)",
spec
"warning: invalid logging log_filters '{}', ignoring it (too many '/'s)",
log_filters
);
return (dirs, None);
return (directives, None);
}

mods.map(|m| {
let print_warning =
|v: &str| eprintln!("warning: invalid log_filters value '{}', ignoring it", v);

for s in m.split(',') {
if s.len() == 0 {
if s.is_empty() {
continue;
}
let mut parts = s.split('=');
Expand All @@ -43,76 +65,54 @@ fn parse_spec(spec: &str) -> (Vec<Directive>, Option<LevelFilter>) {
(Some(part0), Some(part1), None) => match part1.parse() {
Ok(num) => (num, Some(part0)),
_ => {
eprintln!(
"warning: invalid logging spec '{}', \
ignoring it",
part1
);
print_warning(part1);
continue;
}
},
_ => {
eprintln!(
"warning: invalid logging spec '{}', \
ignoring it",
s
);
print_warning(s);
continue;
}
};
dirs.push(Directive {
directives.push(Directive {
name: name.map(|s| s.to_string()),
level: log_level,
});
}
});

let mut tmp_filter = LevelFilter::Off;
for d in dirs.iter() {
if d.name == None {
if d.level > tmp_filter {
tmp_filter = d.level;
}
let mut filter_level = LevelFilter::Off;
for d in directives.iter() {
if d.name.is_none() && d.level > filter_level {
filter_level = d.level;
}
}

let filter = if let Some(f) = filter {
if f > tmp_filter {
if f > filter_level {
Some(f)
} else {
Some(tmp_filter)
Some(filter_level)
}
} else if filter_level == LevelFilter::Off {
None
} else {
if tmp_filter == LevelFilter::Off {
None
} else {
Some(tmp_filter)
}
Some(filter_level)
};

return (dirs, filter);
return (directives, filter);
}

pub fn init_logger_log4rs(spec: &str, params: &Cli) -> Result<(), String> {
use log4rs::{
append::{
console::ConsoleAppender,
rolling_file::{
policy::{
self,
compound::{roll, trigger},
},
RollingFileAppender,
},
},
config,
encode::pattern::PatternEncoder,
};
/// Initialize the log4rs configuration.
pub fn init(log_filters: &str, params: &Cli) -> Result<(), String> {
if params.log_size == 0 {
return Err("the `--log-size` can't be 0".to_string());
}

let (directives, filter) = parse_spec(spec);
let (directives, filter) = parse_log_filters(log_filters);
let filter = filter.unwrap_or(LevelFilter::Info);

let (pattern1, pattern2) = if filter > LevelFilter::Info {
let (console_pattern, log_file_pattern) = if filter > LevelFilter::Info {
(
"{d(%Y-%m-%d %H:%M:%S:%3f)} {T} {h({l})} {t} {m}\n",
"{d(%Y-%m-%d %H:%M:%S:%3f)} {T} {l} {t} {m}\n", // remove color
Expand All @@ -124,34 +124,37 @@ pub fn init_logger_log4rs(spec: &str, params: &Cli) -> Result<(), String> {
)
};

let console = ConsoleAppender::builder()
.encoder(Box::new(PatternEncoder::new(pattern1)))
.build();
let full_log_filename = format!(
"{}{}{}",
params.log_dir,
std::path::MAIN_SEPARATOR,
&params.log_filename
);

let log = params.log_dir.clone() + "/" + &params.log_name;
let log_file = if params.log_compression {
log.clone() + ".gz"
let roller_pattern = if params.log_compression {
full_log_filename.clone() + ".gz"
} else {
log.clone()
full_log_filename.clone()
};

if params.log_size == 0 {
return Err("the `--log-size` can't be 0".to_string());
}

let trigger = trigger::size::SizeTrigger::new(1024 * 1024 * params.log_size);
let roll_pattern = format!("{}.{{}}", log_file);
let roll = roll::fixed_window::FixedWindowRoller::builder()
.build(roll_pattern.as_str(), params.log_roll_count)
let roller = roll::fixed_window::FixedWindowRoller::builder()
.build(&format!("{}.{{}}", roller_pattern), params.log_roll_count)
.map_err(|e| format!("log rotate file:{:?}", e))?;

let policy = policy::compound::CompoundPolicy::new(Box::new(trigger), Box::new(roll));
let policy = policy::compound::CompoundPolicy::new(
Box::new(SizeTrigger::new(params.log_size * 1024 * 1024)), // log_size MB
Box::new(roller),
);

let roll_file = RollingFileAppender::builder()
.encoder(Box::new(PatternEncoder::new(pattern2)))
.build(log, Box::new(policy))
.encoder(Box::new(PatternEncoder::new(log_file_pattern)))
.build(full_log_filename, Box::new(policy))
.map_err(|e| format!("{}", e))?;

let mut tmp_builder = if params.log_console {
let mut config_builder = if params.log_console {
let console = ConsoleAppender::builder()
.encoder(Box::new(PatternEncoder::new(console_pattern)))
.build();
config::Config::builder()
.appender(config::Appender::builder().build("console", Box::new(console)))
.appender(config::Appender::builder().build("roll", Box::new(roll_file)))
Expand All @@ -162,7 +165,7 @@ pub fn init_logger_log4rs(spec: &str, params: &Cli) -> Result<(), String> {

for d in directives {
if let Some(name) = d.name {
tmp_builder = tmp_builder.logger(config::Logger::builder().build(name, d.level));
config_builder = config_builder.logger(config::Logger::builder().build(name, d.level));
}
}

Expand All @@ -175,10 +178,11 @@ pub fn init_logger_log4rs(spec: &str, params: &Cli) -> Result<(), String> {
config::Root::builder().appender("roll").build(filter)
};

let log_config = tmp_builder
let log_config = config_builder
.build(root)
.expect("Construct log config failure");

log4rs::init_config(log_config).expect("Initializing log config shouldn't be fail");
log4rs::init_config(log_config).expect("The log4rs config initialization shouldn't fail; qed");

Ok(())
}

0 comments on commit 22e34b1

Please sign in to comment.