tracing_glog/
lib.rs

1//! `tracing-glog` is a [glog]-inspired formatter for [`tracing-subscriber`].
2//!
3//! `tracing-glog` is meant to be used with [`tracing-subscriber`]'s [`fmt::Subscriber`]
4//! and [`fmt::Layer`] to format events in a `glog`-inspired fashion. Similar to
5//! `tracing-subscriber`'s [`Full`] formatter, this formatter shows the span context before
6//! printing event data. Spans are displayed including their names and fields. The severity,
7//! time, PID, thread name, file, and line are also included.
8//!
9//! # Example Output
10//!
11//! <pre><font color="#26A269">I</font><font color="#8D8F8A">1201 01:13:04.724801 1025672 </font><b>main</b> [<b>yak_shave</b>] <b>examples/yak-shave.rs</b>:<b>34</b>] preparing to shave yaks, <b>number_of_yaks</b>: 3
12//! <font color="#26A269">I</font><font color="#8D8F8A">1201 01:13:04.724948 1025672 </font><b>main</b> [<b>yak_shave</b>] <b>examples/yak-shave.rs</b>:<b>75</b>] [<b>shaving_yaks</b>{<i><b>yaks</b></i>: 3}] shaving yaks
13//! <font color="#A2734C">W</font><font color="#8D8F8A">1201 01:13:04.725071 1025672 </font><b>main</b> [<b>yak_shave</b>] <b>examples/yak-shave.rs</b>:<b>56</b>] [<b>shaving_yaks</b>{<i><b>yaks</b></i>: 3}, <b>shave</b>{<i><b>yak</b></i>: 3}] could not locate yak
14//! <font color="#C01C28">E</font><font color="#8D8F8A">1201 01:13:04.725135 1025672 </font><b>main</b> [<b>yak_shave</b>] <b>examples/yak-shave.rs</b>:<b>85</b>] [<b>shaving_yaks</b>{<i><b>yaks</b></i>: 3}] failed to shave yak, <b>yak</b>: 3, <b>error</b>: out of cash
15//! <font color="#26A269">I</font><font color="#8D8F8A">1201 01:13:04.725195 1025672 </font><b>main</b> [<b>yak_shave</b>] <b>examples/yak-shave.rs</b>:<b>38</b>] yak shaving completed, <b>all_yaks_shaved</b>: false
16//! </pre>
17//!
18//! # Usage
19//!
20//! With [`fmt::Subscriber`]:
21//!
22//! ```
23//! use tracing_glog::{Glog, GlogFields};
24//!
25//! tracing_subscriber::fmt()
26//!     .event_format(Glog::default())
27//!     .fmt_fields(GlogFields::default())
28//!     .init();
29//! ```
30//!
31//! With [`fmt::Layer`]:
32//!
33//! ```
34//! use tracing_subscriber::prelude::*;
35//! use tracing_subscriber::{fmt, Registry};
36//! use tracing_glog::{Glog, GlogFields};
37//!
38//! let fmt = fmt::Layer::default()
39//!     .event_format(Glog::default())
40//!     .fmt_fields(GlogFields::default());
41//!
42//! let subscriber = Registry::default().with(fmt);
43//! tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber");
44//! ```
45//!
46//! With [`UtcTime`]:
47//!
48//! ```
49//! use tracing_subscriber::prelude::*;
50//! use tracing_subscriber::{fmt, Registry};
51//! use tracing_glog::{Glog, GlogFields};
52//!
53//! let fmt = fmt::Layer::default()
54//!     .event_format(Glog::default().with_timer(tracing_glog::UtcTime::default()))
55//!     .fmt_fields(GlogFields::default());
56//!
57//! let subscriber = Registry::default().with(fmt);
58//! tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber");
59//! ```
60//!
61//! With [`LocalTime`]:
62//!
63//! ```
64//! use tracing_subscriber::prelude::*;
65//! use tracing_subscriber::{fmt, Registry};
66//! use tracing_glog::{Glog, GlogFields};
67//!
68//! let fmt = fmt::Layer::default()
69//!     .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
70//!     .fmt_fields(GlogFields::default());
71//!
72//! let subscriber = Registry::default().with(fmt);
73//! tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber");
74//! ```
75//!
76//! <div class="example-wrap" style="display:inline-block">
77//! <pre class="compile_fail" style="white-space:normal;font:inherit;">
78//!     <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/"><code>time</code>
79//!     crate</a> must be compiled with <code>--cfg unsound_local_offset</code> in order to use
80//!     `LocalTime`. When this cfg is not enabled, local timestamps cannot be recorded, and
81//!     no events will be emitted.
82//!
83//!    See the <a href="https://docs.rs/time/0.3.9/time/#feature-flags"><code>time</code>
84//!    documentation</a> for more details.
85//! </pre></div>
86//!
87//! [glog]: https://github.com/google/glog
88//! [`tracing-subscriber`]: https://docs.rs/tracing-subscriber
89//! [`fmt::Subscriber`]: tracing_subscriber::fmt::Subscriber
90//! [`fmt::Layer`]: tracing_subscriber::fmt::Layer
91//! [`Full`]: tracing_subscriber::fmt::format::Full
92
93#[deny(rustdoc::broken_intra_doc_links)]
94mod format;
95
96#[cfg(feature = "time")]
97pub mod time_crate;
98
99#[cfg(feature = "ansi")]
100mod nu_ansi_term {
101    pub use ::nu_ansi_term::*;
102}
103
104#[cfg(not(feature = "ansi"))]
105mod nu_ansi_term {
106    // Minimal API shim for nu_ansi_term to avoid a pile of #[cfg(feature = "ansi")] directives.
107    #[derive(Copy, Clone)]
108    pub struct Style;
109
110    impl Style {
111        pub fn new() -> Self {
112            Style
113        }
114        pub fn bold(&self) -> Self {
115            Style
116        }
117        pub fn prefix(&self) -> &'static str {
118            ""
119        }
120        pub fn infix(&self, _: Style) -> &'static str {
121            ""
122        }
123        pub fn suffix(&self) -> &'static str {
124            ""
125        }
126    }
127}
128
129use crate::nu_ansi_term::Style;
130use format::FmtLevel;
131pub use format::{FormatLevelChars, LocalTime, UtcTime};
132use std::fmt;
133use tracing::{
134    field::{Field, Visit},
135    Subscriber,
136};
137#[cfg(feature = "tracing-log")]
138use tracing_log::NormalizeEvent;
139use tracing_subscriber::{
140    field::{MakeVisitor, VisitFmt, VisitOutput},
141    fmt::{
142        format::Writer, time::FormatTime, FmtContext, FormatEvent, FormatFields, FormattedFields,
143    },
144    registry::LookupSpan,
145};
146
147use crate::format::{FormatProcessData, FormatSpanFields};
148
149/// A [glog]-inspired span and event formatter.
150///
151/// [glog]: https://github.com/google/glog
152pub struct Glog<T = UtcTime> {
153    timer: T,
154    level_chars: &'static FormatLevelChars,
155    with_span_context: bool,
156    with_thread_names: bool,
157    with_target: bool,
158    with_span_names: bool,
159}
160
161impl<T> Glog<T> {
162    /// Use the given [timer] for span and event time stamps.
163    ///
164    /// `tracing-glog` provides two timers: [`LocalTime`] and [`UtcTime`].
165    /// [`UtcTime`] is the default timer.
166    ///
167    /// [timer]: tracing_subscriber::fmt::time::FormatTime
168    pub fn with_timer<T2>(self, timer: T2) -> Glog<T2>
169    where
170        T2: FormatTime,
171    {
172        Glog {
173            timer,
174            level_chars: self.level_chars,
175            with_thread_names: self.with_thread_names,
176            with_target: self.with_target,
177            with_span_context: self.with_span_context,
178            with_span_names: self.with_span_names,
179        }
180    }
181
182    pub fn with_thread_names(self, with_thread_names: bool) -> Glog<T> {
183        Glog {
184            with_thread_names,
185            ..self
186        }
187    }
188
189    pub fn with_target(self, with_target: bool) -> Glog<T> {
190        Glog {
191            with_target,
192            ..self
193        }
194    }
195
196    /// Sets whether or not the span name is included. Defaults to true.
197    ///
198    /// If span names are not included, then the fields from all spans are
199    /// printed as a single list of fields. This results is a more compact output.
200    ///
201    /// # Example Output
202    /// With `with_span_names` set to true:
203    /// <pre>
204    /// I0731 16:23:45.674465 990039 examples/tokio.rs:38] [parent_task{subtasks: 10, reason: "testing"}, subtask{number: 10}] polling subtask, number: 10
205    /// </pre>
206    ///
207    /// With `with_span_names` set to false:
208    /// <pre>
209    /// I0731 16:23:45.674465 990039 examples/tokio.rs:38] [subtasks: 10, reason: "testing", number: 10] polling subtask, number: 10
210    /// </pre>
211    pub fn with_span_names(self, with_span_names: bool) -> Glog<T> {
212        Glog {
213            with_span_names,
214            ..self
215        }
216    }
217
218    /// Sets whether or not the span context is included. Defaults to true.
219    ///
220    /// By default, formatters building atop of [`mod@tracing_subscriber::fmt`]
221    /// will include the span context as [`fmt::Layer`] and
222    /// [`fmt::Subscriber`] assume that span context is
223    /// is valuable and the _raison d’être_ for using `tracing` and, as such, do not provide a
224    /// toggle. However, users migrating from logging systems such
225    /// as [glog](https://github.com/google/glog) or folly's [`xlog`](https://github.com/facebook/folly/blob/main/folly/logging/xlog.h)
226    /// might find the span context to be overwhelming. Therefore, this formatter-level toggle
227    /// is availible in order to provide a smoother onboarding experience to [`tracing`].
228    ///
229    /// **Notice:** This is a relatively coarse toggle. In most circumstances, usage of
230    /// `tracing-subscriber`'s [`filter_fn`] is preferred to disable spans on a more
231    /// fine-grained basis.
232    ///
233    /// [`fmt::Layer`]: tracing_subscriber::fmt::Layer
234    /// [`fmt::Subscriber`]: tracing_subscriber::fmt::Subscriber
235    /// [per-layer filtering]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#per-layer-filtering
236    /// [`filter_fn`]: fn@tracing_subscriber::filter::filter_fn
237    /// [`tracing`]: mod@tracing
238    pub fn with_span_context(self, with_span_context: bool) -> Glog<T> {
239        Glog {
240            with_span_context,
241            ..self
242        }
243    }
244
245    /// Sets the characters to use to indicate the level for each event.
246    /// Defaults to the initial character of the level.
247    pub fn with_format_level_chars(self, level_chars: &'static FormatLevelChars) -> Glog<T> {
248        Glog {
249            level_chars,
250            ..self
251        }
252    }
253}
254
255impl Default for Glog<UtcTime> {
256    fn default() -> Self {
257        Glog {
258            timer: UtcTime::default(),
259            level_chars: &format::DEFAULT_FORMAT_LEVEL_CHARS,
260            with_thread_names: false,
261            with_target: false,
262            with_span_context: true,
263            with_span_names: true,
264        }
265    }
266}
267
268impl<S, N, T> FormatEvent<S, N> for Glog<T>
269where
270    S: Subscriber + for<'a> LookupSpan<'a>,
271    N: for<'a> FormatFields<'a> + 'static,
272    T: FormatTime,
273{
274    fn format_event(
275        &self,
276        ctx: &FmtContext<'_, S, N>,
277        mut writer: Writer<'_>,
278        event: &tracing::Event<'_>,
279    ) -> fmt::Result {
280        let level = *event.metadata().level();
281
282        // Convert log level to a single character representation.)
283        let level = FmtLevel::format_level(level, self.level_chars, writer.has_ansi_escapes());
284        write!(writer, "{level}")?;
285
286        // write the timestamp:
287        self.timer.format_time(&mut writer)?;
288
289        // get some process information
290        let pid = get_pid();
291        let thread = std::thread::current();
292        let thread_name = thread.name();
293
294        #[cfg(feature = "tracing-log")]
295        let normalized_meta = event.normalized_metadata();
296        #[cfg(feature = "tracing-log")]
297        let metadata = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
298        #[cfg(not(feature = "tracing-log"))]
299        let metadata = event.metadata();
300
301        let data = FormatProcessData {
302            pid,
303            thread_name,
304            with_thread_names: self.with_thread_names,
305            metadata,
306            with_target: self.with_target,
307            #[cfg(feature = "ansi")]
308            ansi: writer.has_ansi_escapes(),
309        };
310        write!(writer, "{data}] ")?;
311
312        if self.with_span_context {
313            // now, we're printing the span context into brackets of `[]`, which glog parsers ignore.
314            let leaf = ctx.lookup_current();
315
316            if let Some(leaf) = leaf {
317                let mut wrote_open_bracket = false;
318
319                // Write spans and fields of each span
320                let mut iter = leaf.scope().from_root();
321                let mut span = iter.next().expect(
322                    "Unable to get the next item in the iterator; this should not be possible.",
323                );
324                loop {
325                    let ext = span.extensions();
326                    let fields = &ext
327                        .get::<FormattedFields<N>>()
328                        .expect("will never be `None`");
329
330                    let fields = if !fields.is_empty() {
331                        Some(fields.as_str())
332                    } else {
333                        None
334                    };
335
336                    if self.with_span_names || fields.is_some() {
337                        if !wrote_open_bracket {
338                            // Write the opening bracket once we know we need one
339                            write!(writer, "[")?;
340                            wrote_open_bracket = true;
341                        }
342                        let fields = FormatSpanFields::format_fields(
343                            span.name(),
344                            fields,
345                            writer.has_ansi_escapes(),
346                            self.with_span_names,
347                        );
348                        write!(writer, "{fields}")?;
349                    }
350
351                    drop(ext);
352                    match iter.next() {
353                        // if there's more, add a space.
354                        Some(next) => {
355                            if wrote_open_bracket {
356                                write!(writer, ", ")?;
357                            }
358                            span = next;
359                        }
360                        // if there's nothing there, close.
361                        None => break,
362                    }
363                }
364                if wrote_open_bracket {
365                    write!(writer, "] ")?;
366                }
367            }
368        }
369        ctx.field_format().format_fields(writer.by_ref(), event)?;
370        writeln!(writer)
371    }
372}
373
374#[derive(Clone)]
375struct FieldConfig {
376    should_quote_strings: bool,
377    use_whitespace_in_field: bool,
378}
379
380impl Default for FieldConfig {
381    fn default() -> Self {
382        Self {
383            should_quote_strings: true,
384            use_whitespace_in_field: true,
385        }
386    }
387}
388
389#[derive(Default)]
390pub struct GlogFields {
391    config: FieldConfig,
392}
393
394impl GlogFields {
395    /// Sets whether or not strings are wrapped in quotes.
396    ///
397    /// This is helpful for reducing line width at the cost of clarity when
398    /// using strings with whitespace as fields on Spans and Events.
399    pub fn should_quote_strings(mut self, value: bool) -> Self {
400        self.config.should_quote_strings = value;
401        self
402    }
403
404    /// Sets whether or not whitespace is added to printed fields.
405    ///
406    /// This defaults to `false`.
407    pub fn use_whitespace_in_field(mut self, value: bool) -> Self {
408        self.config.use_whitespace_in_field = value;
409        self
410    }
411
412    /// Sets the formatter to use compact options.
413    ///
414    /// Setting `.compat()` will set [`GlogFields::use_whitespace_in_field`]
415    /// and [`GlogFields::should_quote_strings`] to false.
416    pub fn compact(self) -> Self {
417        self.should_quote_strings(false)
418            .use_whitespace_in_field(false)
419    }
420}
421
422impl<'a> MakeVisitor<Writer<'a>> for GlogFields {
423    type Visitor = GlogVisitor<'a>;
424
425    #[inline]
426    fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor {
427        GlogVisitor::new(target, self.config.clone())
428    }
429}
430
431#[doc(hidden)]
432pub struct GlogVisitor<'a> {
433    writer: Writer<'a>,
434    is_empty: bool,
435    style: Style,
436    result: fmt::Result,
437    config: FieldConfig,
438}
439
440impl<'a> GlogVisitor<'a> {
441    fn new(writer: Writer<'a>, config: FieldConfig) -> Self {
442        Self {
443            writer,
444            is_empty: true,
445            style: Style::new(),
446            result: Ok(()),
447            config,
448        }
449    }
450
451    fn write_padded(&mut self, value: &impl fmt::Debug) {
452        let padding = if self.is_empty {
453            self.is_empty = false;
454            ""
455        } else {
456            ", "
457        };
458        self.result = write!(self.writer, "{padding}{value:?}");
459    }
460
461    fn write_field(&mut self, name: &str, value: &dyn fmt::Debug) {
462        let bold = self.bold();
463        if self.config.use_whitespace_in_field {
464            self.write_padded(&format_args!(
465                "{}{}{}: {:?}",
466                bold.prefix(),
467                name,
468                bold.infix(self.style),
469                value,
470            ));
471        } else {
472            self.write_padded(&format_args!(
473                "{}{}{}:{:?}",
474                bold.prefix(),
475                name,
476                bold.infix(self.style),
477                value,
478            ));
479        }
480    }
481
482    fn bold(&self) -> Style {
483        if self.writer.has_ansi_escapes() {
484            self.style.bold()
485        } else {
486            Style::new()
487        }
488    }
489}
490
491impl<'a> Visit for GlogVisitor<'a> {
492    fn record_str(&mut self, field: &Field, value: &str) {
493        if self.result.is_err() {
494            return;
495        }
496
497        if field.name() == "message" {
498            self.record_debug(field, &format_args!("{value}"))
499        } else if self.config.should_quote_strings {
500            self.record_debug(field, &value)
501        } else {
502            self.record_debug(field, &format_args!("{value}"))
503        }
504    }
505
506    fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
507        if let Some(source) = value.source() {
508            self.record_debug(
509                field,
510                &format_args!("{}, {}.sources: {}", value, field, ErrorSourceList(source),),
511            )
512        } else {
513            self.record_debug(field, &format_args!("{value}"))
514        }
515    }
516
517    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
518        if self.result.is_err() {
519            return;
520        }
521
522        match field.name() {
523            "message" => self.write_padded(&format_args!("{}{:?}", self.style.prefix(), value,)),
524            // Skip fields that are actually log metadata that have already been handled
525            name if name.starts_with("log.") => self.result = Ok(()),
526            name if name.starts_with("r#") => self.write_field(&name[2..], value),
527            name => self.write_field(name, value),
528        };
529    }
530}
531
532impl<'a> VisitOutput<fmt::Result> for GlogVisitor<'a> {
533    fn finish(mut self) -> fmt::Result {
534        write!(&mut self.writer, "{}", self.style.suffix())?;
535        self.result
536    }
537}
538
539impl<'a> VisitFmt for GlogVisitor<'a> {
540    fn writer(&mut self) -> &mut dyn fmt::Write {
541        &mut self.writer
542    }
543}
544
545/// Renders an error into a list of sources, *including* the error
546struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static));
547
548impl<'a> fmt::Display for ErrorSourceList<'a> {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        let mut list = f.debug_list();
551        let mut curr = Some(self.0);
552        while let Some(curr_err) = curr {
553            list.entry(&format_args!("{curr_err}"));
554            curr = curr_err.source();
555        }
556        list.finish()
557    }
558}
559
560#[inline(always)]
561fn get_pid() -> u32 {
562    std::process::id()
563}