micromegas_telemetry_sink/
tracing_interop.rs1use 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 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 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 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
56pub 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
113pub 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}