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::{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    with_span_context: bool,
155    with_thread_names: bool,
156    with_target: bool,
157    with_span_names: bool,
158}
159
160impl<T> Glog<T> {
161    /// Use the given [timer] for span and event time stamps.
162    ///
163    /// `tracing-glog` provides two timers: [`LocalTime`] and [`UtcTime`].
164    /// [`UtcTime`] is the default timer.
165    ///
166    /// [timer]: tracing_subscriber::fmt::time::FormatTime
167    pub fn with_timer<T2>(self, timer: T2) -> Glog<T2>
168    where
169        T2: FormatTime,
170    {
171        Glog {
172            timer,
173            with_thread_names: self.with_thread_names,
174            with_target: self.with_target,
175            with_span_context: self.with_span_context,
176            with_span_names: self.with_span_names,
177        }
178    }
179
180    pub fn with_thread_names(self, with_thread_names: bool) -> Glog<T> {
181        Glog {
182            with_thread_names,
183            ..self
184        }
185    }
186
187    pub fn with_target(self, with_target: bool) -> Glog<T> {
188        Glog {
189            with_target,
190            ..self
191        }
192    }
193
194    /// Sets whether or not the span name is included. Defaults to true.
195    ///
196    /// If span names are not included, then the fields from all spans are
197    /// printed as a single list of fields. This results is a more compact output.
198    ///
199    /// # Example Output
200    /// With `with_span_names` set to true:
201    /// <pre>
202    /// I0731 16:23:45.674465 990039 examples/tokio.rs:38] [parent_task{subtasks: 10, reason: "testing"}, subtask{number: 10}] polling subtask, number: 10
203    /// </pre>
204    ///
205    /// With `with_span_names` set to false:
206    /// <pre>
207    /// I0731 16:23:45.674465 990039 examples/tokio.rs:38] [subtasks: 10, reason: "testing", number: 10] polling subtask, number: 10
208    /// </pre>
209    pub fn with_span_names(self, with_span_names: bool) -> Glog<T> {
210        Glog {
211            with_span_names,
212            ..self
213        }
214    }
215
216    /// Sets whether or not the span context is included. Defaults to true.
217    ///
218    /// By default, formatters building atop of [`mod@tracing_subscriber::fmt`]
219    /// will include the span context as [`fmt::Layer`] and
220    /// [`fmt::Subscriber`] assume that span context is
221    /// is valuable and the _raison d’être_ for using `tracing` and, as such, do not provide a
222    /// toggle. However, users migrating from logging systems such
223    /// as [glog](https://github.com/google/glog) or folly's [`xlog`](https://github.com/facebook/folly/blob/main/folly/logging/xlog.h)
224    /// might find the span context to be overwhelming. Therefore, this formatter-level toggle
225    /// is availible in order to provide a smoother onboarding experience to [`tracing`].
226    ///
227    /// **Notice:** This is a relatively coarse toggle. In most circumstances, usage of
228    /// `tracing-subscriber`'s [`filter_fn`] is preferred to disable spans on a more
229    /// fine-grained basis.
230    ///
231    /// [`fmt::Layer`]: tracing_subscriber::fmt::Layer
232    /// [`fmt::Subscriber`]: tracing_subscriber::fmt::Subscriber
233    /// [per-layer filtering]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#per-layer-filtering
234    /// [`filter_fn`]: fn@tracing_subscriber::filter::filter_fn
235    /// [`tracing`]: mod@tracing
236    pub fn with_span_context(self, with_span_context: bool) -> Glog<T> {
237        Glog {
238            with_span_context,
239            ..self
240        }
241    }
242}
243
244impl Default for Glog<UtcTime> {
245    fn default() -> Self {
246        Glog {
247            timer: UtcTime::default(),
248            with_thread_names: false,
249            with_target: false,
250            with_span_context: true,
251            with_span_names: true,
252        }
253    }
254}
255
256impl<S, N, T> FormatEvent<S, N> for Glog<T>
257where
258    S: Subscriber + for<'a> LookupSpan<'a>,
259    N: for<'a> FormatFields<'a> + 'static,
260    T: FormatTime,
261{
262    fn format_event(
263        &self,
264        ctx: &FmtContext<'_, S, N>,
265        mut writer: Writer<'_>,
266        event: &tracing::Event<'_>,
267    ) -> fmt::Result {
268        let level = *event.metadata().level();
269
270        // Convert log level to a single character representation.)
271        let level = FmtLevel::format_level(level, writer.has_ansi_escapes());
272        write!(writer, "{}", level)?;
273
274        // write the timestamp:
275        self.timer.format_time(&mut writer)?;
276
277        // get some process information
278        let pid = get_pid();
279        let thread = std::thread::current();
280        let thread_name = thread.name();
281
282        #[cfg(feature = "tracing-log")]
283        let normalized_meta = event.normalized_metadata();
284        #[cfg(feature = "tracing-log")]
285        let metadata = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
286        #[cfg(not(feature = "tracing-log"))]
287        let metadata = event.metadata();
288
289        let data = FormatProcessData {
290            pid,
291            thread_name,
292            with_thread_names: self.with_thread_names,
293            metadata,
294            with_target: self.with_target,
295            #[cfg(feature = "ansi")]
296            ansi: writer.has_ansi_escapes(),
297        };
298        write!(writer, "{}] ", data)?;
299
300        if self.with_span_context {
301            // now, we're printing the span context into brackets of `[]`, which glog parsers ignore.
302            let leaf = ctx.lookup_current();
303
304            if let Some(leaf) = leaf {
305                let mut wrote_open_bracket = false;
306
307                // Write spans and fields of each span
308                let mut iter = leaf.scope().from_root();
309                let mut span = iter.next().expect(
310                    "Unable to get the next item in the iterator; this should not be possible.",
311                );
312                loop {
313                    let ext = span.extensions();
314                    let fields = &ext
315                        .get::<FormattedFields<N>>()
316                        .expect("will never be `None`");
317
318                    let fields = if !fields.is_empty() {
319                        Some(fields.as_str())
320                    } else {
321                        None
322                    };
323
324                    if self.with_span_names || fields.is_some() {
325                        if !wrote_open_bracket {
326                            // Write the opening bracket once we know we need one
327                            write!(writer, "[")?;
328                            wrote_open_bracket = true;
329                        }
330                        let fields = FormatSpanFields::format_fields(
331                            span.name(),
332                            fields,
333                            writer.has_ansi_escapes(),
334                            self.with_span_names,
335                        );
336                        write!(writer, "{}", fields)?;
337                    }
338
339                    drop(ext);
340                    match iter.next() {
341                        // if there's more, add a space.
342                        Some(next) => {
343                            if wrote_open_bracket {
344                                write!(writer, ", ")?;
345                            }
346                            span = next;
347                        }
348                        // if there's nothing there, close.
349                        None => break,
350                    }
351                }
352                if wrote_open_bracket {
353                    write!(writer, "] ")?;
354                }
355            }
356        }
357        ctx.field_format().format_fields(writer.by_ref(), event)?;
358        writeln!(writer)
359    }
360}
361
362#[derive(Clone)]
363struct FieldConfig {
364    should_quote_strings: bool,
365    use_whitespace_in_field: bool,
366}
367
368impl Default for FieldConfig {
369    fn default() -> Self {
370        Self {
371            should_quote_strings: true,
372            use_whitespace_in_field: true,
373        }
374    }
375}
376
377#[derive(Default)]
378pub struct GlogFields {
379    config: FieldConfig,
380}
381
382impl GlogFields {
383    /// Sets whether or not strings are wrapped in quotes.
384    ///
385    /// This is helpful for reducing line width at the cost of clarity when
386    /// using strings with whitespace as fields on Spans and Events.
387    pub fn should_quote_strings(mut self, value: bool) -> Self {
388        self.config.should_quote_strings = value;
389        self
390    }
391
392    /// Sets whether or not whitespace is added to printed fields.
393    ///
394    /// This defaults to `false`.
395    pub fn use_whitespace_in_field(mut self, value: bool) -> Self {
396        self.config.use_whitespace_in_field = value;
397        self
398    }
399
400    /// Sets the formatter to use compact options.
401    ///
402    /// Setting `.compat()` will set [`GlogFields::use_whitespace_in_field`]
403    /// and [`GlogFields::should_quote_strings`] to false.
404    pub fn compact(self) -> Self {
405        self.should_quote_strings(false)
406            .use_whitespace_in_field(false)
407    }
408}
409
410impl<'a> MakeVisitor<Writer<'a>> for GlogFields {
411    type Visitor = GlogVisitor<'a>;
412
413    #[inline]
414    fn make_visitor(&self, target: Writer<'a>) -> Self::Visitor {
415        GlogVisitor::new(target, self.config.clone())
416    }
417}
418
419#[doc(hidden)]
420pub struct GlogVisitor<'a> {
421    writer: Writer<'a>,
422    is_empty: bool,
423    style: Style,
424    result: fmt::Result,
425    config: FieldConfig,
426}
427
428impl<'a> GlogVisitor<'a> {
429    fn new(writer: Writer<'a>, config: FieldConfig) -> Self {
430        Self {
431            writer,
432            is_empty: true,
433            style: Style::new(),
434            result: Ok(()),
435            config,
436        }
437    }
438
439    fn write_padded(&mut self, value: &impl fmt::Debug) {
440        let padding = if self.is_empty {
441            self.is_empty = false;
442            ""
443        } else {
444            ", "
445        };
446        self.result = write!(self.writer, "{}{:?}", padding, value);
447    }
448
449    fn write_field(&mut self, name: &str, value: &dyn fmt::Debug) {
450        let bold = self.bold();
451        if self.config.use_whitespace_in_field {
452            self.write_padded(&format_args!(
453                "{}{}{}: {:?}",
454                bold.prefix(),
455                name,
456                bold.infix(self.style),
457                value,
458            ));
459        } else {
460            self.write_padded(&format_args!(
461                "{}{}{}:{:?}",
462                bold.prefix(),
463                name,
464                bold.infix(self.style),
465                value,
466            ));
467        }
468    }
469
470    fn bold(&self) -> Style {
471        if self.writer.has_ansi_escapes() {
472            self.style.bold()
473        } else {
474            Style::new()
475        }
476    }
477}
478
479impl<'a> Visit for GlogVisitor<'a> {
480    fn record_str(&mut self, field: &Field, value: &str) {
481        if self.result.is_err() {
482            return;
483        }
484
485        if field.name() == "message" {
486            self.record_debug(field, &format_args!("{}", value))
487        } else if self.config.should_quote_strings {
488            self.record_debug(field, &value)
489        } else {
490            self.record_debug(field, &format_args!("{}", value))
491        }
492    }
493
494    fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
495        if let Some(source) = value.source() {
496            self.record_debug(
497                field,
498                &format_args!("{}, {}.sources: {}", value, field, ErrorSourceList(source),),
499            )
500        } else {
501            self.record_debug(field, &format_args!("{}", value))
502        }
503    }
504
505    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
506        if self.result.is_err() {
507            return;
508        }
509
510        match field.name() {
511            "message" => self.write_padded(&format_args!("{}{:?}", self.style.prefix(), value,)),
512            // Skip fields that are actually log metadata that have already been handled
513            name if name.starts_with("log.") => self.result = Ok(()),
514            name if name.starts_with("r#") => self.write_field(&name[2..], value),
515            name => self.write_field(name, value),
516        };
517    }
518}
519
520impl<'a> VisitOutput<fmt::Result> for GlogVisitor<'a> {
521    fn finish(mut self) -> fmt::Result {
522        write!(&mut self.writer, "{}", self.style.suffix())?;
523        self.result
524    }
525}
526
527impl<'a> VisitFmt for GlogVisitor<'a> {
528    fn writer(&mut self) -> &mut dyn fmt::Write {
529        &mut self.writer
530    }
531}
532
533/// Renders an error into a list of sources, *including* the error
534struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static));
535
536impl<'a> fmt::Display for ErrorSourceList<'a> {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        let mut list = f.debug_list();
539        let mut curr = Some(self.0);
540        while let Some(curr_err) = curr {
541            list.entry(&format_args!("{}", curr_err));
542            curr = curr_err.source();
543        }
544        list.finish()
545    }
546}
547
548#[inline(always)]
549fn get_pid() -> u32 {
550    std::process::id()
551}