Browse Source

Use a proper (and colored) logger for terminal

pull/2/head
JuanLeon Lahoz 5 years ago
parent
commit
3083bb4adc
  1. 22
      Cargo.lock
  2. 2
      Cargo.toml
  3. 1
      src/app.rs
  4. 73
      src/main.rs
  5. 47
      src/reader.rs
  6. 7
      src/timehist.rs

22
Cargo.lock generated

@ -243,6 +243,15 @@ version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "lowcharts" name = "lowcharts"
version = "0.2.0" version = "0.2.0"
@ -252,7 +261,9 @@ dependencies = [
"derive_builder", "derive_builder",
"float_eq", "float_eq",
"isatty", "isatty",
"log",
"regex", "regex",
"simplelog",
"tempfile", "tempfile",
"yansi", "yansi",
] ]
@ -417,6 +428,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "simplelog"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59d0fe306a0ced1c88a58042dc22fc2ddd000982c26d75f6aa09a394547c41e0"
dependencies = [
"chrono",
"log",
"termcolor",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"

2
Cargo.toml

@ -12,6 +12,8 @@ isatty = "0.1"
derive_builder = "0.10.0" derive_builder = "0.10.0"
regex = "1.4.5" regex = "1.4.5"
chrono = "0.4" chrono = "0.4"
simplelog = "^0.10.0"
log = "*"
[dev-dependencies] [dev-dependencies]
float_eq = "0.5.0" float_eq = "0.5.0"

1
src/app.rs

@ -133,6 +133,7 @@ pub fn get_app() -> App<'static> {
.long("color") .long("color")
.about("Use colors in the output") .about("Use colors in the output")
.possible_values(&["auto", "no", "yes"]) .possible_values(&["auto", "no", "yes"])
.default_value("auto")
.takes_value(true), .takes_value(true),
) )
.arg( .arg(

73
src/main.rs

@ -3,12 +3,15 @@ use std::env;
use isatty::stdout_isatty; use isatty::stdout_isatty;
use regex::Regex; use regex::Regex;
use yansi::Color::{Red, Yellow};
use yansi::Paint; use yansi::Paint;
#[macro_use] #[macro_use]
extern crate derive_builder; extern crate derive_builder;
#[macro_use]
extern crate log;
use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode};
mod app; mod app;
mod dateparser; mod dateparser;
mod histogram; mod histogram;
@ -18,9 +21,13 @@ mod reader;
mod stats; mod stats;
mod timehist; mod timehist;
fn disable_color_if_needed(option: &str) { fn configure_output(option: &str, verbose: bool) {
let mut color_choice = ColorChoice::Auto;
match option { match option {
"no" => Paint::disable(), "no" => {
Paint::disable();
color_choice = ColorChoice::Never;
}
"auto" => match env::var("TERM") { "auto" => match env::var("TERM") {
Ok(value) if value == "dumb" => Paint::disable(), Ok(value) if value == "dumb" => Paint::disable(),
_ => { _ => {
@ -29,21 +36,35 @@ fn disable_color_if_needed(option: &str) {
} }
} }
}, },
"yes" => {
color_choice = ColorChoice::Always;
}
_ => (), _ => (),
} };
TermLogger::init(
if verbose {
LevelFilter::Info
} else {
LevelFilter::Debug
},
ConfigBuilder::new()
.set_time_level(LevelFilter::Trace)
.set_thread_level(LevelFilter::Trace)
.set_target_level(LevelFilter::Trace)
.build(),
TerminalMode::Stderr,
color_choice,
)
.unwrap();
} }
fn get_reader(matches: &ArgMatches, verbose: bool) -> reader::DataReader { fn get_reader(matches: &ArgMatches) -> reader::DataReader {
let mut builder = reader::DataReaderBuilder::default(); let mut builder = reader::DataReaderBuilder::default();
builder.verbose(verbose);
if matches.is_present("min") || matches.is_present("max") { if matches.is_present("min") || matches.is_present("max") {
let min = matches.value_of_t("min").unwrap_or(f64::NEG_INFINITY); let min = matches.value_of_t("min").unwrap_or(f64::NEG_INFINITY);
let max = matches.value_of_t("max").unwrap_or(f64::INFINITY); let max = matches.value_of_t("max").unwrap_or(f64::INFINITY);
if min > max { if min > max {
eprintln!( error!("Minimum should be smaller than maximum");
"[{}] Minimum should be smaller than maximum",
Red.paint("ERROR")
);
std::process::exit(1); std::process::exit(1);
} }
builder.range(min..max); builder.range(min..max);
@ -54,7 +75,7 @@ fn get_reader(matches: &ArgMatches, verbose: bool) -> reader::DataReader {
builder.regex(re); builder.regex(re);
} }
_ => { _ => {
eprintln!("[{}]: Failed to parse regex {}", Red.paint("ERROR"), string); error!("Failed to parse regex {}", string);
std::process::exit(1); std::process::exit(1);
} }
}; };
@ -62,11 +83,11 @@ fn get_reader(matches: &ArgMatches, verbose: bool) -> reader::DataReader {
builder.build().unwrap() builder.build().unwrap()
} }
fn histogram(matches: &ArgMatches, verbose: bool) { fn histogram(matches: &ArgMatches) {
let reader = get_reader(&matches, verbose); let reader = get_reader(&matches);
let vec = reader.read(matches.value_of("input").unwrap()); let vec = reader.read(matches.value_of("input").unwrap());
if vec.is_empty() { if vec.is_empty() {
eprintln!("[{}] No data to process", Yellow.paint("WARN")); warn!("No data to process");
std::process::exit(0); std::process::exit(0);
} }
let stats = stats::Stats::new(&vec); let stats = stats::Stats::new(&vec);
@ -77,14 +98,14 @@ fn histogram(matches: &ArgMatches, verbose: bool) {
let mut histogram = let mut histogram =
histogram::Histogram::new(intervals, (stats.max - stats.min) / intervals as f64, stats); histogram::Histogram::new(intervals, (stats.max - stats.min) / intervals as f64, stats);
histogram.load(&vec); histogram.load(&vec);
println!("{:width$}", histogram, width = width); print!("{:width$}", histogram, width = width);
} }
fn plot(matches: &ArgMatches, verbose: bool) { fn plot(matches: &ArgMatches) {
let reader = get_reader(&matches, verbose); let reader = get_reader(&matches);
let vec = reader.read(matches.value_of("input").unwrap()); let vec = reader.read(matches.value_of("input").unwrap());
if vec.is_empty() { if vec.is_empty() {
eprintln!("[{}] No data to process", Yellow.paint("WARN")); warn!("No data to process");
std::process::exit(0); std::process::exit(0);
} }
let mut plot = plot::Plot::new( let mut plot = plot::Plot::new(
@ -117,7 +138,7 @@ fn timehist(matches: &ArgMatches) {
builder.regex(re); builder.regex(re);
} }
_ => { _ => {
eprintln!("[{}]: Failed to parse regex {}", Red.paint("ERROR"), string); error!("Failed to parse regex {}", string);
std::process::exit(1); std::process::exit(1);
} }
}; };
@ -129,7 +150,7 @@ fn timehist(matches: &ArgMatches) {
let reader = builder.build().unwrap(); let reader = builder.build().unwrap();
let vec = reader.read(matches.value_of("input").unwrap()); let vec = reader.read(matches.value_of("input").unwrap());
if vec.len() <= 1 { if vec.len() <= 1 {
eprintln!("[{}] Not enough data to process", Yellow.paint("WARN")); warn!("Not enough data to process");
std::process::exit(0); std::process::exit(0);
} }
let mut timehist = timehist::TimeHistogram::new(matches.value_of_t("intervals").unwrap(), &vec); let mut timehist = timehist::TimeHistogram::new(matches.value_of_t("intervals").unwrap(), &vec);
@ -140,16 +161,16 @@ fn timehist(matches: &ArgMatches) {
fn main() { fn main() {
let matches = app::get_app().get_matches(); let matches = app::get_app().get_matches();
let verbose = matches.is_present("verbose"); configure_output(
if let Some(c) = matches.value_of("color") { matches.value_of("color").unwrap(),
disable_color_if_needed(c); matches.is_present("verbose"),
} );
match matches.subcommand() { match matches.subcommand() {
Some(("hist", subcommand_matches)) => { Some(("hist", subcommand_matches)) => {
histogram(subcommand_matches, verbose); histogram(subcommand_matches);
} }
Some(("plot", subcommand_matches)) => { Some(("plot", subcommand_matches)) => {
plot(subcommand_matches, verbose); plot(subcommand_matches);
} }
Some(("matches", subcommand_matches)) => { Some(("matches", subcommand_matches)) => {
matchbar(subcommand_matches); matchbar(subcommand_matches);

47
src/reader.rs

@ -4,7 +4,6 @@ use std::ops::Range;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use regex::Regex; use regex::Regex;
use yansi::Color::{Magenta, Red};
use crate::dateparser::LogDateParser; use crate::dateparser::LogDateParser;
use crate::matchbar::{MatchBar, MatchBarRow}; use crate::matchbar::{MatchBar, MatchBarRow};
@ -15,8 +14,6 @@ pub struct DataReader {
range: Option<Range<f64>>, range: Option<Range<f64>>,
#[builder(setter(strip_option), default)] #[builder(setter(strip_option), default)]
regex: Option<Regex>, regex: Option<Regex>,
#[builder(default)]
verbose: bool,
} }
impl DataReader { impl DataReader {
@ -40,7 +37,7 @@ impl DataReader {
} }
} }
} }
Err(error) => eprintln!("[{}]: {}", Red.paint("ERROR"), error), Err(error) => error!("{}", error),
} }
} }
vec vec
@ -50,14 +47,7 @@ impl DataReader {
match line.parse::<f64>() { match line.parse::<f64>() {
Ok(n) => Some(n), Ok(n) => Some(n),
Err(parse_error) => { Err(parse_error) => {
if self.verbose { debug!("Cannot parse float ({}) at '{}'", parse_error, line);
eprintln!(
"[{}] Cannot parse float ({}) at '{}'",
Red.paint("ERROR"),
parse_error,
line
);
}
None None
} }
} }
@ -75,13 +65,7 @@ impl DataReader {
} }
} }
None => { None => {
if self.verbose { debug!("Regex does not match '{}'", line);
eprintln!(
"[{}] Regex does not match '{}'",
Magenta.paint("DEBUG"),
line
);
}
None None
} }
} }
@ -99,7 +83,7 @@ impl DataReader {
row.inc_if_matches(&as_string); row.inc_if_matches(&as_string);
} }
} }
Err(error) => eprintln!("[{}]: {}", Red.paint("ERROR"), error), Err(error) => error!("{}", error),
} }
} }
MatchBar::new(rows) MatchBar::new(rows)
@ -121,7 +105,7 @@ impl TimeReader {
let first_line = match iterator.next() { let first_line = match iterator.next() {
Some(Ok(as_string)) => as_string, Some(Ok(as_string)) => as_string,
Some(Err(error)) => { Some(Err(error)) => {
eprintln!("[{}]: {}", Red.paint("ERROR"), error); error!("{}", error);
return vec; return vec;
} }
_ => return vec, _ => return vec,
@ -130,22 +114,14 @@ impl TimeReader {
Some(ts_format) => match LogDateParser::new_with_format(&first_line, &ts_format) { Some(ts_format) => match LogDateParser::new_with_format(&first_line, &ts_format) {
Ok(p) => p, Ok(p) => p,
Err(error) => { Err(error) => {
eprintln!( error!("Could not figure out parsing strategy: {}", error);
"[{}]: Could not figure out parsing strategy: {}",
Red.paint("ERROR"),
error
);
return vec; return vec;
} }
}, },
None => match LogDateParser::new_with_guess(&first_line) { None => match LogDateParser::new_with_guess(&first_line) {
Ok(p) => p, Ok(p) => p,
Err(error) => { Err(error) => {
eprintln!( error!("Could not figure out parsing strategy: {}", error);
"[{}]: Could not figure out parsing strategy: {}",
Red.paint("ERROR"),
error
);
return vec; return vec;
} }
}, },
@ -166,7 +142,7 @@ impl TimeReader {
} }
} }
} }
Err(error) => eprintln!("[{}]: {}", Red.paint("ERROR"), error), Err(error) => error!("{}", error),
} }
} }
vec vec
@ -179,12 +155,7 @@ fn open_file(path: &str) -> Box<dyn io::BufRead> {
_ => match File::open(path) { _ => match File::open(path) {
Ok(fd) => Box::new(io::BufReader::new(fd)), Ok(fd) => Box::new(io::BufReader::new(fd)),
Err(error) => { Err(error) => {
eprintln!( error!("Could not open {}: {}", path, error);
"[{}] Could not open {}: {}",
Red.paint("ERROR"),
path,
error
);
std::process::exit(0); std::process::exit(0);
} }
}, },

7
src/timehist.rs

@ -106,13 +106,6 @@ impl fmt::Display for TimeHistogram {
)?; )?;
let fmt = self.date_fmt_string(); let fmt = self.date_fmt_string();
for row in self.vec.iter() { for row in self.vec.iter() {
// println!("ROW");
// println!("COUNT {}", row.count);
// println!("WIDTH {}", row.count / divisor);
// println!("WIDTH2 {:A<width$}", "", width = row.count / divisor);
// println!("LABEL1 {}", row.start);
// println!("LABEFMT {}", self.date_fmt_string());
// println!("LABEL2 {}", row.start.format(self.date_fmt_string()));
writeln!( writeln!(
f, f,
"[{label}] [{count}] {bar}", "[{label}] [{count}] {bar}",

Loading…
Cancel
Save