logo
  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
//! 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,
}

// ===== 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`]: #method.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`]: #method.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`]: #method.init_with_filter
    /// [`builder`]: #method.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()
    }
}

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;
                }
            }
        }

        // 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<'_>) {
        crate::dispatch_record(record);
    }

    fn flush(&self) {}
}

// ===== impl Builder =====

impl Builder {
    /// Returns a new `Builder` to construct a [`LogTracer`].
    ///
    /// [`LogTracer`]: struct.LogTracer.html
    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)
    }

    /// 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")))]
    pub fn init(self) -> Result<(), SetLoggerError> {
        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(),
        }
    }
}