blob: 71dfc864a26ef04a5a8e19209a89c0cc39b4a182 [file] [log] [blame]
//! An adapter for converting [`log`] records into `tracing` `Event`s.
//!
//! This module provides the [`LogTracer`] type which implements `log`'s [logger
//! interface] by recording log records as `tracing` `Event`s. This is intended for
//! use in conjunction with a `tracing` `Subscriber` to consume events from
//! dependencies that emit [`log`] records within a trace context.
//!
//! # Usage
//!
//! To create and initialize a `LogTracer` with the default configurations, use:
//!
//! * [`init`] if you want to convert all logs, regardless of log level,
//! allowing the tracing `Subscriber` to perform any filtering
//! * [`init_with_filter`] to convert all logs up to a specified log level
//!
//! In addition, a [builder] is available for cases where more advanced
//! configuration is required. In particular, the builder can be used to [ignore
//! log records][ignore] emitted by particular crates. This is useful in cases
//! such as when a crate emits both `tracing` diagnostics _and_ log records by
//! default.
//!
//! [`LogTracer`]: struct.LogTracer.html
//! [`log`]: https://docs.rs/log/0.4.8/log/
//! [logger interface]: https://docs.rs/log/0.4.8/log/trait.Log.html
//! [`init`]: struct.LogTracer.html#method.init.html
//! [`init_with_filter`]: struct.LogTracer.html#method.init_with_filter.html
//! [builder]: struct.LogTracer.html#method.builder
//! [ignore]: struct.Builder.html#method.ignore_crate
use crate::{format_trace, AsTrace};
use log;
pub use log::SetLoggerError;
use tracing_core::dispatcher;
/// A simple "logger" that converts all log records into `tracing` `Event`s.
#[derive(Debug)]
pub struct LogTracer {
ignore_crates: Box<[String]>,
}
/// Configures a new `LogTracer`.
#[derive(Debug)]
pub struct Builder {
ignore_crates: Vec<String>,
filter: log::LevelFilter,
}
// ===== impl LogTracer =====
impl LogTracer {
/// Returns a builder that allows customizing a `LogTracer` and setting it
/// the default logger.
///
/// For example:
/// ```rust
/// # use std::error::Error;
/// use tracing_log::LogTracer;
/// use log;
///
/// # fn main() -> Result<(), Box<Error>> {
/// LogTracer::builder()
/// .ignore_crate("foo") // suppose the `foo` crate is using `tracing`'s log feature
/// .with_max_level(log::LevelFilter::Info)
/// .init()?;
///
/// // will be available for Subscribers as a tracing Event
/// log::info!("an example info log");
/// # Ok(())
/// # }
/// ```
pub fn builder() -> Builder {
Builder::default()
}
/// Creates a new `LogTracer` that can then be used as a logger for the `log` crate.
///
/// It is generally simpler to use the [`init`] or [`init_with_filter`] methods
/// which will create the `LogTracer` and set it as the global logger.
///
/// Logger setup without the initialization methods can be done with:
///
/// ```rust
/// # use std::error::Error;
/// use tracing_log::LogTracer;
/// use log;
///
/// # fn main() -> Result<(), Box<Error>> {
/// let logger = LogTracer::new();
/// log::set_boxed_logger(Box::new(logger))?;
/// log::set_max_level(log::LevelFilter::Trace);
///
/// // will be available for Subscribers as a tracing Event
/// log::trace!("an example trace log");
/// # Ok(())
/// # }
/// ```
///
/// [`init`]: #method.init
/// [`init_with_filter`]: .#method.init_with_filter
pub fn new() -> Self {
Self {
ignore_crates: Vec::new().into_boxed_slice(),
}
}
/// Sets up `LogTracer` as global logger for the `log` crate,
/// with the given level as max level filter.
///
/// Setting a global logger can only be done once.
///
/// The [`builder`] function can be used to customize the `LogTracer` before
/// initializing it.
///
/// [`builder`]: #method.builder
#[cfg(feature = "std")]
pub fn init_with_filter(level: log::LevelFilter) -> Result<(), SetLoggerError> {
Self::builder().with_max_level(level).init()
}
/// Sets a `LogTracer` as the global logger for the `log` crate.
///
/// Setting a global logger can only be done once.
///
/// ```rust
/// # use std::error::Error;
/// use tracing_log::LogTracer;
/// use log;
///
/// # fn main() -> Result<(), Box<Error>> {
/// LogTracer::init()?;
///
/// // will be available for Subscribers as a tracing Event
/// log::trace!("an example trace log");
/// # Ok(())
/// # }
/// ```
///
/// This will forward all logs to `tracing` and lets the current `Subscriber`
/// determine if they are enabled.
///
/// The [`builder`] function can be used to customize the `LogTracer` before
/// initializing it.
///
/// If you know in advance you want to filter some log levels,
/// use [`builder`] or [`init_with_filter`] instead.
///
/// [`init_with_filter`]: #method.init_with_filter
/// [`builder`]: #method.builder
#[cfg(feature = "std")]
pub fn init() -> Result<(), SetLoggerError> {
Self::builder().init()
}
}
impl Default for LogTracer {
fn default() -> Self {
Self::new()
}
}
impl log::Log for LogTracer {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
if self.ignore_crates.is_empty() {
return true;
}
// If we are ignoring certain module paths, ensure that the metadata
// does not start with one of those paths.
let target = metadata.target();
!self
.ignore_crates
.iter()
.any(|ignored| target.starts_with(ignored))
}
fn log(&self, record: &log::Record<'_>) {
let enabled = dispatcher::get_default(|dispatch| {
// TODO: can we cache this for each log record, so we can get
// similar to the callsite cache?
dispatch.enabled(&record.as_trace())
});
if enabled {
// TODO: if the record is enabled, we'll get the current dispatcher
// twice --- once to check if enabled, and again to dispatch the event.
// If we could construct events without dispatching them, we could
// re-use the dispatcher reference...
format_trace(record).unwrap();
}
}
fn flush(&self) {}
}
// ===== impl Builder =====
impl Builder {
/// Returns a new `Builder` to construct a [`LogTracer`].
///
/// [`LogTracer`]: struct.LogTracer.html
pub fn new() -> Self {
Self::default()
}
/// Sets a global maximum level for `log` records.
///
/// Log records whose level is more verbose than the provided level will be
/// disabled.
///
/// By default, all `log` records will be enabled.
pub fn with_max_level(self, filter: impl Into<log::LevelFilter>) -> Self {
let filter = filter.into();
Self { filter, ..self }
}
/// Configures the `LogTracer` to ignore all log records whose target
/// starts with the given string.
///
/// This should be used when a crate enables the `tracing/log` feature to
/// emit log records for tracing events. Otherwise, those events will be
/// recorded twice.
pub fn ignore_crate(mut self, name: impl Into<String>) -> Self {
self.ignore_crates.push(name.into());
self
}
/// Configures the `LogTracer` to ignore all log records whose target
/// starts with any of the given the given strings.
///
/// This should be used when a crate enables the `tracing/log` feature to
/// emit log records for tracing events. Otherwise, those events will be
/// recorded twice.
pub fn ignore_all<I>(self, crates: impl IntoIterator<Item = I>) -> Self
where
I: Into<String>,
{
crates.into_iter().fold(self, Self::ignore_crate)
}
/// Constructs a new `LogTracer` with the provided configuration and sets it
/// as the default logger.
///
/// Setting a global logger can only be done once.
#[cfg(feature = "std")]
pub fn init(self) -> Result<(), SetLoggerError> {
let ignore_crates = self.ignore_crates.into_boxed_slice();
let logger = Box::new(LogTracer { ignore_crates });
log::set_boxed_logger(logger)?;
log::set_max_level(self.filter);
Ok(())
}
}
impl Default for Builder {
fn default() -> Self {
Self {
ignore_crates: Vec::new(),
filter: log::LevelFilter::max(),
}
}
}