tracing_log/
log_tracer.rs

1//! An adapter for converting [`log`] records into `tracing` `Event`s.
2//!
3//! This module provides the [`LogTracer`] type which implements `log`'s [logger
4//! interface] by recording log records as `tracing` `Event`s. This is intended for
5//! use in conjunction with a `tracing` `Subscriber` to consume events from
6//! dependencies that emit [`log`] records within a trace context.
7//!
8//! # Usage
9//!
10//! To create and initialize a `LogTracer` with the default configurations, use:
11//!
12//! * [`init`] if you want to convert all logs, regardless of log level,
13//!   allowing the tracing `Subscriber` to perform any filtering
14//! * [`init_with_filter`] to convert all logs up to a specified log level
15//!
16//! In addition, a [builder] is available for cases where more advanced
17//! configuration is required. In particular, the builder can be used to [ignore
18//! log records][ignore] emitted by particular crates. This is useful in cases
19//! such as when a crate emits both `tracing` diagnostics _and_ log records by
20//! default.
21//!
22//! [logger interface]: log::Log
23//! [`init`]: LogTracer.html#method.init
24//! [`init_with_filter`]: LogTracer.html#method.init_with_filter
25//! [builder]: LogTracer::builder()
26//! [ignore]: Builder::ignore_crate()
27use crate::AsTrace;
28pub use log::SetLoggerError;
29use tracing_core::dispatcher;
30
31/// A simple "logger" that converts all log records into `tracing` `Event`s.
32#[derive(Debug)]
33pub struct LogTracer {
34    ignore_crates: Box<[String]>,
35}
36
37/// Configures a new `LogTracer`.
38#[derive(Debug)]
39pub struct Builder {
40    ignore_crates: Vec<String>,
41    filter: log::LevelFilter,
42    #[cfg(all(feature = "interest-cache", feature = "std"))]
43    interest_cache_config: Option<crate::InterestCacheConfig>,
44}
45
46// ===== impl LogTracer =====
47
48impl LogTracer {
49    /// Returns a builder that allows customizing a `LogTracer` and setting it
50    /// the default logger.
51    ///
52    /// For example:
53    /// ```rust
54    /// # use std::error::Error;
55    /// use tracing_log::LogTracer;
56    /// use log;
57    ///
58    /// # fn main() -> Result<(), Box<Error>> {
59    /// LogTracer::builder()
60    ///     .ignore_crate("foo") // suppose the `foo` crate is using `tracing`'s log feature
61    ///     .with_max_level(log::LevelFilter::Info)
62    ///     .init()?;
63    ///
64    /// // will be available for Subscribers as a tracing Event
65    /// log::info!("an example info log");
66    /// # Ok(())
67    /// # }
68    /// ```
69    pub fn builder() -> Builder {
70        Builder::default()
71    }
72
73    /// Creates a new `LogTracer` that can then be used as a logger for the `log` crate.
74    ///
75    /// It is generally simpler to use the [`init`] or [`init_with_filter`] methods
76    /// which will create the `LogTracer` and set it as the global logger.
77    ///
78    /// Logger setup without the initialization methods can be done with:
79    ///
80    /// ```rust
81    /// # use std::error::Error;
82    /// use tracing_log::LogTracer;
83    /// use log;
84    ///
85    /// # fn main() -> Result<(), Box<Error>> {
86    /// let logger = LogTracer::new();
87    /// log::set_boxed_logger(Box::new(logger))?;
88    /// log::set_max_level(log::LevelFilter::Trace);
89    ///
90    /// // will be available for Subscribers as a tracing Event
91    /// log::trace!("an example trace log");
92    /// # Ok(())
93    /// # }
94    /// ```
95    ///
96    /// [`init`]: LogTracer::init()
97    /// [`init_with_filter`]: .#method.init_with_filter
98    pub fn new() -> Self {
99        Self {
100            ignore_crates: Vec::new().into_boxed_slice(),
101        }
102    }
103
104    /// Sets up `LogTracer` as global logger for the `log` crate,
105    /// with the given level as max level filter.
106    ///
107    /// Setting a global logger can only be done once.
108    ///
109    /// The [`builder`] function can be used to customize the `LogTracer` before
110    /// initializing it.
111    ///
112    /// [`builder`]: LogTracer::builder()
113    #[cfg(feature = "std")]
114    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
115    pub fn init_with_filter(level: log::LevelFilter) -> Result<(), SetLoggerError> {
116        Self::builder().with_max_level(level).init()
117    }
118
119    /// Sets a `LogTracer` as the global logger for the `log` crate.
120    ///
121    /// Setting a global logger can only be done once.
122    ///
123    /// ```rust
124    /// # use std::error::Error;
125    /// use tracing_log::LogTracer;
126    /// use log;
127    ///
128    /// # fn main() -> Result<(), Box<Error>> {
129    /// LogTracer::init()?;
130    ///
131    /// // will be available for Subscribers as a tracing Event
132    /// log::trace!("an example trace log");
133    /// # Ok(())
134    /// # }
135    /// ```
136    ///
137    /// This will forward all logs to `tracing` and lets the current `Subscriber`
138    /// determine if they are enabled.
139    ///
140    /// The [`builder`] function can be used to customize the `LogTracer` before
141    /// initializing it.
142    ///
143    /// If you know in advance you want to filter some log levels,
144    /// use [`builder`] or [`init_with_filter`] instead.
145    ///
146    /// [`init_with_filter`]: LogTracer::init_with_filter()
147    /// [`builder`]: LogTracer::builder()
148    #[cfg(feature = "std")]
149    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
150    pub fn init() -> Result<(), SetLoggerError> {
151        Self::builder().init()
152    }
153}
154
155impl Default for LogTracer {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161#[cfg(all(feature = "interest-cache", feature = "std"))]
162use crate::interest_cache::try_cache as try_cache_interest;
163
164#[cfg(not(all(feature = "interest-cache", feature = "std")))]
165fn try_cache_interest(_: &log::Metadata<'_>, callback: impl FnOnce() -> bool) -> bool {
166    callback()
167}
168
169impl log::Log for LogTracer {
170    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
171        // First, check the log record against the current max level enabled by
172        // the current `tracing` subscriber.
173        if metadata.level().as_trace() > tracing_core::LevelFilter::current() {
174            // If the log record's level is above that, disable it.
175            return false;
176        }
177
178        // Okay, it wasn't disabled by the max level — do we have any specific
179        // modules to ignore?
180        if !self.ignore_crates.is_empty() {
181            // If we are ignoring certain module paths, ensure that the metadata
182            // does not start with one of those paths.
183            let target = metadata.target();
184            for ignored in &self.ignore_crates[..] {
185                if target.starts_with(ignored) {
186                    return false;
187                }
188            }
189        }
190
191        try_cache_interest(metadata, || {
192            // Finally, check if the current `tracing` dispatcher cares about this.
193            dispatcher::get_default(|dispatch| dispatch.enabled(&metadata.as_trace()))
194        })
195    }
196
197    fn log(&self, record: &log::Record<'_>) {
198        if self.enabled(record.metadata()) {
199            crate::dispatch_record(record);
200        }
201    }
202
203    fn flush(&self) {}
204}
205
206// ===== impl Builder =====
207
208impl Builder {
209    /// Returns a new `Builder` to construct a [`LogTracer`].
210    ///
211    pub fn new() -> Self {
212        Self::default()
213    }
214
215    /// Sets a global maximum level for `log` records.
216    ///
217    /// Log records whose level is more verbose than the provided level will be
218    /// disabled.
219    ///
220    /// By default, all `log` records will be enabled.
221    pub fn with_max_level(self, filter: impl Into<log::LevelFilter>) -> Self {
222        let filter = filter.into();
223        Self { filter, ..self }
224    }
225
226    /// Configures the `LogTracer` to ignore all log records whose target
227    /// starts with the given string.
228    ///
229    /// This should be used when a crate enables the `tracing/log` feature to
230    /// emit log records for tracing events. Otherwise, those events will be
231    /// recorded twice.
232    pub fn ignore_crate(mut self, name: impl Into<String>) -> Self {
233        self.ignore_crates.push(name.into());
234        self
235    }
236
237    /// Configures the `LogTracer` to ignore all log records whose target
238    /// starts with any of the given the given strings.
239    ///
240    /// This should be used when a crate enables the `tracing/log` feature to
241    /// emit log records for tracing events. Otherwise, those events will be
242    /// recorded twice.
243    pub fn ignore_all<I>(self, crates: impl IntoIterator<Item = I>) -> Self
244    where
245        I: Into<String>,
246    {
247        crates.into_iter().fold(self, Self::ignore_crate)
248    }
249
250    /// Configures the `LogTracer` to either disable or enable the interest cache.
251    ///
252    /// When enabled, a per-thread LRU cache will be used to cache whenever the logger
253    /// is interested in a given [level] + [target] pair for records generated through
254    /// the `log` crate.
255    ///
256    /// When no `trace!` logs are enabled the logger is able to cheaply filter
257    /// them out just by comparing their log level to the globally specified
258    /// maximum, and immediately reject them. When *any* other `trace!` log is
259    /// enabled (even one which doesn't actually exist!) the logger has to run
260    /// its full filtering machinery on each and every `trace!` log, which can
261    /// potentially be very expensive.
262    ///
263    /// Enabling this cache is useful in such situations to improve performance.
264    ///
265    /// You most likely do not want to enabled this if you have registered any dynamic
266    /// filters on your logger and you want them to be run every time.
267    ///
268    /// This is disabled by default.
269    ///
270    /// [level]: log::Metadata::level
271    /// [target]: log::Metadata::target
272    #[cfg(all(feature = "interest-cache", feature = "std"))]
273    #[cfg_attr(docsrs, doc(cfg(all(feature = "interest-cache", feature = "std"))))]
274    pub fn with_interest_cache(mut self, config: crate::InterestCacheConfig) -> Self {
275        self.interest_cache_config = Some(config);
276        self
277    }
278
279    /// Constructs a new `LogTracer` with the provided configuration and sets it
280    /// as the default logger.
281    ///
282    /// Setting a global logger can only be done once.
283    #[cfg(feature = "std")]
284    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
285    #[allow(unused_mut)]
286    pub fn init(mut self) -> Result<(), SetLoggerError> {
287        #[cfg(all(feature = "interest-cache", feature = "std"))]
288        crate::interest_cache::configure(self.interest_cache_config.take());
289
290        let ignore_crates = self.ignore_crates.into_boxed_slice();
291        let logger = Box::new(LogTracer { ignore_crates });
292        log::set_boxed_logger(logger)?;
293        log::set_max_level(self.filter);
294        Ok(())
295    }
296}
297
298impl Default for Builder {
299    fn default() -> Self {
300        Self {
301            ignore_crates: Vec::new(),
302            filter: log::LevelFilter::max(),
303            #[cfg(all(feature = "interest-cache", feature = "std"))]
304            interest_cache_config: None,
305        }
306    }
307}