1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
#[cfg(test)]
mod tests;
/// Re-export all of tracing for downstream consumers.
pub use tracing;
use atty::Stream;
use std::io;
use tracing::{
dispatcher::SetGlobalDefaultError, subscriber::set_global_default,
};
use tracing_subscriber::{
fmt, layer::SubscriberExt, EnvFilter, Layer, Registry,
};
/// Basic invocation. Simply call `jacklog::init(Some("mybin=debug"))` to
/// automatically set a default log level, that can be overridden via the usual
/// `RUST_LOG` env var.
///
/// All logs will go to STDERR. If jacklog detects that STDERR is NOT a TTY,
/// then logs will be output as JSON, ready to be parsed by log aggregators. If
/// STDERR IS a TTY, then logs will be output nicely formatted.
///
/// # Errors
///
/// Will return an error if there's an issue setting the global default subscriber.
pub fn init<T: AsRef<str>>(
default_level: Option<&T>,
) -> Result<(), SetGlobalDefaultError> {
let level_layer = parse_from_env(default_level);
start(Some(level_layer))
}
/// Set the log level using 0-indexed integers, starting from 0=off. Especially
/// useful if writing a CLI and accepting verbosity settings via number of
/// flags (-v, -vv, -vvv, etc.). Will be overridden via the usual `RUST_LOG`
/// env var, if it's present.
///
/// In what is probably the most common use case for this method, where a
/// repeating flag is used to increase verbosity, do the following math for the
/// behavior you want:
///
/// Default of warn, with increasing verbosity when number of -v arguments
/// is one or more:
///
/// 2 + (number of -v flags)
///
/// Default of no logging, with increasing verbosity when number of -v arguments
/// is one or more:
///
/// 0 + (number of -v flags)
///
/// Default of info, with increasing verbosity when number of -v arguments
/// is one or more:
///
/// 3 + (number of -v flags)
///
/// You probably get the idea.
///
/// # Errors
///
/// Will return an error if there's an issue setting the global subscriber.
pub fn from_level(
level: u8,
crates: Option<&[&str]>,
) -> Result<(), SetGlobalDefaultError> {
// Figure out which filter to use.
let level_layer = env_filter_from_level_and_crates(level, crates);
start(Some(level_layer))
}
/// Encapsulate the common options we always use when starting the logging.
fn start(level_layer: Option<EnvFilter>) -> Result<(), SetGlobalDefaultError> {
// If stderr is a tty, then we'll print nicely human-formatted,
// colorized text.
let format_layer = if atty::is(Stream::Stderr) {
fmt::layer().with_ansi(true).with_writer(io::stderr).boxed()
// Otherwise, we'll use uncolored JSON. This is opinionated, but hey, that's
// what this crate is for.
} else {
fmt::layer().json().with_writer(io::stderr).boxed()
};
// Stack up all our layers.
let subscriber = Registry::default().with(format_layer).with(level_layer);
set_global_default(subscriber)
}
/// Figure out the level based on a 0-indexed hierarchy, with 0 being "off".
fn env_filter_from_level_and_crates(
level: u8,
crates: Option<&[&str]>,
) -> EnvFilter {
// Convert the integer level to a string level name.
let level = match level {
0 => "off",
1 => "error",
2 => "warn",
3 => "info",
4 => "debug",
_ => "trace",
};
let level = if let Some(crates) = crates {
crates
.iter()
.map(|c| format!("{c}={level}"))
.collect::<Vec<String>>()
.join(",")
} else {
level.to_string()
};
// Set up the filter to use, defaulting to the level passed in.
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level))
}
fn parse_from_env<T: AsRef<str>>(default_level: Option<&T>) -> EnvFilter {
// Set up the filter to use, defaulting to just the warn log level for all crates/modules.
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
EnvFilter::new(default_level.map_or("warn", AsRef::as_ref))
})
}