tracing_rewrite/
lib.rs

1use tracing::{field::FieldSet, Event, Level, Metadata, Subscriber};
2use tracing_core::Kind;
3use tracing_subscriber::{
4    fmt::{format::Writer, FmtContext, FormatEvent, FormatFields},
5    registry::LookupSpan,
6};
7
8pub struct EventFormatter<const VISITOR_SIZE: usize, F, T> {
9    formatter: F,
10    check: T,
11}
12
13impl<const VISITOR_SIZE: usize, F, T> EventFormatter<VISITOR_SIZE, F, T>
14where
15    T: Fn(&Metadata<'static>) -> Option<Level> + Send + Sync,
16{
17    pub fn new(formatter: F, check: T) -> Self {
18        Self { formatter, check }
19    }
20}
21
22impl<const VISITOR_SIZE: usize, F, T, S, N> FormatEvent<S, N> for EventFormatter<VISITOR_SIZE, F, T>
23where
24    F: FormatEvent<S, N>,
25    T: Fn(&Metadata<'static>) -> Option<Level> + Send + Sync,
26    S: Subscriber + for<'a> LookupSpan<'a>,
27    N: for<'a> FormatFields<'a> + 'static,
28{
29    fn format_event(
30        &self,
31        ctx: &FmtContext<'_, S, N>,
32        writer: Writer<'_>,
33        event: &Event<'_>,
34    ) -> std::fmt::Result {
35        let metadata = event.metadata();
36
37        if let Some(level) = (self.check)(metadata) {
38            let kind = if metadata.is_event() {
39                Kind::EVENT
40            } else if metadata.is_span() {
41                Kind::SPAN
42            } else {
43                unreachable!()
44            };
45
46            let fields = metadata.fields();
47            // Safety: at the moment of writing this code, FieldSet is made like
48            // ```rust
49            // pub struct FieldSet {
50            //   names: &'static [&'static str],
51            //   callsite: callsite::Identifier,
52            // }
53            // ```
54            // and Identifier is make like
55            // ```rust
56            // #[derive(Clone)]
57            // pub struct Identifier(
58            //   #[doc(hidden)]
59            //   pub &'static dyn Callsite,
60            // );
61            // ```
62            // that means we can copy the static references without causing any UB
63            let cloned = unsafe { std::mem::transmute_copy::<FieldSet, FieldSet>(fields) };
64
65            // here we are leaking memory, but should be mainly references
66            let metadata = Box::leak::<'static>(Box::new(Metadata::new(
67                metadata.name(),
68                metadata.target(),
69                level,
70                metadata.file(),
71                metadata.line(),
72                metadata.module_path(),
73                cloned,
74                kind,
75            )));
76
77            let mut visitor = visitor::Visitor::<VISITOR_SIZE>::new();
78            event.record(&mut visitor);
79            let values = visitor.get_values();
80            let valueset = fields.value_set(&values);
81            let event = if let Some(parent) = event.parent() {
82                Event::new_child_of(parent, metadata, &valueset)
83            } else {
84                Event::new(metadata, &valueset)
85            };
86            let res = self.formatter.format_event(ctx, writer, &event);
87
88            // here we're freeing the leaked memory
89            // Miri tells us we're doing an invalid operation, because metadata is borrowed for 'static
90            // and we don't have any guarantee the implementor of the trait is keeping references to it
91            // that is possible, but unlikely.
92            // If you're experiencing UB, please enable `i_really_want_memory_leak`  feature
93            #[cfg(not(feature = "i_really_want_memory_leak"))]
94            drop(unsafe { Box::from_raw(metadata as *const Metadata as *mut Metadata) });
95
96            res
97        } else {
98            self.formatter.format_event(ctx, writer, event)
99        }
100    }
101}
102
103mod visitor {
104    use std::fmt::Debug;
105
106    use tracing::{field::Visit, Level, Metadata, Value};
107    use tracing_core::{metadata, Callsite, Field, Interest, Kind};
108
109    const FAKE_FIELD_NAME: &str = "foo";
110
111    // tracing automatically filters out fields with a different call site
112    struct FakeCallSite();
113    static FAKE_CALLSITE: FakeCallSite = FakeCallSite();
114    static FAKE_META: Metadata<'static> = metadata! {
115        name: "",
116        target: module_path!(),
117        level: Level::INFO,
118        fields: &[FAKE_FIELD_NAME],
119        callsite: &FAKE_CALLSITE,
120        kind: Kind::SPAN,
121    };
122
123    impl Callsite for FakeCallSite {
124        fn set_interest(&self, _: Interest) {
125            unimplemented!()
126        }
127
128        fn metadata(&self) -> &Metadata<'_> {
129            &FAKE_META
130        }
131    }
132
133    pub struct Visitor<const N: usize> {
134        index: usize,
135        // TODO: avoid allocating with String
136        values: [(Field, Option<String>); N],
137    }
138
139    impl<const N: usize> Visitor<N> {
140        pub fn new() -> Self {
141            Visitor {
142                index: 0,
143                values: [(); N].map(|_| (FAKE_META.fields().field(FAKE_FIELD_NAME).unwrap(), None)),
144            }
145        }
146
147        pub fn get_values(&self) -> [(&Field, Option<&dyn Value>); N] {
148            let mut index = 0;
149            [(); N].map(|_| {
150                let val = (
151                    &self.values[index].0,
152                    self.values[index].1.as_ref().map(|s| s as &dyn Value),
153                );
154                index += 1;
155                val
156            })
157        }
158    }
159
160    impl<const N: usize> Visit for Visitor<N> {
161        fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
162            // Safety: same assumptions as before, becuase Field is like
163            // ```rust
164            // #[derive(Debug)]
165            // pub struct Field {
166            //     i: usize,
167            //     fields: FieldSet,
168            // }
169            // ```
170            let cloned = unsafe { std::mem::transmute_copy::<Field, Field>(field) };
171            self.values[self.index] = (cloned, Some(format!("{value:?}")));
172            self.index += 1;
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use tracing::{Level, Metadata};
180    use tracing_subscriber::{
181        fmt,
182        util::{SubscriberInitExt, TryInitError},
183        EnvFilter,
184    };
185
186    fn init_tracing(
187        check: impl Fn(&Metadata<'static>) -> Option<Level> + Send + Sync + 'static,
188    ) -> Result<(), TryInitError> {
189        let format = fmt::format()
190            .with_target(true)
191            .with_line_number(true)
192            .with_thread_ids(true)
193            .compact();
194
195        // Miri doesn't allow accessing system time
196        #[cfg(miri)]
197        let format = format.without_time();
198
199        let builder = fmt::Subscriber::builder();
200
201        builder
202            .with_env_filter(EnvFilter::from_default_env())
203            .event_format(format)
204            .map_event_format(|formatter| super::EventFormatter::<10, _, _>::new(formatter, check))
205            .finish()
206            .try_init()
207    }
208
209    #[test]
210    fn miri_tracing() {
211        init_tracing(|metadata| {
212            (dbg!(metadata.file()).is_some_and(|file| file == "src/lib.rs")
213                && Level::ERROR.eq(metadata.level()))
214            .then_some(Level::WARN)
215        })
216        .unwrap();
217
218        tracing::error!("test");
219    }
220}