Replace simplelog with tracing_subscriber::fmt (#525)

* Disable feat log-always of dep tracing
* Add dep tracing-log 0.1.3 with no feat
* Add new dep tracing-appender v0.2.2
* Add dep tracing-subscriber 0.3.16 with feat fmt and json
* Fix `MainExit::report`: Do not use `log::{error, warn}`
  since `MainExit::report` might be called with no `log`ger, it can only
  use `println!` and `eprintln!`.
* Use `tracing_subscriber::fmt` instead of `simple_log`
* Rm unused dep simplelog from crates/bin
* Fix `BinstallError::report`: Avoid `log::{warn, error}`
   since they might be called after `tracing_appender::WorkerGuard` is
   dropped.
* Make tracing output more readable to end users
* Add new dep tracing-core v0.1.30
* Add new dep once_cell v1.16.0
* Refactor: Extract new mod `logging`
* Add new option `Args::json_output`
* Fix `MainExit::report`: Ignore io error
* Fix `BinstallError::report`: Ignore IO error

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2022-11-11 10:05:09 +11:00 committed by GitHub
parent e378be73df
commit 3841762a5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 290 additions and 59 deletions

View file

@ -29,11 +29,15 @@ dirs = "4.0.0"
log = "0.4.17"
miette = "5.4.1"
mimalloc = { version = "0.1.31", default-features = false, optional = true }
once_cell = "1.16.0"
semver = "1.0.14"
simplelog = "0.12.0"
tempfile = "3.3.0"
tokio = { version = "1.21.2", features = ["rt-multi-thread"], default-features = false }
tracing = { version = "0.1.37", features = ["log-always"], default-features = false }
tracing-core = "0.1.30"
tracing = { version = "0.1.37", default-features = false }
tracing-log = { version = "0.1.3", default-features = false }
tracing-appender = "0.2.2"
tracing-subscriber = { version = "0.3.16", features = ["fmt", "json"], default-features = false }
[build-dependencies]
embed-resource = "1.7.4"

View file

@ -188,6 +188,10 @@ pub struct Args {
#[clap(help_heading = "Options", long, value_enum, value_name = "VERSION")]
pub min_tls_version: Option<TLSVersion>,
/// Print logs in json format to be parsable.
#[clap(help_heading = "Options", long)]
pub json_output: bool,
/// Print version information
#[clap(help_heading = "Meta", short = 'V')]
pub version: bool,

View file

@ -1,12 +1,12 @@
use std::{
future::Future,
io::{self, Write},
process::{ExitCode, Termination},
time::Duration,
};
use binstalk::errors::BinstallError;
use binstalk::helpers::{signal::cancel_on_user_sig_term, tasks::AutoAbortJoinHandle};
use log::{error, info};
use miette::Result;
use tokio::runtime::Runtime;
@ -21,13 +21,13 @@ impl Termination for MainExit {
match self {
Self::Success(spent) => {
if let Some(spent) = spent {
info!("Done in {spent:?}");
writeln!(io::stdout(), "Done in {spent:?}").ok();
}
ExitCode::SUCCESS
}
Self::Error(err) => err.report(),
Self::Report(err) => {
error!("Fatal error:\n{err:?}");
writeln!(io::stderr(), "Fatal error:\n{err:?}").ok();
ExitCode::from(16)
}
}

View file

@ -2,4 +2,5 @@ pub mod args;
pub mod bin_util;
pub mod entry;
pub mod install_path;
pub mod logging;
pub mod ui;

183
crates/bin/src/logging.rs Normal file
View file

@ -0,0 +1,183 @@
use std::{cmp::min, io, iter::repeat};
use log::{LevelFilter, Log, STATIC_MAX_LEVEL};
use once_cell::sync::Lazy;
use tracing::{
callsite::Callsite,
dispatcher, field,
subscriber::{self, set_global_default},
Event, Level, Metadata,
};
use tracing_appender::non_blocking::{NonBlockingBuilder, WorkerGuard};
use tracing_core::{identify_callsite, metadata::Kind, subscriber::Subscriber};
use tracing_log::AsTrace;
use tracing_subscriber::{filter::targets::Targets, fmt::fmt, layer::SubscriberExt};
use crate::args::Args;
// Shamelessly taken from tracing-log
struct Fields {
message: field::Field,
}
static FIELD_NAMES: &[&str] = &["message"];
impl Fields {
fn new(cs: &'static dyn Callsite) -> Self {
let fieldset = cs.metadata().fields();
let message = fieldset.field("message").unwrap();
Fields { message }
}
}
macro_rules! log_cs {
($level:expr, $cs:ident, $meta:ident, $fields:ident, $ty:ident) => {
struct $ty;
static $cs: $ty = $ty;
static $meta: Metadata<'static> = Metadata::new(
"log event",
"log",
$level,
None,
None,
None,
field::FieldSet::new(FIELD_NAMES, identify_callsite!(&$cs)),
Kind::EVENT,
);
static $fields: Lazy<Fields> = Lazy::new(|| Fields::new(&$cs));
impl Callsite for $ty {
fn set_interest(&self, _: subscriber::Interest) {}
fn metadata(&self) -> &'static Metadata<'static> {
&$meta
}
}
};
}
log_cs!(
Level::TRACE,
TRACE_CS,
TRACE_META,
TRACE_FIELDS,
TraceCallsite
);
log_cs!(
Level::DEBUG,
DEBUG_CS,
DEBUG_META,
DEBUG_FIELDS,
DebugCallsite
);
log_cs!(Level::INFO, INFO_CS, INFO_META, INFO_FIELDS, InfoCallsite);
log_cs!(Level::WARN, WARN_CS, WARN_META, WARN_FIELDS, WarnCallsite);
log_cs!(
Level::ERROR,
ERROR_CS,
ERROR_META,
ERROR_FIELDS,
ErrorCallsite
);
fn loglevel_to_cs(level: log::Level) -> (&'static Fields, &'static Metadata<'static>) {
match level {
log::Level::Trace => (&*TRACE_FIELDS, &TRACE_META),
log::Level::Debug => (&*DEBUG_FIELDS, &DEBUG_META),
log::Level::Info => (&*INFO_FIELDS, &INFO_META),
log::Level::Warn => (&*WARN_FIELDS, &WARN_META),
log::Level::Error => (&*ERROR_FIELDS, &ERROR_META),
}
}
struct Logger;
impl Logger {
fn init(log_level: LevelFilter) {
log::set_max_level(log_level);
log::set_logger(&Self).unwrap();
}
}
impl Log for Logger {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
if metadata.level() > log::max_level() {
// First, check the log record against the current max level enabled.
false
} else {
// Check if the current `tracing` dispatcher cares about this.
dispatcher::get_default(|dispatch| dispatch.enabled(&metadata.as_trace()))
}
}
fn log(&self, record: &log::Record<'_>) {
// Dispatch manually instead of using methods provided by tracing-log
// to avoid having fields "log.target = ..." in the log message,
// which makes the log really hard to read.
if self.enabled(record.metadata()) {
dispatcher::get_default(|dispatch| {
let (keys, meta) = loglevel_to_cs(record.level());
dispatch.event(&Event::new(
meta,
&meta
.fields()
.value_set(&[(&keys.message, Some(record.args() as &dyn field::Value))]),
));
});
}
}
fn flush(&self) {}
}
pub fn logging(args: &Args) -> WorkerGuard {
// Calculate log_level
let log_level = min(args.log_level, STATIC_MAX_LEVEL);
let allowed_targets =
(log_level != LevelFilter::Trace).then_some(["binstalk", "cargo_binstall"]);
// Forward log to tracing
Logger::init(log_level);
// Setup non-blocking stdout
let (non_blocking, guard) = NonBlockingBuilder::default()
.lossy(false)
.finish(io::stdout());
// Build fmt subscriber
let log_level = log_level.as_trace();
let subscriber_builder = fmt().with_writer(non_blocking).with_max_level(log_level);
let subscriber: Box<dyn Subscriber + Send + Sync> = if args.json_output {
Box::new(subscriber_builder.json().finish())
} else {
Box::new(
subscriber_builder
.compact()
// Disable time, target, file, line_num, thread name/ids to make the
// output more readable
.without_time()
.with_target(false)
.with_file(false)
.with_line_number(false)
.with_thread_names(false)
.with_thread_ids(false)
.finish(),
)
};
// Builder layer for filtering
let filter_layer = allowed_targets.map(|allowed_targets| {
Targets::new().with_targets(allowed_targets.into_iter().zip(repeat(log_level)))
});
// Builder final subscriber with filtering
let subscriber = subscriber.with(filter_layer);
// Setup global subscriber
set_global_default(subscriber).unwrap();
guard
}

View file

@ -6,7 +6,8 @@ use log::debug;
use cargo_binstall::{
args,
bin_util::{run_tokio_main, MainExit},
entry, ui,
entry,
logging::logging,
};
#[cfg(feature = "mimalloc")]
@ -26,7 +27,7 @@ fn main() -> MainExit {
println!("{}", env!("CARGO_PKG_VERSION"));
MainExit::Success(None)
} else {
ui::logging(&args);
let _guard = logging(&args);
let start = Instant::now();

View file

@ -1,16 +1,10 @@
use std::{
cmp::min,
io::{self, BufRead, Write},
thread,
};
use log::{LevelFilter, STATIC_MAX_LEVEL};
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use tokio::sync::mpsc;
use binstalk::errors::BinstallError;
use crate::args::Args;
use tokio::sync::mpsc;
#[derive(Debug)]
struct UIThreadInner {
@ -102,24 +96,3 @@ impl UIThread {
}
}
}
pub fn logging(args: &Args) {
let log_level = min(args.log_level, STATIC_MAX_LEVEL);
// Setup logging
let mut log_config = ConfigBuilder::new();
if log_level != LevelFilter::Trace {
log_config.add_filter_allow_str("binstalk");
log_config.add_filter_allow_str("cargo_binstall");
}
log_config.set_location_level(LevelFilter::Off);
TermLogger::init(
log_level,
log_config.build(),
TerminalMode::Mixed,
ColorChoice::Auto,
)
.unwrap();
}

View file

@ -1,11 +1,10 @@
use std::{
io,
io::{self, Write},
path::PathBuf,
process::{ExitCode, ExitStatus, Termination},
};
use compact_str::CompactString;
use log::{error, warn};
use miette::{Diagnostic, Report};
use thiserror::Error;
use tokio::task;
@ -383,10 +382,9 @@ impl Termination for BinstallError {
fn report(self) -> ExitCode {
let code = self.exit_code();
if let BinstallError::UserAbort = self {
warn!("Installation cancelled");
writeln!(io::stdout(), "Installation cancelled").ok();
} else {
error!("Fatal error:");
eprintln!("{:?}", Report::new(self));
writeln!(io::stderr(), "Fatal error:\n{:?}", Report::new(self)).ok();
}
code