1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
//! 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,
}
}
}