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