Skip to main content

micromegas_telemetry_sink/
tracing_interop.rs

1use micromegas_tracing::{
2    dispatch::{log_interop, log_interop_dyn_tagged},
3    levels::LevelFilter,
4    logs::{FILTER_LEVEL_UNSET_VALUE, LogMetadata},
5    property_set::DynProperty,
6};
7use std::sync::atomic::AtomicU32;
8use tracing_subscriber::{layer::Context, prelude::*};
9
10use std::fmt::{self, Write};
11use tracing::field::{Field, Visit};
12pub struct FieldFormatVisitor<'a> {
13    pub buffer: &'a mut String,
14    pub target: Option<String>,
15    /// Event fields that become dynamic properties, in addition to their message text.
16    pub properties: Vec<(&'static str, String)>,
17}
18
19impl FieldFormatVisitor<'_> {
20    fn write_message_part(&mut self, field: &Field, value: &dyn fmt::Debug) {
21        if field.name() == "message" {
22            write!(self.buffer, "{value:?} ").unwrap();
23        } else {
24            write!(self.buffer, "{}={:?} ", field.name(), value).unwrap();
25        }
26    }
27
28    // `log.*` fields only carry the origin of a bridged `log` record.
29    fn is_property(field: &Field) -> bool {
30        field.name() != "message" && !field.name().starts_with("log.")
31    }
32}
33
34impl Visit for FieldFormatVisitor<'_> {
35    fn record_str(&mut self, field: &Field, value: &str) {
36        if field.name() == "log.target" {
37            self.target = Some(value.to_owned());
38            return;
39        }
40        if Self::is_property(field) {
41            self.properties.push((field.name(), value.to_owned()));
42        }
43        self.write_message_part(field, &value);
44    }
45
46    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
47        let formatted = format!("{value:?}");
48        // `Arguments` debug-prints verbatim, so the value is not formatted a second time.
49        self.write_message_part(field, &format_args!("{formatted}"));
50        if Self::is_property(field) {
51            self.properties.push((field.name(), formatted));
52        }
53    }
54}
55
56/// A tracing layer compatible with `tracing_subscriber`.
57///
58/// Setting-up this layer still requires the proper initialization of a `TelemetryGuard`.
59pub struct TracingCaptureLayer {
60    pub max_level: LevelFilter,
61}
62
63impl<S> tracing_subscriber::Layer<S> for TracingCaptureLayer
64where
65    S: tracing::Subscriber,
66{
67    fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
68        let level = tracing_level_to_mm_level(metadata.level());
69        level <= self.max_level
70    }
71
72    fn on_event(
73        &self,
74        event: &tracing::Event<'_>,
75        _ctx: tracing_subscriber::layer::Context<'_, S>,
76    ) {
77        let level = tracing_level_to_mm_level(event.metadata().level());
78        if level > self.max_level {
79            return;
80        }
81        let mut buffer = String::new();
82        let mut formatter = FieldFormatVisitor {
83            buffer: &mut buffer,
84            target: None,
85            properties: vec![],
86        };
87        event.record(&mut formatter);
88        let FieldFormatVisitor {
89            target, properties, ..
90        } = formatter;
91        let log_desc = LogMetadata {
92            level,
93            level_filter: AtomicU32::new(FILTER_LEVEL_UNSET_VALUE),
94            fmt_str: "{}",
95            target: target.as_deref().unwrap_or(event.metadata().target()),
96            module_path: event.metadata().module_path().unwrap_or_default(),
97            file: "",
98            line: 0,
99        };
100
101        if properties.is_empty() {
102            log_interop(&log_desc, format_args!("{}", buffer));
103        } else {
104            let dyn_properties: Vec<DynProperty<'_>> = properties
105                .iter()
106                .map(|(name, value)| DynProperty::new(name, value))
107                .collect();
108            log_interop_dyn_tagged(&log_desc, &dyn_properties, format_args!("{}", buffer));
109        }
110    }
111}
112
113/// Installs a `tracing` layer that forwards events to the Micromegas tracing system.
114///
115/// This allows applications using the `tracing` crate to integrate with Micromegas telemetry.
116///
117/// # Arguments
118///
119/// * `interop_max_level_override` - An optional `LevelFilter` to override the maximum level
120///   for the `tracing` layer. If `None`, the global Micromegas max level is used.
121pub fn install_tracing_interop(interop_max_level_override: Option<LevelFilter>) {
122    let max_level = interop_max_level_override.unwrap_or(micromegas_tracing::levels::max_level());
123
124    tracing_subscriber::registry()
125        .with(TracingCaptureLayer { max_level })
126        .init();
127    tracing::debug!("installed tracing interop");
128}
129
130fn tracing_level_to_mm_level(level: &tracing_core::Level) -> micromegas_tracing::levels::Level {
131    match *level {
132        tracing_core::Level::ERROR => micromegas_tracing::levels::Level::Error,
133        tracing_core::Level::WARN => micromegas_tracing::levels::Level::Warn,
134        tracing_core::Level::INFO => micromegas_tracing::levels::Level::Info,
135        tracing_core::Level::DEBUG => micromegas_tracing::levels::Level::Debug,
136        tracing_core::Level::TRACE => micromegas_tracing::levels::Level::Trace,
137    }
138}