blazesym_c/
trace.rs

1use std::ffi::c_char;
2use std::ffi::CStr;
3use std::io;
4use std::io::BufRead as _;
5use std::io::Cursor;
6
7use tracing::subscriber::set_global_default as set_global_subscriber;
8use tracing_subscriber::filter::LevelFilter;
9use tracing_subscriber::fmt;
10use tracing_subscriber::fmt::format::FmtSpan;
11use tracing_subscriber::fmt::time::SystemTime;
12use tracing_subscriber::FmtSubscriber;
13
14use crate::blaze_err;
15#[cfg(doc)]
16use crate::blaze_err_last;
17use crate::set_last_err;
18
19
20/// The level at which to emit traces.
21#[repr(transparent)]
22#[derive(Copy, Clone, Debug, PartialEq)]
23pub struct blaze_trace_lvl(u8);
24
25impl blaze_trace_lvl {
26    /// Emit all trace events.
27    ///
28    /// This is the most verbose level and includes all others.
29    pub const TRACE: blaze_trace_lvl = blaze_trace_lvl(0);
30    /// Emit debug traces and above.
31    ///
32    /// This level excludes traces emitted with "TRACE" verbosity.
33    pub const DEBUG: blaze_trace_lvl = blaze_trace_lvl(1);
34    /// Emit info level traces and above.
35    ///
36    /// This level excludes traces emitted with "TRACE" or "DEBUG"
37    /// verbosity.
38    pub const INFO: blaze_trace_lvl = blaze_trace_lvl(2);
39    /// Only emit warnings.
40    pub const WARN: blaze_trace_lvl = blaze_trace_lvl(3);
41}
42
43
44impl From<blaze_trace_lvl> for LevelFilter {
45    fn from(other: blaze_trace_lvl) -> Self {
46        match other {
47            blaze_trace_lvl::WARN => LevelFilter::WARN,
48            blaze_trace_lvl::INFO => LevelFilter::INFO,
49            blaze_trace_lvl::DEBUG => LevelFilter::DEBUG,
50            blaze_trace_lvl::TRACE => LevelFilter::TRACE,
51            _ => LevelFilter::TRACE,
52        }
53    }
54}
55
56
57/// The signature of a callback function as passed to [`blaze_trace`].
58pub type blaze_trace_cb = extern "C" fn(*const c_char);
59
60
61struct LineWriter<F> {
62    /// A buffer used for formatting traces.
63    buf: Vec<u8>,
64    /// The callback used for emitting formatted traces.
65    f: F,
66}
67
68impl<F> LineWriter<F> {
69    fn new(f: F) -> Self {
70        Self { buf: Vec::new(), f }
71    }
72}
73
74impl<F> io::Write for LineWriter<F>
75where
76    F: FnMut(&CStr),
77{
78    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
79        let delim = b'\n';
80        let mut read = 0;
81        let mut cursor = Cursor::new(buf);
82
83        loop {
84            let n = cursor.read_until(delim, &mut self.buf)?;
85            if n == 0 {
86                break Ok(read)
87            }
88            read += n;
89
90            if self.buf.last() == Some(&delim) {
91                // We reached a complete line. Emit it via the callback.
92                let () = self.buf.push(b'\0');
93                // SAFETY: We properly NUL terminated the C string.
94                let cstr = unsafe { CStr::from_ptr(self.buf.as_ptr().cast()) };
95                let () = (self.f)(cstr);
96                let () = self.buf.clear();
97            } else {
98                break Ok(read)
99            }
100        }
101    }
102
103    fn flush(&mut self) -> io::Result<()> {
104        // We flush on a per-line basis.
105        Ok(())
106    }
107}
108
109
110/// Enable the main library's tracing infrastructure and invoke a
111/// callback function for each emitted trace line.
112///
113/// The provided [`blaze_trace_lvl`] determines what kind of traces are
114/// emitted.
115///
116/// This function should be invoked at most once. Subsequent invocations
117/// will not affect tracing behavior.
118///
119/// On error the function sets the thread's last error to indicate the
120/// problem encountered. Use [`blaze_err_last`] to retrieve this error.
121///
122/// # Notes
123/// - the format of emitted lines is unspecified and subject to change; it is
124///   meant for human consumption and not programmatic evaluation
125#[no_mangle]
126pub extern "C" fn blaze_trace(lvl: blaze_trace_lvl, cb: blaze_trace_cb) {
127    let format = fmt::format().with_target(false).compact();
128    let subscriber = FmtSubscriber::builder()
129        .event_format(format)
130        .with_max_level(LevelFilter::from(lvl))
131        .with_span_events(FmtSpan::FULL)
132        .with_timer(SystemTime)
133        .with_writer(move || {
134            let emit = move |cstr: &CStr| cb(cstr.as_ptr());
135            LineWriter::new(emit)
136        })
137        .finish();
138
139    let err = set_global_subscriber(subscriber)
140        .map(|()| blaze_err::OK)
141        .unwrap_or(blaze_err::ALREADY_EXISTS);
142    let () = set_last_err(err);
143}
144
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    use std::cmp::max;
151    use std::hash::BuildHasher as _;
152    use std::hash::Hasher as _;
153    use std::hash::RandomState;
154    use std::io::Write as _;
155
156    use blazesym::__private::ReadRaw;
157
158
159    /// Test that we can convert `blaze_trace_lvl` values into their
160    /// `LevelFilter` counter parts.
161    #[test]
162    fn lvl_conversions() {
163        assert_eq!(
164            LevelFilter::from(blaze_trace_lvl::DEBUG),
165            LevelFilter::DEBUG
166        );
167        assert_eq!(LevelFilter::from(blaze_trace_lvl::INFO), LevelFilter::INFO);
168        assert_eq!(
169            LevelFilter::from(blaze_trace_lvl::TRACE),
170            LevelFilter::TRACE
171        );
172        assert_eq!(LevelFilter::from(blaze_trace_lvl::WARN), LevelFilter::WARN);
173    }
174
175    /// Check that our `CbWriter` works as expected.
176    #[test]
177    fn line_writing() {
178        let data = br"INFO symbolize: new src=Process(self) addrs=AbsAddr([0x0])
179INFO symbolize: enter src=Process(self) addrs=AbsAddr([0x0])
180INFO symbolize:handle_unknown_addr: new src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
181INFO symbolize:handle_unknown_addr: enter src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
182INFO symbolize:handle_unknown_addr: exit src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
183INFO symbolize:handle_unknown_addr: close src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
184INFO symbolize: exit src=Process(self) addrs=AbsAddr([0x0])
185INFO symbolize: close src=Process(self) addrs=AbsAddr([0x0])
186";
187        let mut to_write = &data[..];
188
189        fn rand() -> u64 {
190            RandomState::new().build_hasher().finish()
191        }
192
193        let mut bytes = Vec::new();
194        let mut writer = LineWriter::new(|line: &CStr| {
195            let data = line.to_bytes();
196            assert!(data.ends_with(b"\n"), "{line:?}");
197            assert!(
198                !data[..data.len().saturating_sub(1)].contains(&b'\n'),
199                "{line:?}"
200            );
201            let () = bytes.extend_from_slice(data);
202        });
203
204        // Simulate writing of all of `data` into our `LineWriter`
205        // instance in arbitrary length chunks and check that it emits
206        // back all the lines contained in the original data.
207        while !to_write.is_empty() {
208            let cnt = max(rand() % (max(to_write.len() as u64 / 2, 1)), 1) as usize;
209            let data = to_write.read_slice(cnt).unwrap();
210            let n = writer.write(data).unwrap();
211            assert_ne!(n, 0);
212
213            if rand() % 2 == 1 {
214                let () = writer.flush().unwrap();
215            }
216        }
217
218        assert_eq!(to_write, &[] as &[u8]);
219        assert_eq!(bytes.as_slice(), data);
220    }
221}