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#[repr(transparent)]
22#[derive(Copy, Clone, Debug, PartialEq)]
23pub struct blaze_trace_lvl(u8);
24
25impl blaze_trace_lvl {
26 pub const TRACE: blaze_trace_lvl = blaze_trace_lvl(0);
30 pub const DEBUG: blaze_trace_lvl = blaze_trace_lvl(1);
34 pub const INFO: blaze_trace_lvl = blaze_trace_lvl(2);
39 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
57pub type blaze_trace_cb = extern "C" fn(*const c_char);
59
60
61struct LineWriter<F> {
62 buf: Vec<u8>,
64 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 let () = self.buf.push(b'\0');
93 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 Ok(())
106 }
107}
108
109
110#[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]
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 #[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 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}