use std::env;
use std::marker::PhantomData;
use log::{kv, LevelFilter, SetLoggerError};
use crate::format::{Format, Gcloud, LogFmt};
use crate::{Logger, Targets};
#[derive(Debug)]
pub struct Config<F, Kvs> {
filter: LevelFilter,
targets: Targets,
kvs: Kvs,
_format: PhantomData<F>,
}
impl Config<(), NoKvs> {
pub fn logfmt() -> Config<LogFmt, NoKvs> {
Config::new(NoKvs)
}
pub fn gcloud() -> Config<Gcloud, NoKvs> {
Config::new(NoKvs)
}
}
impl<F, Kvs> Config<F, Kvs>
where
F: Format + Send + Sync + 'static,
Kvs: kv::Source + Send + Sync + 'static,
{
fn new(kvs: Kvs) -> Config<F, Kvs> {
Config {
filter: get_max_level(),
targets: get_log_targets(),
kvs,
_format: PhantomData,
}
}
pub fn with_kvs<K>(self, kvs: K) -> Config<F, K>
where
K: kv::Source + Send + Sync + 'static,
{
Config {
filter: self.filter,
targets: self.targets,
kvs,
_format: self._format,
}
}
pub fn init(self) {
self.try_init()
.unwrap_or_else(|err| panic!("failed to initialise the logger: {}", err));
}
pub fn try_init(self) -> Result<(), SetLoggerError> {
let logger = Box::new(Logger {
filter: self.filter,
targets: self.targets,
kvs: self.kvs,
_format: self._format,
});
log::set_boxed_logger(logger)?;
log::set_max_level(self.filter);
#[cfg(all(feature = "log-panic", not(feature = "nightly")))]
log_panics::init();
#[cfg(all(feature = "log-panic", feature = "nightly"))]
std::panic::set_hook(Box::new(log_panic));
Ok(())
}
}
pub(crate) fn get_max_level() -> LevelFilter {
for var in &["LOG", "LOG_LEVEL"] {
if let Ok(level) = env::var(var) {
if let Ok(level) = level.parse() {
return level;
}
}
}
if env::var("TRACE").is_ok() {
LevelFilter::Trace
} else if env::var("DEBUG").is_ok() {
LevelFilter::Debug
} else {
LevelFilter::Info
}
}
pub(crate) fn get_log_targets() -> Targets {
match env::var("LOG_TARGET") {
Ok(ref targets) if !targets.is_empty() => {
Targets::Only(targets.split(',').map(|target| target.into()).collect())
}
_ => Targets::All,
}
}
#[cfg(all(feature = "log-panic", feature = "nightly"))]
fn log_panic(info: &std::panic::PanicInfo<'_>) {
use std::backtrace::Backtrace;
use std::thread;
let thread = thread::current();
let thread_name = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "<unknown>",
},
};
let (file, line) = match info.location() {
Some(location) => (location.file(), location.line()),
None => ("<unknown>", 0),
};
let backtrace = Backtrace::force_capture();
log::logger().log(
&log::Record::builder()
.args(format_args!(
"thread '{}' panicked at '{}', {}:{}",
thread_name, msg, file, line
))
.level(log::Level::Error)
.target("panic")
.file(Some(file))
.line(Some(line))
.key_values(&("backtrace", &backtrace as &dyn std::fmt::Display))
.build(),
);
}
#[derive(Debug)]
pub struct NoKvs;
impl kv::Source for NoKvs {
fn visit<'kvs>(&'kvs self, _: &mut dyn log::kv::Visitor<'kvs>) -> Result<(), log::kv::Error> {
Ok(())
}
fn get<'v>(&'v self, _: log::kv::Key<'_>) -> Option<log::kv::Value<'v>> {
None
}
fn count(&self) -> usize {
0
}
}