use crate::api::add_breadcrumb;
use crate::backtrace_support::current_stacktrace;
use crate::hub::Hub;
use crate::protocol::{Breadcrumb, Event, Exception, Level};
pub struct LoggerOptions {
pub global_filter: Option<log::LevelFilter>,
pub filter: log::LevelFilter,
pub emit_breadcrumbs: bool,
pub emit_error_events: bool,
pub emit_warning_events: bool,
pub attach_stacktraces: bool,
}
impl Default for LoggerOptions {
fn default() -> LoggerOptions {
LoggerOptions {
global_filter: None,
filter: log::LevelFilter::Info,
emit_breadcrumbs: true,
emit_error_events: true,
emit_warning_events: false,
attach_stacktraces: true,
}
}
}
impl LoggerOptions {
#[inline(always)]
fn effective_global_filter(&self) -> log::LevelFilter {
let filter = if let Some(filter) = self.global_filter {
if filter < self.filter {
self.filter
} else {
filter
}
} else {
self.filter
};
std::cmp::max(filter, self.issue_filter())
}
#[inline(always)]
fn issue_filter(&self) -> log::LevelFilter {
if self.emit_warning_events {
log::LevelFilter::Warn
} else if self.emit_error_events {
log::LevelFilter::Error
} else {
log::LevelFilter::Off
}
}
fn create_issue_for_record(&self, record: &log::Record<'_>) -> bool {
match record.level() {
log::Level::Warn => self.emit_warning_events,
log::Level::Error => self.emit_error_events,
_ => false,
}
}
}
pub struct Logger {
dest: Option<Box<dyn log::Log>>,
options: LoggerOptions,
}
impl Logger {
pub fn new(dest: Option<Box<dyn log::Log>>, options: LoggerOptions) -> Logger {
Logger { dest, options }
}
pub fn options(&self) -> &LoggerOptions {
&self.options
}
pub fn dest_log(&self) -> Option<&dyn log::Log> {
self.dest.as_ref().map(|x| &**x)
}
}
pub fn breadcrumb_from_record(record: &log::Record<'_>) -> Breadcrumb {
Breadcrumb {
ty: "log".into(),
level: convert_log_level(record.level()),
category: Some(record.target().into()),
message: Some(format!("{}", record.args())),
..Default::default()
}
}
pub fn event_from_record(record: &log::Record<'_>, with_stacktrace: bool) -> Event<'static> {
Event {
logger: Some(record.target().into()),
level: convert_log_level(record.level()),
exception: vec![Exception {
ty: record.target().into(),
value: Some(format!("{}", record.args())),
stacktrace: if with_stacktrace {
current_stacktrace()
} else {
None
},
..Default::default()
}]
.into(),
..Default::default()
}
}
impl log::Log for Logger {
fn enabled(&self, md: &log::Metadata<'_>) -> bool {
if let Some(global_filter) = self.options.global_filter {
if md.level() > global_filter {
return false;
}
}
md.level() <= self.options.filter || self.dest.as_ref().map_or(false, |x| x.enabled(md))
}
fn log(&self, record: &log::Record<'_>) {
if self.options.create_issue_for_record(record) {
Hub::with_active(|hub| {
hub.capture_event(event_from_record(record, self.options.attach_stacktraces))
});
}
if self.options.emit_breadcrumbs && record.level() <= self.options.filter {
add_breadcrumb(|| breadcrumb_from_record(record))
}
if let Some(ref log) = self.dest {
if log.enabled(record.metadata()) {
log.log(record);
}
}
}
fn flush(&self) {
if let Some(ref log) = self.dest {
log.flush();
}
}
}
fn convert_log_level(level: log::Level) -> Level {
match level {
log::Level::Error => Level::Error,
log::Level::Warn => Level::Warning,
log::Level::Info => Level::Info,
log::Level::Debug | log::Level::Trace => Level::Debug,
}
}
pub fn init(dest: Option<Box<dyn log::Log>>, options: LoggerOptions) {
let logger = Logger::new(dest, options);
let filter = logger.options().effective_global_filter();
if filter > log::max_level() {
log::set_max_level(filter);
}
log::set_boxed_logger(Box::new(logger)).unwrap();
}
#[test]
fn test_filters() {
let opt_warn = LoggerOptions {
filter: log::LevelFilter::Warn,
..Default::default()
};
assert_eq!(opt_warn.effective_global_filter(), log::LevelFilter::Warn);
assert_eq!(opt_warn.issue_filter(), log::LevelFilter::Error);
let opt_debug = LoggerOptions {
global_filter: Some(log::LevelFilter::Debug),
filter: log::LevelFilter::Warn,
..Default::default()
};
assert_eq!(opt_debug.effective_global_filter(), log::LevelFilter::Debug);
let opt_debug_inverse = LoggerOptions {
global_filter: Some(log::LevelFilter::Warn),
filter: log::LevelFilter::Debug,
..Default::default()
};
assert_eq!(
opt_debug_inverse.effective_global_filter(),
log::LevelFilter::Debug
);
let opt_weird = LoggerOptions {
filter: log::LevelFilter::Error,
emit_warning_events: true,
..Default::default()
};
assert_eq!(opt_weird.issue_filter(), log::LevelFilter::Warn);
assert_eq!(opt_weird.effective_global_filter(), log::LevelFilter::Warn);
}