micromegas_telemetry_sink/
tracing_interop.rs

1use micromegas_tracing::{
2    dispatch::log_interop,
3    levels::LevelFilter,
4    logs::{FILTER_LEVEL_UNSET_VALUE, LogMetadata},
5};
6use std::sync::atomic::AtomicU32;
7use tracing_subscriber::{layer::Context, prelude::*};
8
9use std::fmt::{self, Write};
10use tracing::field::{Field, Visit};
11pub struct FieldFormatVisitor<'a> {
12    pub buffer: &'a mut String,
13    pub target: Option<String>,
14}
15
16impl Visit for FieldFormatVisitor<'_> {
17    fn record_str(&mut self, field: &Field, value: &str) {
18        if field.name() == "log.target" {
19            self.target = Some(value.to_owned());
20        } else {
21            self.record_debug(field, &value)
22        }
23    }
24    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
25        if field.name() == "message" {
26            write!(self.buffer, "{value:?} ").unwrap();
27        } else {
28            write!(self.buffer, "{}={:?} ", field.name(), value).unwrap();
29        }
30    }
31}
32
33/// A tracing layer compatible with `tracing_subscriber`.
34///
35/// Setting-up this layer still requires the proper initialization of a `TelemetryGuard`.
36pub struct TracingCaptureLayer {
37    pub max_level: LevelFilter,
38}
39
40impl<S> tracing_subscriber::Layer<S> for TracingCaptureLayer
41where
42    S: tracing::Subscriber,
43{
44    fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
45        let level = tracing_level_to_mm_level(metadata.level());
46        level <= self.max_level
47    }
48
49    fn on_event(
50        &self,
51        event: &tracing::Event<'_>,
52        _ctx: tracing_subscriber::layer::Context<'_, S>,
53    ) {
54        let level = tracing_level_to_mm_level(event.metadata().level());
55        if level > self.max_level {
56            return;
57        }
58        let mut buffer = String::new();
59        let mut formatter = FieldFormatVisitor {
60            buffer: &mut buffer,
61            target: None,
62        };
63        event.record(&mut formatter);
64        let log_desc = LogMetadata {
65            level,
66            level_filter: AtomicU32::new(FILTER_LEVEL_UNSET_VALUE),
67            fmt_str: "{}",
68            target: formatter
69                .target
70                .as_deref()
71                .unwrap_or(event.metadata().target()),
72            module_path: event.metadata().module_path().unwrap_or_default(),
73            file: "",
74            line: 0,
75        };
76
77        log_interop(&log_desc, format_args!("{}", &buffer));
78    }
79}
80
81/// Installs a `tracing` layer that forwards events to the Micromegas tracing system.
82///
83/// This allows applications using the `tracing` crate to integrate with Micromegas telemetry.
84///
85/// # Arguments
86///
87/// * `interop_max_level_override` - An optional `LevelFilter` to override the maximum level
88///   for the `tracing` layer. If `None`, the global Micromegas max level is used.
89pub fn install_tracing_interop(interop_max_level_override: Option<LevelFilter>) {
90    let max_level = interop_max_level_override.unwrap_or(micromegas_tracing::levels::max_level());
91
92    tracing_subscriber::registry()
93        .with(TracingCaptureLayer { max_level })
94        .init();
95    tracing::debug!("installed tracing interop");
96}
97
98fn tracing_level_to_mm_level(level: &tracing_core::Level) -> micromegas_tracing::levels::Level {
99    match *level {
100        tracing_core::Level::ERROR => micromegas_tracing::levels::Level::Error,
101        tracing_core::Level::WARN => micromegas_tracing::levels::Level::Warn,
102        tracing_core::Level::INFO => micromegas_tracing::levels::Level::Info,
103        tracing_core::Level::DEBUG => micromegas_tracing::levels::Level::Debug,
104        tracing_core::Level::TRACE => micromegas_tracing::levels::Level::Trace,
105    }
106}