//! 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. //! //! [logger interface]: log::Log //! [`init`]: LogTracer.html#method.init //! [`init_with_filter`]: LogTracer.html#method.init_with_filter //! [builder]: LogTracer::builder() //! [ignore]: Builder::ignore_crate() use crate::AsTrace; 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, filter: log::LevelFilter, #[cfg(all(feature = "interest-cache", feature = "std"))] interest_cache_config: Option, } // ===== 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> { /// 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> { /// 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`]: LogTracer::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`]: LogTracer::builder() #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(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> { /// 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`]: LogTracer::init_with_filter() /// [`builder`]: LogTracer::builder() #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn init() -> Result<(), SetLoggerError> { Self::builder().init() } } impl Default for LogTracer { fn default() -> Self { Self::new() } } #[cfg(all(feature = "interest-cache", feature = "std"))] use crate::interest_cache::try_cache as try_cache_interest; #[cfg(not(all(feature = "interest-cache", feature = "std")))] fn try_cache_interest(_: &log::Metadata<'_>, callback: impl FnOnce() -> bool) -> bool { callback() } impl log::Log for LogTracer { fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { // First, check the log record against the current max level enabled by // the current `tracing` subscriber. if metadata.level().as_trace() > tracing_core::LevelFilter::current() { // If the log record's level is above that, disable it. return false; } // Okay, it wasn't disabled by the max level — do we have any specific // modules to ignore? if !self.ignore_crates.is_empty() { // If we are ignoring certain module paths, ensure that the metadata // does not start with one of those paths. let target = metadata.target(); for ignored in &self.ignore_crates[..] { if target.starts_with(ignored) { return false; } } } try_cache_interest(metadata, || { // Finally, check if the current `tracing` dispatcher cares about this. dispatcher::get_default(|dispatch| dispatch.enabled(&metadata.as_trace())) }) } fn log(&self, record: &log::Record<'_>) { if self.enabled(record.metadata()) { crate::dispatch_record(record); } } fn flush(&self) {} } // ===== impl Builder ===== impl Builder { /// Returns a new `Builder` to construct a [`LogTracer`]. /// 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) -> 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) -> 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(self, crates: impl IntoIterator) -> Self where I: Into, { crates.into_iter().fold(self, Self::ignore_crate) } /// Configures the `LogTracer` to either disable or enable the interest cache. /// /// When enabled, a per-thread LRU cache will be used to cache whenever the logger /// is interested in a given [level] + [target] pair for records generated through /// the `log` crate. /// /// When no `trace!` logs are enabled the logger is able to cheaply filter /// them out just by comparing their log level to the globally specified /// maximum, and immediately reject them. When *any* other `trace!` log is /// enabled (even one which doesn't actually exist!) the logger has to run /// its full filtering machinery on each and every `trace!` log, which can /// potentially be very expensive. /// /// Enabling this cache is useful in such situations to improve performance. /// /// You most likely do not want to enabled this if you have registered any dynamic /// filters on your logger and you want them to be run every time. /// /// This is disabled by default. /// /// [level]: log::Metadata::level /// [target]: log::Metadata::target #[cfg(all(feature = "interest-cache", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "interest-cache", feature = "std"))))] pub fn with_interest_cache(mut self, config: crate::InterestCacheConfig) -> Self { self.interest_cache_config = Some(config); self } /// 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")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[allow(unused_mut)] pub fn init(mut self) -> Result<(), SetLoggerError> { #[cfg(all(feature = "interest-cache", feature = "std"))] crate::interest_cache::configure(self.interest_cache_config.take()); 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(), #[cfg(all(feature = "interest-cache", feature = "std"))] interest_cache_config: None, } } }