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