linker_diff/
diagnostics.rs

1//! Provides mechanisms for helping diagnose problems in linker-diff. Specifically, a way to set up
2//! tracing so that we can collect tracing logs within some scope and attach them to a diff report.
3//!
4//! To get tracing messages, make sure the code you want to trace from is called from somewhere
5//! inside a call to `trace_scope`. Assuming it is, you can then call `tracing::trace!()` and see
6//! the output in the diff report for whatever was being diffed.
7
8use std::cell::RefCell;
9use std::fmt::Write as _;
10use tracing_subscriber::layer::SubscriberExt as _;
11
12/// Enable diagnostics. Configures the global tracing subscriber, so cannot be used in conjunction
13/// with other things that do that. For that reason, this should be called from the main binary.
14pub fn enable_diagnostics() {
15    let layer = TraceLayer;
16    let subscriber = tracing_subscriber::Registry::default().with(layer);
17    tracing::subscriber::set_global_default(subscriber)
18        .expect("Only one global tracing subscriber can be setup");
19}
20
21#[derive(Default, Debug, Clone)]
22pub(crate) struct TraceOutput {
23    pub(crate) messages: Vec<String>,
24}
25
26impl TraceOutput {
27    pub(crate) fn append(&mut self, mut other: TraceOutput) {
28        self.messages.append(&mut other.messages);
29    }
30}
31
32thread_local! {
33    pub static TRACE_STACK: RefCell<Vec<TraceOutput>> = const { RefCell::new(Vec::new()) };
34}
35
36/// Runs `f` then returns all trace output emitted while it was running.
37pub(crate) fn trace_scope<T>(trace_output: &mut TraceOutput, f: impl FnOnce() -> T) -> T {
38    TRACE_STACK.with_borrow_mut(|stack| stack.push(TraceOutput::default()));
39
40    let result = f();
41
42    *trace_output = TRACE_STACK.with_borrow_mut(|stack| stack.pop()).unwrap();
43
44    result
45}
46
47struct TraceLayer;
48
49impl<S> tracing_subscriber::Layer<S> for TraceLayer
50where
51    S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
52{
53    fn on_event(
54        &self,
55        event: &tracing::Event<'_>,
56        _ctx: tracing_subscriber::layer::Context<'_, S>,
57    ) {
58        if TRACE_STACK.with_borrow(|stack| stack.is_empty()) {
59            return;
60        }
61
62        let mut formatter = MessageFormatter::default();
63        event.record(&mut formatter);
64
65        TRACE_STACK.with_borrow_mut(|stack| {
66            if let Some(out) = stack.last_mut() {
67                out.messages.push(formatter.out);
68            }
69        });
70    }
71}
72
73#[derive(Default)]
74struct MessageFormatter {
75    out: String,
76}
77
78impl tracing::field::Visit for MessageFormatter {
79    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
80        if !self.out.is_empty() {
81            self.out.push(' ');
82        }
83        let _ = write!(&mut self.out, "{field}={value:?}");
84    }
85}