• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()
27 use crate::AsTrace;
28 pub use log::SetLoggerError;
29 use tracing_core::dispatcher;
30 
31 /// A simple "logger" that converts all log records into `tracing` `Event`s.
32 #[derive(Debug)]
33 pub struct LogTracer {
34     ignore_crates: Box<[String]>,
35 }
36 
37 /// Configures a new `LogTracer`.
38 #[derive(Debug)]
39 pub 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 
48 impl 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     /// ```
builder() -> Builder69     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
new() -> Self98     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")))]
init_with_filter(level: log::LevelFilter) -> Result<(), SetLoggerError>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")))]
init() -> Result<(), SetLoggerError>150     pub fn init() -> Result<(), SetLoggerError> {
151         Self::builder().init()
152     }
153 }
154 
155 impl Default for LogTracer {
default() -> Self156     fn default() -> Self {
157         Self::new()
158     }
159 }
160 
161 #[cfg(all(feature = "interest-cache", feature = "std"))]
162 use crate::interest_cache::try_cache as try_cache_interest;
163 
164 #[cfg(not(all(feature = "interest-cache", feature = "std")))]
try_cache_interest(_: &log::Metadata<'_>, callback: impl FnOnce() -> bool) -> bool165 fn try_cache_interest(_: &log::Metadata<'_>, callback: impl FnOnce() -> bool) -> bool {
166     callback()
167 }
168 
169 impl log::Log for LogTracer {
enabled(&self, metadata: &log::Metadata<'_>) -> bool170     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 
log(&self, record: &log::Record<'_>)197     fn log(&self, record: &log::Record<'_>) {
198         if self.enabled(record.metadata()) {
199             crate::dispatch_record(record);
200         }
201     }
202 
flush(&self)203     fn flush(&self) {}
204 }
205 
206 // ===== impl Builder =====
207 
208 impl Builder {
209     /// Returns a new `Builder` to construct a [`LogTracer`].
210     ///
new() -> Self211     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.
with_max_level(self, filter: impl Into<log::LevelFilter>) -> Self221     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.
ignore_crate(mut self, name: impl Into<String>) -> Self232     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.
ignore_all<I>(self, crates: impl IntoIterator<Item = I>) -> Self where I: Into<String>,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"))))]
with_interest_cache(mut self, config: crate::InterestCacheConfig) -> Self274     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)]
init(mut self) -> Result<(), SetLoggerError>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 
298 impl Default for Builder {
default() -> Self299     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 }
308