πŸ›ˆ Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

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` `Collector` 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 `Collector` 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::dispatch;
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}
43
44// ===== impl LogTracer =====
45
46impl LogTracer {
47    /// Returns a builder that allows customizing a `LogTracer` and setting it
48    /// the default logger.
49    ///
50    /// For example:
51    /// ```rust
52    /// # use std::error::Error;
53    /// use tracing_log::LogTracer;
54    /// use log;
55    ///
56    /// # fn main() -> Result<(), Box<dyn Error>> {
57    /// LogTracer::builder()
58    ///     .ignore_crate("foo") // suppose the `foo` crate is using `tracing`'s log feature
59    ///     .with_max_level(log::LevelFilter::Info)
60    ///     .init()?;
61    ///
62    /// // will be available for Subscribers as a tracing Event
63    /// log::info!("an example info log");
64    /// # Ok(())
65    /// # }
66    /// ```
67    pub fn builder() -> Builder {
68        Builder::default()
69    }
70
71    /// Creates a new `LogTracer` that can then be used as a logger for the `log` crate.
72    ///
73    /// It is generally simpler to use the [`init`] or [`init_with_filter`] methods
74    /// which will create the `LogTracer` and set it as the global logger.
75    ///
76    /// Logger setup without the initialization methods can be done with:
77    ///
78    /// ```rust
79    /// # use std::error::Error;
80    /// use tracing_log::LogTracer;
81    /// use log;
82    ///
83    /// # fn main() -> Result<(), Box<dyn Error>> {
84    /// let logger = LogTracer::new();
85    /// log::set_boxed_logger(Box::new(logger))?;
86    /// log::set_max_level(log::LevelFilter::Trace);
87    ///
88    /// // will be available for Subscribers as a tracing Event
89    /// log::trace!("an example trace log");
90    /// # Ok(())
91    /// # }
92    /// ```
93    ///
94    /// [`init`]: LogTracer::init()
95    /// [`init_with_filter`]: .#method.init_with_filter
96    pub fn new() -> Self {
97        Self {
98            ignore_crates: Vec::new().into_boxed_slice(),
99        }
100    }
101
102    /// Sets up `LogTracer` as global logger for the `log` crate,
103    /// with the given level as max level filter.
104    ///
105    /// Setting a global logger can only be done once.
106    ///
107    /// The [`builder`] function can be used to customize the `LogTracer` before
108    /// initializing it.
109    ///
110    /// [`builder`]: LogTracer::builder()
111    #[cfg(feature = "std")]
112    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
113    pub fn init_with_filter(level: log::LevelFilter) -> Result<(), SetLoggerError> {
114        Self::builder().with_max_level(level).init()
115    }
116
117    /// Sets a `LogTracer` as the global logger for the `log` crate.
118    ///
119    /// Setting a global logger can only be done once.
120    ///
121    /// ```rust
122    /// # use std::error::Error;
123    /// use tracing_log::LogTracer;
124    /// use log;
125    ///
126    /// # fn main() -> Result<(), Box<dyn Error>> {
127    /// LogTracer::init()?;
128    ///
129    /// // will be available for Subscribers as a tracing Event
130    /// log::trace!("an example trace log");
131    /// # Ok(())
132    /// # }
133    /// ```
134    ///
135    /// This will forward all logs to `tracing` and lets the current `Collector`
136    /// determine if they are enabled.
137    ///
138    /// The [`builder`] function can be used to customize the `LogTracer` before
139    /// initializing it.
140    ///
141    /// If you know in advance you want to filter some log levels,
142    /// use [`builder`] or [`init_with_filter`] instead.
143    ///
144    /// [`init_with_filter`]: LogTracer::init_with_filter()
145    /// [`builder`]: LogTracer::builder()
146    #[cfg(feature = "std")]
147    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
148    pub fn init() -> Result<(), SetLoggerError> {
149        Self::builder().init()
150    }
151}
152
153impl Default for LogTracer {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl log::Log for LogTracer {
160    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
161        // First, check the log record against the current max level enabled by
162        // the current `tracing` subscriber.
163        if metadata.level().as_trace() > tracing_core::LevelFilter::current() {
164            // If the log record's level is above that, disable it.
165            return false;
166        }
167
168        // Okay, it wasn't disabled by the max level β€” do we have any specific
169        // modules to ignore?
170        if !self.ignore_crates.is_empty() {
171            // If we are ignoring certain module paths, ensure that the metadata
172            // does not start with one of those paths.
173            let target = metadata.target();
174            for ignored in &self.ignore_crates[..] {
175                if target.starts_with(ignored) {
176                    return false;
177                }
178            }
179        }
180
181        // Finally, check if the current `tracing` dispatcher cares about this.
182        dispatch::get_default(|dispatch| dispatch.enabled(&metadata.as_trace()))
183    }
184
185    fn log(&self, record: &log::Record<'_>) {
186        if self.enabled(record.metadata()) {
187            crate::dispatch_record(record);
188        }
189    }
190
191    fn flush(&self) {}
192}
193
194// ===== impl Builder =====
195
196impl Builder {
197    /// Returns a new `Builder` to construct a [`LogTracer`].
198    ///
199    pub fn new() -> Self {
200        Self::default()
201    }
202
203    /// Sets a global maximum level for `log` records.
204    ///
205    /// Log records whose level is more verbose than the provided level will be
206    /// disabled.
207    ///
208    /// By default, all `log` records will be enabled.
209    pub fn with_max_level(self, filter: impl Into<log::LevelFilter>) -> Self {
210        let filter = filter.into();
211        Self { filter, ..self }
212    }
213
214    /// Configures the `LogTracer` to ignore all log records whose target
215    /// starts with the given string.
216    ///
217    /// This should be used when a crate enables the `tracing/log` feature to
218    /// emit log records for tracing events. Otherwise, those events will be
219    /// recorded twice.
220    pub fn ignore_crate(mut self, name: impl Into<String>) -> Self {
221        self.ignore_crates.push(name.into());
222        self
223    }
224
225    /// Configures the `LogTracer` to ignore all log records whose target
226    /// starts with any of the given the given strings.
227    ///
228    /// This should be used when a crate enables the `tracing/log` feature to
229    /// emit log records for tracing events. Otherwise, those events will be
230    /// recorded twice.
231    pub fn ignore_all<I>(self, crates: impl IntoIterator<Item = I>) -> Self
232    where
233        I: Into<String>,
234    {
235        crates.into_iter().fold(self, Self::ignore_crate)
236    }
237
238    /// Constructs a new `LogTracer` with the provided configuration and sets it
239    /// as the default logger.
240    ///
241    /// Setting a global logger can only be done once.
242    #[cfg(feature = "std")]
243    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
244    pub fn init(self) -> Result<(), SetLoggerError> {
245        let ignore_crates = self.ignore_crates.into_boxed_slice();
246        let logger = Box::new(LogTracer { ignore_crates });
247        log::set_boxed_logger(logger)?;
248        log::set_max_level(self.filter);
249        Ok(())
250    }
251}
252
253impl Default for Builder {
254    fn default() -> Self {
255        Self {
256            ignore_crates: Vec::new(),
257            filter: log::LevelFilter::max(),
258        }
259    }
260}