diff --git a/Cargo.lock b/Cargo.lock index 1f831a3232..0e67c74932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1070,19 +1070,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.8.4" @@ -1658,15 +1645,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "hyper" version = "0.14.20" @@ -2529,13 +2507,14 @@ dependencies = [ "nu-utils", "openssl", "pretty_assertions", - "pretty_env_logger", "rayon", "reedline", "rstest", "serial_test", "signal-hook", + "simplelog", "tempfile", + "time 0.3.13", "winres", ] @@ -3608,16 +3587,6 @@ dependencies = [ "output_vt100", ] -[[package]] -name = "pretty_env_logger" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" -dependencies = [ - "env_logger 0.7.1", - "log", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3713,7 +3682,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "env_logger 0.8.4", + "env_logger", "log", "rand 0.8.5", ] @@ -4431,7 +4400,7 @@ checksum = "b2d399ad15b5c90d8e6461da75c751c77501598dd915d81a108401b252aaa99f" dependencies = [ "const_format", "is_debug", - "time 0.3.11", + "time 0.3.13", "tzdb", ] @@ -4477,6 +4446,17 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "simplelog" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786" +dependencies = [ + "log", + "termcolor", + "time 0.3.13", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -4889,15 +4869,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ "itoa 1.0.2", "libc", "num_threads", + "time-macros", ] +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 505fe905a7..7bad4cf3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,10 @@ nu-table = { path = "./crates/nu-table", version = "0.66.4" } nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.4" } nu-utils = { path = "./crates/nu-utils", version = "0.66.4" } reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]} -pretty_env_logger = "0.4.0" rayon = "1.5.1" is_executable = "1.0.1" +simplelog = "0.12.0" +time = "0.3.12" [target.'cfg(not(target_os = "windows"))'.dependencies] # Our dependencies don't use OpenSSL on Windows diff --git a/src/logger.rs b/src/logger.rs index 12db2ac0e0..c7a7f7f818 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,118 +1,117 @@ -use chrono::{DateTime, Local}; -use core::fmt; -use log::Level; -use log::LevelFilter; +use log::{Level, LevelFilter, SetLoggerError}; use nu_protocol::ShellError; -use pretty_env_logger::env_logger::fmt::Color; -use pretty_env_logger::env_logger::Builder; -use std::io::Write; -use std::sync::atomic::{AtomicUsize, Ordering}; +use simplelog::{ + format_description, Color, ColorChoice, Config, ConfigBuilder, LevelPadding, TermLogger, + TerminalMode, WriteLogger, +}; -pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> { - let mut builder = my_formatted_timed_builder(); - f(&mut builder)?; - let _ = builder.try_init(); - Ok(()) +use std::{fs::File, path::Path, str::FromStr}; + +pub enum LogTarget { + Stdout, + Stderr, + Mixed, + File, } -pub fn my_formatted_timed_builder() -> Builder { - let mut builder = Builder::new(); - - builder.format(|f, record| { - let target = record.target(); - let max_width = max_target_width(target); - - let mut style = f.style(); - let level = colored_level(&mut style, record.level()); - - let mut style = f.style(); - let target = style.set_bold(true).value(Padded { - value: target, - width: max_width, - }); - - let dt = match DateTime::parse_from_rfc3339(&f.timestamp_millis().to_string()) { - Ok(d) => d.with_timezone(&Local), - Err(_) => Local::now(), - }; - let time = dt.format("%Y-%m-%d %I:%M:%S%.3f %p"); - writeln!(f, "{}|{}|{}|{}", time, level, target, record.args(),) - }); - - builder +impl From<&str> for LogTarget { + fn from(s: &str) -> Self { + match s { + "stdout" => Self::Stdout, + "mixed" => Self::Mixed, + "file" => Self::File, + _ => Self::Stderr, + } + } } -pub fn configure(level: &str, logger: &mut Builder) -> Result<(), ShellError> { - let level = match level { - "error" => LevelFilter::Error, - "warn" => LevelFilter::Warn, - "info" => LevelFilter::Info, - "debug" => LevelFilter::Debug, - "trace" => LevelFilter::Trace, - _ => LevelFilter::Warn, +pub fn logger( + f: impl FnOnce(&mut ConfigBuilder) -> (LevelFilter, LogTarget), +) -> Result<(), ShellError> { + let mut builder = ConfigBuilder::new(); + let (level, target) = f(&mut builder); + + let config = builder.build(); + let _ = match target { + LogTarget::Stdout => { + TermLogger::init(level, config, TerminalMode::Stdout, ColorChoice::Auto) + } + LogTarget::Mixed => TermLogger::init(level, config, TerminalMode::Mixed, ColorChoice::Auto), + LogTarget::File => { + let pid = std::process::id(); + let mut path = std::env::temp_dir(); + path.push(format!("nu-{}.log", pid)); + + set_write_logger(level, config, &path) + } + _ => TermLogger::init(level, config, TerminalMode::Stderr, ColorChoice::Auto), }; - logger.filter_module("nu", level); - - if let Ok(s) = std::env::var("RUST_LOG") { - logger.parse_filters(&s); - } - Ok(()) } -// pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> { -// if let Some(filters) = app.develop() { -// filters.into_iter().filter_map(Result::ok).for_each(|name| { -// logger.filter_module(&name, LevelFilter::Trace); -// }) -// } +fn set_write_logger(level: LevelFilter, config: Config, path: &Path) -> Result<(), SetLoggerError> { + // Use TermLogger instead if WriteLogger is not available + match File::create(path) { + Ok(file) => WriteLogger::init(level, config, file), + Err(_) => { + let default_logger = + TermLogger::init(level, config, TerminalMode::Stderr, ColorChoice::Auto); -// Ok(()) -// } + if default_logger.is_ok() { + log::warn!("failed to init WriteLogger, use TermLogger instead"); + } -// pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> { -// if let Some(filters) = app.debug() { -// filters.into_iter().filter_map(Result::ok).for_each(|name| { -// logger.filter_module(&name, LevelFilter::Debug); -// }) -// } - -// Ok(()) -// } - -fn colored_level<'a>( - style: &'a mut pretty_env_logger::env_logger::fmt::Style, - level: Level, -) -> pretty_env_logger::env_logger::fmt::StyledValue<'a, &'static str> { - match level { - Level::Trace => style.set_color(Color::Magenta).value("TRACE"), - Level::Debug => style.set_color(Color::Blue).value("DEBUG"), - Level::Info => style.set_color(Color::Green).value("INFO "), - Level::Warn => style.set_color(Color::Yellow).value("WARN "), - Level::Error => style.set_color(Color::Red).value("ERROR"), + default_logger + } } } -static MAX_MODULE_WIDTH: AtomicUsize = AtomicUsize::new(0); +pub fn configure( + level: &str, + target: &str, + builder: &mut ConfigBuilder, +) -> (LevelFilter, LogTarget) { + let level = match Level::from_str(level) { + Ok(level) => level, + Err(_) => Level::Warn, + }; -fn max_target_width(target: &str) -> usize { - let max_width = MAX_MODULE_WIDTH.load(Ordering::Relaxed); - if max_width < target.len() { - MAX_MODULE_WIDTH.store(target.len(), Ordering::Relaxed); - target.len() - } else { - max_width + // Add allowed module filter + builder.add_filter_allow_str("nu"); + + // Set level padding + builder.set_level_padding(LevelPadding::Right); + + // Custom time format + builder.set_time_format_custom(format_description!( + "[year]-[month]-[day] [hour repr:12]:[minute]:[second].[subsecond digits:3] [period]" + )); + + // Show module path + builder.set_target_level(LevelFilter::Error); + + // Don't show thread id + builder.set_thread_level(LevelFilter::Off); + + let log_target = LogTarget::from(target); + + // Only TermLogger supports color output + if !matches!(log_target, LogTarget::File) { + set_colored_level(builder, level); } + + (level.to_level_filter(), log_target) } -struct Padded { - value: T, - width: usize, -} +fn set_colored_level(builder: &mut ConfigBuilder, level: Level) { + let color = match level { + Level::Trace => Color::Magenta, + Level::Debug => Color::Blue, + Level::Info => Color::Green, + Level::Warn => Color::Yellow, + Level::Error => Color::Red, + }; -impl fmt::Display for Padded { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{: Result<()> { "--config" | "--env-config" => args.next().map(|a| escape_quote_string(&a)), #[cfg(feature = "plugin")] "--plugin-config" => args.next().map(|a| escape_quote_string(&a)), - "--log-level" | "--testbin" | "--threads" | "-t" => args.next(), + "--log-level" | "--log-target" | "--testbin" | "--threads" | "-t" => args.next(), _ => None, }; @@ -157,10 +157,12 @@ fn main() -> Result<()> { .map(|level| level.item) .unwrap_or_else(|| "info".to_string()); - logger(|builder| { - configure(level.as_str(), builder)?; - Ok(()) - })?; + let target = binary_args + .log_target + .map(|target| target.item) + .unwrap_or_else(|| "stderr".to_string()); + + logger(|builder| configure(level.as_str(), target.as_str(), builder))?; info!("start logging {}:{}:{}", file!(), line!(), column!()); } @@ -435,6 +437,7 @@ fn parse_commandline_args( let config_file: Option = call.get_flag_expr("config"); let env_file: Option = call.get_flag_expr("env-config"); let log_level: Option = call.get_flag_expr("log-level"); + let log_target: Option = call.get_flag_expr("log-target"); let threads: Option = call.get_flag(engine_state, &mut stack, "threads")?; let table_mode: Option = call.get_flag(engine_state, &mut stack, "table-mode")?; @@ -464,6 +467,7 @@ fn parse_commandline_args( let config_file = extract_contents(config_file)?; let env_file = extract_contents(env_file)?; let log_level = extract_contents(log_level)?; + let log_target = extract_contents(log_target)?; let help = call.has_flag("help"); @@ -496,6 +500,7 @@ fn parse_commandline_args( config_file, env_file, log_level, + log_target, perf, threads, table_mode, @@ -521,6 +526,7 @@ struct NushellCliArgs { config_file: Option>, env_file: Option>, log_level: Option>, + log_target: Option>, perf: bool, threads: Option, table_mode: Option, @@ -576,6 +582,12 @@ impl Command for Nu { "log level for performance logs", None, ) + .named( + "log-target", + SyntaxShape::String, + "set the target for the log to output. stdout, stderr(default), mixed or file", + None, + ) .named( "threads", SyntaxShape::Int,