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}