tui_logger/
tracing_subscriber.rs

1//! `tracing-subscriber` support for `tui-logger`
2
3use super::TUI_LOGGER;
4use log::{self, Log, Record};
5use std::collections::BTreeMap;
6use std::fmt;
7use tracing_subscriber::registry::LookupSpan;
8use tracing_subscriber::Layer;
9
10#[derive(Default)]
11struct ToStringVisitor<'a>(BTreeMap<&'a str, String>);
12
13impl fmt::Display for ToStringVisitor<'_> {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        self.0.iter().try_for_each(|(k, v)| -> fmt::Result {
16            if *k == "message" {
17                write!(f, " {}", v)
18            } else {
19                write!(f, " {}: {}", k, v)
20            }
21        })
22    }
23}
24
25impl<'a> tracing::field::Visit for ToStringVisitor<'a> {
26    fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
27        self.0
28            .insert(field.name(), format_args!("{}", value).to_string());
29    }
30
31    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
32        self.0
33            .insert(field.name(), format_args!("{}", value).to_string());
34    }
35
36    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
37        self.0
38            .insert(field.name(), format_args!("{}", value).to_string());
39    }
40
41    fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
42        self.0
43            .insert(field.name(), format_args!("{}", value).to_string());
44    }
45
46    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
47        self.0
48            .insert(field.name(), format_args!("{}", value).to_string());
49    }
50
51    fn record_error(
52        &mut self,
53        field: &tracing::field::Field,
54        value: &(dyn std::error::Error + 'static),
55    ) {
56        self.0
57            .insert(field.name(), format_args!("{}", value).to_string());
58    }
59
60    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
61        self.0
62            .insert(field.name(), format_args!("{:?}", value).to_string());
63    }
64}
65
66#[allow(clippy::needless_doctest_main)]
67///  tracing-subscriber-compatible layer that feeds messages to `tui-logger`.
68///
69///  ## How it works
70///  Under the hood, tui_logger still uses `log`. `tracing` events are mapped to
71///  `log` events internally (which are then fed to `tui-logger`).
72///
73///  ## Usage note
74///  As per the example below, [init_logger()] must be called prior to logging events.
75///
76///  [init_logger()]: crate::init_logger()
77///  ## Basic usage
78///  ```
79///  use tracing_subscriber::prelude::*;
80///
81///  fn main() {
82///     tracing_subscriber::registry()
83///          .with(tui_logger::TuiTracingSubscriberLayer)
84///          .init();
85///     tui_logger::init_logger(tui_logger::LevelFilter::Trace).unwrap();
86///     tracing::info!("Logging via tracing works!");
87///  }
88///  ```
89
90struct SpanAttributes {
91    attributes: String,
92}
93
94pub struct TuiTracingSubscriberLayer;
95
96impl<S> Layer<S> for TuiTracingSubscriberLayer
97where
98    S: tracing::Subscriber + for<'a> LookupSpan<'a>,
99{
100    fn on_new_span(
101        &self,
102        attrs: &tracing::span::Attributes<'_>,
103        id: &tracing::span::Id,
104        ctx: tracing_subscriber::layer::Context<'_, S>,
105    ) {
106        let mut visitor = ToStringVisitor::default();
107        attrs.record(&mut visitor);
108        ctx.span(id)
109            .unwrap()
110            .extensions_mut()
111            .insert(SpanAttributes {
112                attributes: format!("{}", visitor),
113            });
114    }
115
116    fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
117        let mut visitor = ToStringVisitor::default();
118        event.record(&mut visitor);
119
120        let span_attributes = ctx
121            .event_span(event)
122            .and_then(|s| {
123                s.extensions()
124                    .get::<SpanAttributes>()
125                    .map(|a| a.attributes.to_owned())
126            })
127            .unwrap_or_else(String::new);
128
129        let level = match *event.metadata().level() {
130            tracing::Level::ERROR => log::Level::Error,
131            tracing::Level::WARN => log::Level::Warn,
132            tracing::Level::INFO => log::Level::Info,
133            tracing::Level::DEBUG => log::Level::Debug,
134            tracing::Level::TRACE => log::Level::Trace,
135        };
136
137        TUI_LOGGER.log(
138            &Record::builder()
139                .args(format_args!("{}{}", span_attributes, visitor))
140                .level(level)
141                .target(event.metadata().target())
142                .file(event.metadata().file())
143                .line(event.metadata().line())
144                .module_path(event.metadata().module_path())
145                .build(),
146        );
147    }
148}