307 lines
11 KiB
Rust
307 lines
11 KiB
Rust
//! 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<String>,
|
|
filter: log::LevelFilter,
|
|
#[cfg(all(feature = "interest-cache", feature = "std"))]
|
|
interest_cache_config: Option<crate::InterestCacheConfig>,
|
|
}
|
|
|
|
// ===== 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`]: 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<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`]: 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<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)
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
}
|
|
}
|