From d53e29353f8aedb60036a5ed2fd44f1d095e5811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20G=C3=B3rski?= Date: Fri, 25 Dec 2020 17:48:49 +0100 Subject: [PATCH] Added new logging mechanism * Changed logging format * Added flag to specify file which will be used for logs --- Cargo.lock | 94 +++++++++++++++++++++++++++---------------- Cargo.toml | 3 +- src/bin/main.rs | 31 ++++++++++----- src/lib.rs | 3 ++ src/log.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.rs | 24 +++++++++-- 6 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 src/log.rs diff --git a/Cargo.lock b/Cargo.lock index 2aff451..dd41790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,19 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "clap" version = "3.0.0-beta.2" @@ -83,6 +96,17 @@ dependencies = [ "syn", ] +[[package]] +name = "colored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "colored" version = "2.0.0" @@ -107,16 +131,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] -name = "env_logger" -version = "0.7.1" +name = "fern" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" dependencies = [ - "atty", - "humantime", + "colored 1.9.3", "log", - "regex", - "termcolor", ] [[package]] @@ -136,7 +157,7 @@ checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -163,15 +184,6 @@ dependencies = [ "libc", ] -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "indexmap" version = "1.6.0" @@ -221,6 +233,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -271,16 +293,6 @@ dependencies = [ "treeline", ] -[[package]] -name = "pretty_env_logger" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" -dependencies = [ - "env_logger", - "log", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -319,22 +331,17 @@ name = "ptero-cli" version = "0.2.0" dependencies = [ "assert_cmd", + "chrono", "clap", - "colored", + "colored 2.0.0", + "fern", "log", "predicates", - "pretty_env_logger", "rand", "regex", "serde_json", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.7" @@ -470,6 +477,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "treeline" version = "0.1.0" @@ -521,6 +539,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 380d682..2a9ba62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,8 @@ path = "src/bin/main.rs" [dependencies] rand = "0.7.3" log = "0.4" -pretty_env_logger = "0.4.0" +fern = { version = "0.6", features = ["colored"] } +chrono = "0.4" clap = "3.0.0-beta.2" regex = "1.4.2" colored = "2" diff --git a/src/bin/main.rs b/src/bin/main.rs index ba7d33d..084ba76 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -12,6 +12,7 @@ use ptero::{ encoder::{encode_command, EncodeSubCommand}, writer::get_writer, }, + log::{get_file_logger, get_stdout_logger, verbosity_to_level_filter}, }; const BANNER: &str = r#" @@ -48,8 +49,15 @@ struct Opts { /// By default only error logs are printed. #[clap(short, parse(from_occurrences))] verbose: u8, + /// If present, will print the output of the CLI in JSON format that can be further parsed by other tooling. #[clap(long)] json: bool, + /// Path to log file. + /// + /// By default CLI won't save any logs. If this param is used, CLI will append new logs at the end of the file + /// pointed by the path. It is not affected by the `verbose` flag, and saves all the entries (starting from `TRACE`). + #[clap(long)] + log_file: Option, } #[derive(Clap)] @@ -62,16 +70,21 @@ enum SubCommand { GetCapacity(GetCapacityCommand), } -fn enable_logging(verbose: u8) { - let logging_level = match verbose { - 0 => "info", - 1 => "debug", - _ => "trace", +fn enable_logging( + verbose: u8, + log_path: Option, +) -> std::result::Result<(), Box> { + let level_filter = verbosity_to_level_filter(verbose); + let mut log_builder = fern::Dispatch::new().chain(get_stdout_logger(level_filter)); + + log_builder = if let Some(path) = log_path { + log_builder.chain(get_file_logger(&path)) + } else { + log_builder }; - pretty_env_logger::formatted_builder() - .parse_filters(logging_level) - .init(); + log_builder.apply()?; + Ok(()) } fn run_subcommand(subcommand: SubCommand) -> Result> { @@ -95,7 +108,7 @@ fn run_subcommand(subcommand: SubCommand) -> Result> { fn main() -> Result<(), Box> { let opts: Opts = Opts::parse(); - enable_logging(opts.verbose); + enable_logging(opts.verbose, opts.log_file)?; let writer = get_writer(opts.json); writer.message(BANNER); diff --git a/src/lib.rs b/src/lib.rs index 2ba5211..e8274fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub mod context; /// Module containing all the available methods for text steganography. pub mod method; +/// Logger utilities. +pub mod log; + pub mod cli { pub mod capacity; pub mod decoder; diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..7a2d589 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,103 @@ +use fern::{ + colors::{Color, ColoredLevelConfig}, + Dispatch, +}; +use log::{Level, LevelFilter}; + +/// Converts verbosity number to [LevelFilter](log::LevelFilter) enum. +/// Used for configuring the logging level. +/// # Arguments +/// +/// * `verbosity` - verbosity level described by number `u8` +/// +/// # Examples +/// ## Converts verbosity number +/// ``` +/// use ptero::log::verbosity_to_level_filter; +/// use log::{LevelFilter}; +/// +/// assert_eq!(verbosity_to_level_filter(0), LevelFilter::Info); +/// assert_eq!(verbosity_to_level_filter(1), LevelFilter::Debug); +/// assert_eq!(verbosity_to_level_filter(2), LevelFilter::Trace); +/// ``` +/// ## Unrecognized verbosity defaults to trace +/// ``` +/// use ptero::log::verbosity_to_level_filter; +/// use log::{LevelFilter}; +/// +/// assert_eq!(verbosity_to_level_filter(3), LevelFilter::Trace); +/// assert_eq!(verbosity_to_level_filter(100), LevelFilter::Trace); +/// assert_eq!(verbosity_to_level_filter(255), LevelFilter::Trace); +/// ``` +pub fn verbosity_to_level_filter(verbosity: u8) -> LevelFilter { + match verbosity { + 0 => LevelFilter::Info, + 1 => LevelFilter::Debug, + _ => LevelFilter::Trace, + } +} + +/// Returns pre-configured [ColoredLevelConfig](fern::colors::ColoredLevelConfig) used to color +/// logging level. +fn get_logging_colors() -> ColoredLevelConfig { + ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::BrightYellow) + .debug(Color::Magenta) + .trace(Color::BrightBlack) +} + +/// Returns text which will be shown before the message. Used only in stdout formatter. +fn get_level_text(level: &Level) -> &str { + match level { + Level::Error => "ERROR", + Level::Warn => " WARN", + Level::Info => " ", + Level::Debug => "DEBUG", + Level::Trace => "TRACE", + } +} + +/// Returns pre-configured stdout logger. +/// It only shows info relevant to user like message and logging level. +/// Uses coloring unlike file logger. +/// +/// # Arguments +/// * `log_level` - level filter which is used to restrict amount of logs to user +pub fn get_stdout_logger(log_level: LevelFilter) -> Dispatch { + let colors = get_logging_colors(); + + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{color_line}{level_txt}\x1B[0m {message}", + level_txt = get_level_text(&record.level()), + color_line = + format_args!("\x1B[{}m", colors.get_color(&record.level()).to_fg_str()), + message = message, + )); + }) + .level(log_level) + .chain(std::io::stderr()) +} + +/// Returns pre-configured file logger. +/// This logger does not used coloring and adds additional info like date time or module path. +/// It doesn't restrict logging - saves everything beginning from `TRACE` level. +/// +/// # Arguments +/// * `log_path` - path to the log file which will be used to store logs +pub fn get_file_logger(log_path: &str) -> Dispatch { + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] - {}", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + &record.target(), + &record.level(), + message, + )); + }) + .level(LevelFilter::Trace) + .chain(fern::log_file(&log_path).unwrap()) +} diff --git a/tests/utils.rs b/tests/utils.rs index a67793f..8c2acbe 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,15 +1,27 @@ use std::{error::Error, path::PathBuf, sync::Once}; use assert_cmd::Command; +use log::LevelFilter; use serde_json::Value; static INIT: Once = Once::new(); pub fn global_setup() { INIT.call_once(|| { - pretty_env_logger::formatted_builder() - .parse_filters("debug") - .init(); + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] - {}", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + &record.target(), + &record.level(), + message, + )); + }) + .level(LevelFilter::Trace) + .chain(std::io::stdout()) + .apply() + .unwrap(); }); } @@ -45,7 +57,11 @@ pub fn run_encode_command( Ok(json_struct) } -pub fn run_decode_command(stego_text: &PathBuf, pivot: usize, output_path: Option<&PathBuf>) -> Result> { +pub fn run_decode_command( + stego_text: &PathBuf, + pivot: usize, + output_path: Option<&PathBuf>, +) -> Result> { let mut cmd = Command::cargo_bin("ptero_cli").unwrap(); if let Some(path) = output_path { cmd.arg("-o").arg(path);