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}