]>
Commit | Line | Data |
---|---|---|
f035d41b XL |
1 | //! Adapters for connecting unstructured log records from the `log` crate into |
2 | //! the `tracing` ecosystem. | |
3 | //! | |
4 | //! # Overview | |
5 | //! | |
6 | //! [`tracing`] is a framework for instrumenting Rust programs with context-aware, | |
7 | //! structured, event-based diagnostic information. This crate provides | |
8 | //! compatibility layers for using `tracing` alongside the logging facade provided | |
9 | //! by the [`log`] crate. | |
10 | //! | |
11 | //! This crate provides: | |
12 | //! | |
13 | //! - [`AsTrace`] and [`AsLog`] traits for converting between `tracing` and `log` types. | |
14 | //! - [`LogTracer`], a [`log::Log`] implementation that consumes [`log::Record`]s | |
15 | //! and outputs them as [`tracing::Event`]. | |
16 | //! - An [`env_logger`] module, with helpers for using the [`env_logger` crate] | |
17 | //! with `tracing` (optional, enabled by the `env-logger` feature). | |
18 | //! | |
6a06907d XL |
19 | //! *Compiler support: [requires `rustc` 1.42+][msrv]* |
20 | //! | |
21 | //! [msrv]: #supported-rust-versions | |
22 | //! | |
f035d41b XL |
23 | //! # Usage |
24 | //! | |
25 | //! ## Convert log records to tracing `Event`s | |
26 | //! | |
27 | //! To convert [`log::Record`]s as [`tracing::Event`]s, set `LogTracer` as the default | |
28 | //! logger by calling its [`init`] or [`init_with_filter`] methods. | |
29 | //! | |
30 | //! ```rust | |
31 | //! # use std::error::Error; | |
32 | //! use tracing_log::LogTracer; | |
33 | //! use log; | |
34 | //! | |
35 | //! # fn main() -> Result<(), Box<Error>> { | |
36 | //! LogTracer::init()?; | |
37 | //! | |
38 | //! // will be available for Subscribers as a tracing Event | |
39 | //! log::trace!("an example trace log"); | |
40 | //! # Ok(()) | |
41 | //! # } | |
42 | //! ``` | |
43 | //! | |
44 | //! This conversion does not convert unstructured data in log records (such as | |
45 | //! values passed as format arguments to the `log!` macro) to structured | |
46 | //! `tracing` fields. However, it *does* attach these new events to to the | |
47 | //! span that was currently executing when the record was logged. This is the | |
48 | //! primary use-case for this library: making it possible to locate the log | |
49 | //! records emitted by dependencies which use `log` within the context of a | |
50 | //! trace. | |
51 | //! | |
52 | //! ## Convert tracing `Event`s to logs | |
53 | //! | |
54 | //! Enabling the ["log" and "log-always" feature flags][flags] on the `tracing` | |
55 | //! crate will cause all `tracing` spans and events to emit `log::Record`s as | |
56 | //! they occur. | |
57 | //! | |
58 | //! ## Caution: Mixing both conversions | |
59 | //! | |
60 | //! Note that logger implementations that convert log records to trace events | |
61 | //! should not be used with `Subscriber`s that convert trace events _back_ into | |
62 | //! log records (such as the `TraceLogger`), as doing so will result in the | |
63 | //! event recursing between the subscriber and the logger forever (or, in real | |
64 | //! life, probably overflowing the call stack). | |
65 | //! | |
66 | //! If the logging of trace events generated from log records produced by the | |
67 | //! `log` crate is desired, either the `log` crate should not be used to | |
68 | //! implement this logging, or an additional layer of filtering will be | |
69 | //! required to avoid infinitely converting between `Event` and `log::Record`. | |
70 | //! | |
71 | //! # Feature Flags | |
72 | //! * `trace-logger`: enables an experimental `log` subscriber, deprecated since | |
73 | //! version 0.1.1. | |
74 | //! * `log-tracer`: enables the `LogTracer` type (on by default) | |
75 | //! * `env_logger`: enables the `env_logger` module, with helpers for working | |
76 | //! with the [`env_logger` crate]. | |
77 | //! | |
6a06907d XL |
78 | //! ## Supported Rust Versions |
79 | //! | |
80 | //! Tracing is built against the latest stable release. The minimum supported | |
81 | //! version is 1.42. The current Tracing version is not guaranteed to build on | |
82 | //! Rust versions earlier than the minimum supported version. | |
83 | //! | |
84 | //! Tracing follows the same compiler support policies as the rest of the Tokio | |
85 | //! project. The current stable Rust compiler and the three most recent minor | |
86 | //! versions before it will always be supported. For example, if the current | |
87 | //! stable compiler version is 1.45, the minimum supported version will not be | |
88 | //! increased past 1.42, three minor versions prior. Increasing the minimum | |
89 | //! supported compiler version is not considered a semver breaking change as | |
90 | //! long as doing so complies with this policy. | |
91 | //! | |
f035d41b XL |
92 | //! [`init`]: struct.LogTracer.html#method.init |
93 | //! [`init_with_filter`]: struct.LogTracer.html#method.init_with_filter | |
94 | //! [`AsTrace`]: trait.AsTrace.html | |
95 | //! [`AsLog`]: trait.AsLog.html | |
96 | //! [`LogTracer`]: struct.LogTracer.html | |
97 | //! [`TraceLogger`]: struct.TraceLogger.html | |
98 | //! [`env_logger`]: env_logger/index.html | |
99 | //! [`tracing`]: https://crates.io/crates/tracing | |
100 | //! [`log`]: https://crates.io/crates/log | |
101 | //! [`env_logger` crate]: https://crates.io/crates/env-logger | |
102 | //! [`log::Log`]: https://docs.rs/log/latest/log/trait.Log.html | |
103 | //! [`log::Record`]: https://docs.rs/log/latest/log/struct.Record.html | |
104 | //! [`tracing::Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html | |
105 | //! [`Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html | |
106 | //! [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html | |
6a06907d XL |
107 | //! [flags]: https://docs.rs/tracing/latest/tracing/#crate-feature-flags |
108 | #![doc(html_root_url = "https://docs.rs/tracing-log/0.1.2")] | |
109 | #![doc( | |
110 | html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", | |
111 | issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" | |
112 | )] | |
113 | #![cfg_attr(docsrs, feature(doc_cfg), deny(broken_intra_doc_links))] | |
f035d41b XL |
114 | #![warn( |
115 | missing_debug_implementations, | |
116 | missing_docs, | |
117 | rust_2018_idioms, | |
118 | unreachable_pub, | |
119 | bad_style, | |
120 | const_err, | |
121 | dead_code, | |
122 | improper_ctypes, | |
f035d41b XL |
123 | non_shorthand_field_patterns, |
124 | no_mangle_generic_items, | |
125 | overflowing_literals, | |
126 | path_statements, | |
127 | patterns_in_fns_without_body, | |
f035d41b | 128 | private_in_public, |
f035d41b XL |
129 | unconditional_recursion, |
130 | unused, | |
131 | unused_allocation, | |
132 | unused_comparisons, | |
133 | unused_parens, | |
134 | while_true | |
135 | )] | |
136 | use lazy_static::lazy_static; | |
137 | ||
138 | use std::{fmt, io}; | |
139 | ||
140 | use tracing_core::{ | |
141 | callsite::{self, Callsite}, | |
142 | dispatcher, | |
143 | field::{self, Field, Visit}, | |
144 | identify_callsite, | |
145 | metadata::{Kind, Level}, | |
146 | subscriber, Event, Metadata, | |
147 | }; | |
148 | ||
149 | #[cfg(feature = "log-tracer")] | |
6a06907d | 150 | #[cfg_attr(docsrs, doc(cfg(feature = "log-tracer")))] |
f035d41b XL |
151 | pub mod log_tracer; |
152 | ||
153 | #[cfg(feature = "trace-logger")] | |
6a06907d | 154 | #[cfg_attr(docsrs, doc(cfg(feature = "trace-logger")))] |
f035d41b XL |
155 | pub mod trace_logger; |
156 | ||
157 | #[cfg(feature = "log-tracer")] | |
6a06907d | 158 | #[cfg_attr(docsrs, doc(cfg(feature = "log-tracer")))] |
f035d41b XL |
159 | #[doc(inline)] |
160 | pub use self::log_tracer::LogTracer; | |
161 | ||
162 | #[cfg(feature = "trace-logger")] | |
6a06907d | 163 | #[cfg_attr(docsrs, doc(cfg(feature = "trace-logger")))] |
f035d41b XL |
164 | #[deprecated( |
165 | since = "0.1.1", | |
166 | note = "use the `tracing` crate's \"log\" feature flag instead" | |
167 | )] | |
168 | #[allow(deprecated)] | |
169 | #[doc(inline)] | |
170 | pub use self::trace_logger::TraceLogger; | |
171 | ||
172 | #[cfg(feature = "env_logger")] | |
6a06907d | 173 | #[cfg_attr(docsrs, doc(cfg(feature = "env_logger")))] |
f035d41b XL |
174 | pub mod env_logger; |
175 | ||
6a06907d XL |
176 | pub use log; |
177 | ||
f035d41b XL |
178 | /// Format a log record as a trace event in the current span. |
179 | pub fn format_trace(record: &log::Record<'_>) -> io::Result<()> { | |
6a06907d | 180 | dispatch_record(record); |
f035d41b XL |
181 | Ok(()) |
182 | } | |
183 | ||
6a06907d XL |
184 | // XXX(eliza): this is factored out so that we don't have to deal with the pub |
185 | // function `format_trace`'s `Result` return type...maybe we should get rid of | |
186 | // that in 0.2... | |
187 | pub(crate) fn dispatch_record(record: &log::Record<'_>) { | |
188 | dispatcher::get_default(|dispatch| { | |
189 | let filter_meta = record.as_trace(); | |
190 | if !dispatch.enabled(&filter_meta) { | |
191 | return; | |
192 | } | |
193 | ||
194 | let (_, keys, meta) = loglevel_to_cs(record.level()); | |
195 | ||
196 | let log_module = record.module_path(); | |
197 | let log_file = record.file(); | |
198 | let log_line = record.line(); | |
199 | ||
200 | let module = log_module.as_ref().map(|s| s as &dyn field::Value); | |
201 | let file = log_file.as_ref().map(|s| s as &dyn field::Value); | |
202 | let line = log_line.as_ref().map(|s| s as &dyn field::Value); | |
203 | ||
204 | dispatch.event(&Event::new( | |
205 | meta, | |
206 | &meta.fields().value_set(&[ | |
207 | (&keys.message, Some(record.args() as &dyn field::Value)), | |
208 | (&keys.target, Some(&record.target())), | |
209 | (&keys.module, module), | |
210 | (&keys.file, file), | |
211 | (&keys.line, line), | |
212 | ]), | |
213 | )); | |
214 | }); | |
215 | } | |
216 | ||
f035d41b XL |
217 | /// Trait implemented for `tracing` types that can be converted to a `log` |
218 | /// equivalent. | |
219 | pub trait AsLog: crate::sealed::Sealed { | |
220 | /// The `log` type that this type can be converted into. | |
221 | type Log; | |
222 | /// Returns the `log` equivalent of `self`. | |
223 | fn as_log(&self) -> Self::Log; | |
224 | } | |
225 | ||
226 | /// Trait implemented for `log` types that can be converted to a `tracing` | |
227 | /// equivalent. | |
228 | pub trait AsTrace: crate::sealed::Sealed { | |
229 | /// The `tracing` type that this type can be converted into. | |
230 | type Trace; | |
231 | /// Returns the `tracing` equivalent of `self`. | |
232 | fn as_trace(&self) -> Self::Trace; | |
233 | } | |
234 | ||
235 | impl<'a> crate::sealed::Sealed for Metadata<'a> {} | |
236 | ||
237 | impl<'a> AsLog for Metadata<'a> { | |
238 | type Log = log::Metadata<'a>; | |
239 | fn as_log(&self) -> Self::Log { | |
240 | log::Metadata::builder() | |
241 | .level(self.level().as_log()) | |
242 | .target(self.target()) | |
243 | .build() | |
244 | } | |
245 | } | |
6a06907d XL |
246 | impl<'a> crate::sealed::Sealed for log::Metadata<'a> {} |
247 | ||
248 | impl<'a> AsTrace for log::Metadata<'a> { | |
249 | type Trace = Metadata<'a>; | |
250 | fn as_trace(&self) -> Self::Trace { | |
251 | let cs_id = identify_callsite!(loglevel_to_cs(self.level()).0); | |
252 | Metadata::new( | |
253 | "log record", | |
254 | self.target(), | |
255 | self.level().as_trace(), | |
256 | None, | |
257 | None, | |
258 | None, | |
259 | field::FieldSet::new(FIELD_NAMES, cs_id), | |
260 | Kind::EVENT, | |
261 | ) | |
262 | } | |
263 | } | |
f035d41b XL |
264 | |
265 | struct Fields { | |
266 | message: field::Field, | |
267 | target: field::Field, | |
268 | module: field::Field, | |
269 | file: field::Field, | |
270 | line: field::Field, | |
271 | } | |
272 | ||
6a06907d | 273 | static FIELD_NAMES: &[&str] = &[ |
f035d41b XL |
274 | "message", |
275 | "log.target", | |
276 | "log.module_path", | |
277 | "log.file", | |
278 | "log.line", | |
279 | ]; | |
280 | ||
281 | impl Fields { | |
282 | fn new(cs: &'static dyn Callsite) -> Self { | |
283 | let fieldset = cs.metadata().fields(); | |
284 | let message = fieldset.field("message").unwrap(); | |
285 | let target = fieldset.field("log.target").unwrap(); | |
286 | let module = fieldset.field("log.module_path").unwrap(); | |
287 | let file = fieldset.field("log.file").unwrap(); | |
288 | let line = fieldset.field("log.line").unwrap(); | |
289 | Fields { | |
290 | message, | |
291 | target, | |
292 | module, | |
293 | file, | |
294 | line, | |
295 | } | |
296 | } | |
297 | } | |
298 | ||
299 | macro_rules! log_cs { | |
6a06907d XL |
300 | ($level:expr, $cs:ident, $meta:ident, $ty:ident) => { |
301 | struct $ty; | |
302 | static $cs: $ty = $ty; | |
303 | static $meta: Metadata<'static> = Metadata::new( | |
f035d41b XL |
304 | "log event", |
305 | "log", | |
306 | $level, | |
307 | None, | |
308 | None, | |
309 | None, | |
6a06907d | 310 | field::FieldSet::new(FIELD_NAMES, identify_callsite!(&$cs)), |
f035d41b XL |
311 | Kind::EVENT, |
312 | ); | |
313 | ||
6a06907d | 314 | impl callsite::Callsite for $ty { |
f035d41b XL |
315 | fn set_interest(&self, _: subscriber::Interest) {} |
316 | fn metadata(&self) -> &'static Metadata<'static> { | |
6a06907d | 317 | &$meta |
f035d41b XL |
318 | } |
319 | } | |
6a06907d | 320 | }; |
f035d41b XL |
321 | } |
322 | ||
6a06907d XL |
323 | log_cs!( |
324 | tracing_core::Level::TRACE, | |
325 | TRACE_CS, | |
326 | TRACE_META, | |
327 | TraceCallsite | |
328 | ); | |
329 | log_cs!( | |
330 | tracing_core::Level::DEBUG, | |
331 | DEBUG_CS, | |
332 | DEBUG_META, | |
333 | DebugCallsite | |
334 | ); | |
335 | log_cs!(tracing_core::Level::INFO, INFO_CS, INFO_META, InfoCallsite); | |
336 | log_cs!(tracing_core::Level::WARN, WARN_CS, WARN_META, WarnCallsite); | |
337 | log_cs!( | |
338 | tracing_core::Level::ERROR, | |
339 | ERROR_CS, | |
340 | ERROR_META, | |
341 | ErrorCallsite | |
342 | ); | |
f035d41b XL |
343 | |
344 | lazy_static! { | |
6a06907d XL |
345 | static ref TRACE_FIELDS: Fields = Fields::new(&TRACE_CS); |
346 | static ref DEBUG_FIELDS: Fields = Fields::new(&DEBUG_CS); | |
347 | static ref INFO_FIELDS: Fields = Fields::new(&INFO_CS); | |
348 | static ref WARN_FIELDS: Fields = Fields::new(&WARN_CS); | |
349 | static ref ERROR_FIELDS: Fields = Fields::new(&ERROR_CS); | |
f035d41b XL |
350 | } |
351 | ||
6a06907d XL |
352 | fn level_to_cs(level: Level) -> (&'static dyn Callsite, &'static Fields) { |
353 | match level { | |
354 | Level::TRACE => (&TRACE_CS, &*TRACE_FIELDS), | |
355 | Level::DEBUG => (&DEBUG_CS, &*DEBUG_FIELDS), | |
356 | Level::INFO => (&INFO_CS, &*INFO_FIELDS), | |
357 | Level::WARN => (&WARN_CS, &*WARN_FIELDS), | |
358 | Level::ERROR => (&ERROR_CS, &*ERROR_FIELDS), | |
f035d41b XL |
359 | } |
360 | } | |
361 | ||
6a06907d XL |
362 | fn loglevel_to_cs( |
363 | level: log::Level, | |
364 | ) -> ( | |
365 | &'static dyn Callsite, | |
366 | &'static Fields, | |
367 | &'static Metadata<'static>, | |
368 | ) { | |
f035d41b | 369 | match level { |
6a06907d XL |
370 | log::Level::Trace => (&TRACE_CS, &*TRACE_FIELDS, &TRACE_META), |
371 | log::Level::Debug => (&DEBUG_CS, &*DEBUG_FIELDS, &DEBUG_META), | |
372 | log::Level::Info => (&INFO_CS, &*INFO_FIELDS, &INFO_META), | |
373 | log::Level::Warn => (&WARN_CS, &*WARN_FIELDS, &WARN_META), | |
374 | log::Level::Error => (&ERROR_CS, &*ERROR_FIELDS, &ERROR_META), | |
f035d41b XL |
375 | } |
376 | } | |
377 | ||
378 | impl<'a> crate::sealed::Sealed for log::Record<'a> {} | |
379 | ||
380 | impl<'a> AsTrace for log::Record<'a> { | |
381 | type Trace = Metadata<'a>; | |
382 | fn as_trace(&self) -> Self::Trace { | |
383 | let cs_id = identify_callsite!(loglevel_to_cs(self.level()).0); | |
384 | Metadata::new( | |
385 | "log record", | |
386 | self.target(), | |
387 | self.level().as_trace(), | |
388 | self.file(), | |
389 | self.line(), | |
390 | self.module_path(), | |
391 | field::FieldSet::new(FIELD_NAMES, cs_id), | |
392 | Kind::EVENT, | |
393 | ) | |
394 | } | |
395 | } | |
396 | ||
397 | impl crate::sealed::Sealed for tracing_core::Level {} | |
398 | ||
399 | impl AsLog for tracing_core::Level { | |
400 | type Log = log::Level; | |
401 | fn as_log(&self) -> log::Level { | |
402 | match *self { | |
403 | tracing_core::Level::ERROR => log::Level::Error, | |
404 | tracing_core::Level::WARN => log::Level::Warn, | |
405 | tracing_core::Level::INFO => log::Level::Info, | |
406 | tracing_core::Level::DEBUG => log::Level::Debug, | |
407 | tracing_core::Level::TRACE => log::Level::Trace, | |
408 | } | |
409 | } | |
410 | } | |
411 | ||
412 | impl crate::sealed::Sealed for log::Level {} | |
413 | ||
414 | impl AsTrace for log::Level { | |
415 | type Trace = tracing_core::Level; | |
6a06907d | 416 | #[inline] |
f035d41b XL |
417 | fn as_trace(&self) -> tracing_core::Level { |
418 | match self { | |
419 | log::Level::Error => tracing_core::Level::ERROR, | |
420 | log::Level::Warn => tracing_core::Level::WARN, | |
421 | log::Level::Info => tracing_core::Level::INFO, | |
422 | log::Level::Debug => tracing_core::Level::DEBUG, | |
423 | log::Level::Trace => tracing_core::Level::TRACE, | |
424 | } | |
425 | } | |
426 | } | |
427 | ||
6a06907d XL |
428 | impl crate::sealed::Sealed for log::LevelFilter {} |
429 | ||
430 | impl AsTrace for log::LevelFilter { | |
431 | type Trace = tracing_core::LevelFilter; | |
432 | #[inline] | |
433 | fn as_trace(&self) -> tracing_core::LevelFilter { | |
434 | match self { | |
435 | log::LevelFilter::Off => tracing_core::LevelFilter::OFF, | |
436 | log::LevelFilter::Error => tracing_core::LevelFilter::ERROR, | |
437 | log::LevelFilter::Warn => tracing_core::LevelFilter::WARN, | |
438 | log::LevelFilter::Info => tracing_core::LevelFilter::INFO, | |
439 | log::LevelFilter::Debug => tracing_core::LevelFilter::DEBUG, | |
440 | log::LevelFilter::Trace => tracing_core::LevelFilter::TRACE, | |
441 | } | |
442 | } | |
443 | } | |
444 | ||
445 | impl crate::sealed::Sealed for tracing_core::LevelFilter {} | |
446 | ||
447 | impl AsLog for tracing_core::LevelFilter { | |
448 | type Log = log::LevelFilter; | |
449 | #[inline] | |
450 | fn as_log(&self) -> Self::Log { | |
451 | match *self { | |
452 | tracing_core::LevelFilter::OFF => log::LevelFilter::Off, | |
453 | tracing_core::LevelFilter::ERROR => log::LevelFilter::Error, | |
454 | tracing_core::LevelFilter::WARN => log::LevelFilter::Warn, | |
455 | tracing_core::LevelFilter::INFO => log::LevelFilter::Info, | |
456 | tracing_core::LevelFilter::DEBUG => log::LevelFilter::Debug, | |
457 | tracing_core::LevelFilter::TRACE => log::LevelFilter::Trace, | |
458 | } | |
459 | } | |
460 | } | |
f035d41b XL |
461 | /// Extends log `Event`s to provide complete `Metadata`. |
462 | /// | |
463 | /// In `tracing-log`, an `Event` produced by a log (through [`AsTrace`]) has an hard coded | |
464 | /// "log" target and no `file`, `line`, or `module_path` attributes. This happens because `Event` | |
465 | /// requires its `Metadata` to be `'static`, while [`log::Record`]s provide them with a generic | |
466 | /// lifetime. | |
467 | /// | |
468 | /// However, these values are stored in the `Event`'s fields and | |
469 | /// the [`normalized_metadata`] method allows to build a new `Metadata` | |
470 | /// that only lives as long as its source `Event`, but provides complete | |
471 | /// data. | |
472 | /// | |
473 | /// It can typically be used by `Subscriber`s when processing an `Event`, | |
474 | /// to allow accessing its complete metadata in a consistent way, | |
475 | /// regardless of the source of its source. | |
476 | /// | |
477 | /// [`normalized_metadata`]: trait.NormalizeEvent.html#normalized_metadata | |
478 | /// [`AsTrace`]: trait.AsTrace.html | |
479 | /// [`log::Record`]: https://docs.rs/log/0.4.7/log/struct.Record.html | |
480 | pub trait NormalizeEvent<'a>: crate::sealed::Sealed { | |
481 | /// If this `Event` comes from a `log`, this method provides a new | |
482 | /// normalized `Metadata` which has all available attributes | |
483 | /// from the original log, including `file`, `line`, `module_path` | |
484 | /// and `target`. | |
485 | /// Returns `None` is the `Event` is not issued from a `log`. | |
486 | fn normalized_metadata(&'a self) -> Option<Metadata<'a>>; | |
487 | /// Returns whether this `Event` represents a log (from the `log` crate) | |
488 | fn is_log(&self) -> bool; | |
489 | } | |
490 | ||
491 | impl<'a> crate::sealed::Sealed for Event<'a> {} | |
492 | ||
493 | impl<'a> NormalizeEvent<'a> for Event<'a> { | |
494 | fn normalized_metadata(&'a self) -> Option<Metadata<'a>> { | |
495 | let original = self.metadata(); | |
496 | if self.is_log() { | |
6a06907d | 497 | let mut fields = LogVisitor::new_for(self, level_to_cs(*original.level()).1); |
f035d41b XL |
498 | self.record(&mut fields); |
499 | ||
500 | Some(Metadata::new( | |
501 | "log event", | |
502 | fields.target.unwrap_or("log"), | |
6a06907d | 503 | *original.level(), |
f035d41b XL |
504 | fields.file, |
505 | fields.line.map(|l| l as u32), | |
506 | fields.module_path, | |
507 | field::FieldSet::new(&["message"], original.callsite()), | |
508 | Kind::EVENT, | |
509 | )) | |
510 | } else { | |
511 | None | |
512 | } | |
513 | } | |
514 | ||
515 | fn is_log(&self) -> bool { | |
6a06907d | 516 | self.metadata().callsite() == identify_callsite!(level_to_cs(*self.metadata().level()).0) |
f035d41b XL |
517 | } |
518 | } | |
519 | ||
520 | struct LogVisitor<'a> { | |
521 | target: Option<&'a str>, | |
522 | module_path: Option<&'a str>, | |
523 | file: Option<&'a str>, | |
524 | line: Option<u64>, | |
525 | fields: &'static Fields, | |
526 | } | |
527 | ||
528 | impl<'a> LogVisitor<'a> { | |
529 | // We don't actually _use_ the provided event argument; it is simply to | |
530 | // ensure that the `LogVisitor` does not outlive the event whose fields it | |
531 | // is visiting, so that the reference casts in `record_str` are safe. | |
532 | fn new_for(_event: &'a Event<'a>, fields: &'static Fields) -> Self { | |
533 | Self { | |
534 | target: None, | |
535 | module_path: None, | |
536 | file: None, | |
537 | line: None, | |
538 | fields, | |
539 | } | |
540 | } | |
541 | } | |
542 | ||
543 | impl<'a> Visit for LogVisitor<'a> { | |
544 | fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {} | |
545 | ||
546 | fn record_u64(&mut self, field: &Field, value: u64) { | |
547 | if field == &self.fields.line { | |
548 | self.line = Some(value); | |
549 | } | |
550 | } | |
551 | ||
552 | fn record_str(&mut self, field: &Field, value: &str) { | |
553 | unsafe { | |
554 | // The `Visit` API erases the string slice's lifetime. However, we | |
555 | // know it is part of the `Event` struct with a lifetime of `'a`. If | |
556 | // (and only if!) this `LogVisitor` was constructed with the same | |
557 | // lifetime parameter `'a` as the event in question, it's safe to | |
558 | // cast these string slices to the `'a` lifetime. | |
559 | if field == &self.fields.file { | |
560 | self.file = Some(&*(value as *const _)); | |
561 | } else if field == &self.fields.target { | |
562 | self.target = Some(&*(value as *const _)); | |
563 | } else if field == &self.fields.module { | |
564 | self.module_path = Some(&*(value as *const _)); | |
565 | } | |
566 | } | |
567 | } | |
568 | } | |
569 | ||
570 | mod sealed { | |
571 | pub trait Sealed {} | |
572 | } | |
573 | ||
574 | #[cfg(test)] | |
575 | mod test { | |
576 | use super::*; | |
577 | ||
578 | fn test_callsite(level: log::Level) { | |
579 | let record = log::Record::builder() | |
580 | .args(format_args!("Error!")) | |
581 | .level(level) | |
582 | .target("myApp") | |
583 | .file(Some("server.rs")) | |
584 | .line(Some(144)) | |
585 | .module_path(Some("server")) | |
586 | .build(); | |
587 | ||
588 | let meta = record.as_trace(); | |
6a06907d | 589 | let (cs, _keys, _) = loglevel_to_cs(record.level()); |
f035d41b XL |
590 | let cs_meta = cs.metadata(); |
591 | assert_eq!( | |
592 | meta.callsite(), | |
593 | cs_meta.callsite(), | |
594 | "actual: {:#?}\nexpected: {:#?}", | |
595 | meta, | |
596 | cs_meta | |
597 | ); | |
598 | assert_eq!(meta.level(), &level.as_trace()); | |
599 | } | |
600 | ||
601 | #[test] | |
602 | fn error_callsite_is_correct() { | |
603 | test_callsite(log::Level::Error); | |
604 | } | |
605 | ||
606 | #[test] | |
607 | fn warn_callsite_is_correct() { | |
608 | test_callsite(log::Level::Warn); | |
609 | } | |
610 | ||
611 | #[test] | |
612 | fn info_callsite_is_correct() { | |
613 | test_callsite(log::Level::Info); | |
614 | } | |
615 | ||
616 | #[test] | |
617 | fn debug_callsite_is_correct() { | |
618 | test_callsite(log::Level::Debug); | |
619 | } | |
620 | ||
621 | #[test] | |
622 | fn trace_callsite_is_correct() { | |
623 | test_callsite(log::Level::Trace); | |
624 | } | |
625 | } |