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