🛈 Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

tracing_log/
lib.rs

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