| 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 | /// ``` |
| 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 | |
| 155 | impl Default for LogTracer { |
| 156 | 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" )))] |
| 165 | fn try_cache_interest(_: &log::Metadata<'_>, callback: impl FnOnce() -> bool) -> bool { |
| 166 | callback() |
| 167 | } |
| 168 | |
| 169 | impl 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 | |
| 208 | impl 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 | |
| 298 | impl 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 | } |
| 308 | |