use std::ffi::c_char;
use std::ffi::CStr;
use std::io;
use std::io::BufRead as _;
use std::io::Cursor;
use tracing::subscriber::set_global_default as set_global_subscriber;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::fmt::time::SystemTime;
use tracing_subscriber::FmtSubscriber;
use crate::blaze_err;
#[cfg(doc)]
use crate::blaze_err_last;
use crate::set_last_err;
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct blaze_trace_lvl(u8);
impl blaze_trace_lvl {
pub const TRACE: Self = Self(0);
pub const DEBUG: Self = Self(1);
pub const INFO: Self = Self(2);
pub const WARN: Self = Self(3);
}
impl From<blaze_trace_lvl> for LevelFilter {
fn from(other: blaze_trace_lvl) -> Self {
match other {
blaze_trace_lvl::WARN => Self::WARN,
blaze_trace_lvl::INFO => Self::INFO,
blaze_trace_lvl::DEBUG => Self::DEBUG,
blaze_trace_lvl::TRACE => Self::TRACE,
_ => Self::TRACE,
}
}
}
pub type blaze_trace_cb = extern "C" fn(*const c_char);
struct LineWriter<F> {
buf: Vec<u8>,
f: F,
}
impl<F> LineWriter<F> {
fn new(f: F) -> Self {
Self { buf: Vec::new(), f }
}
}
impl<F> io::Write for LineWriter<F>
where
F: FnMut(&CStr),
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let delim = b'\n';
let mut read = 0;
let mut cursor = Cursor::new(buf);
loop {
let n = cursor.read_until(delim, &mut self.buf)?;
if n == 0 {
break Ok(read)
}
read += n;
if self.buf.last() == Some(&delim) {
let () = self.buf.push(b'\0');
let cstr = unsafe { CStr::from_ptr(self.buf.as_ptr().cast()) };
let () = (self.f)(cstr);
let () = self.buf.clear();
} else {
break Ok(read)
}
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[no_mangle]
pub extern "C" fn blaze_trace(lvl: blaze_trace_lvl, cb: blaze_trace_cb) {
let format = fmt::format().with_target(false).compact();
let subscriber = FmtSubscriber::builder()
.event_format(format)
.with_max_level(LevelFilter::from(lvl))
.with_span_events(FmtSpan::FULL)
.with_timer(SystemTime)
.with_writer(move || {
let emit = move |cstr: &CStr| cb(cstr.as_ptr());
LineWriter::new(emit)
})
.finish();
let err = set_global_subscriber(subscriber)
.map(|()| blaze_err::OK)
.unwrap_or(blaze_err::ALREADY_EXISTS);
let () = set_last_err(err);
}
#[cfg(test)]
mod tests {
use super::*;
use std::cmp::max;
use std::hash::BuildHasher as _;
use std::hash::Hasher as _;
use std::hash::RandomState;
use std::io::Write as _;
use blazesym::__private::ReadRaw;
#[test]
fn lvl_conversions() {
assert_eq!(
LevelFilter::from(blaze_trace_lvl::DEBUG),
LevelFilter::DEBUG
);
assert_eq!(LevelFilter::from(blaze_trace_lvl::INFO), LevelFilter::INFO);
assert_eq!(
LevelFilter::from(blaze_trace_lvl::TRACE),
LevelFilter::TRACE
);
assert_eq!(LevelFilter::from(blaze_trace_lvl::WARN), LevelFilter::WARN);
}
#[test]
fn line_writing() {
let data = br"INFO symbolize: new src=Process(self) addrs=AbsAddr([0x0])
INFO symbolize: enter src=Process(self) addrs=AbsAddr([0x0])
INFO symbolize:handle_unknown_addr: new src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
INFO symbolize:handle_unknown_addr: enter src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
INFO symbolize:handle_unknown_addr: exit src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
INFO symbolize:handle_unknown_addr: close src=Process(self) addrs=AbsAddr([0x0]) addr=0x0
INFO symbolize: exit src=Process(self) addrs=AbsAddr([0x0])
INFO symbolize: close src=Process(self) addrs=AbsAddr([0x0])
";
let mut to_write = &data[..];
fn rand() -> u64 {
RandomState::new().build_hasher().finish()
}
let mut bytes = Vec::new();
let mut writer = LineWriter::new(|line: &CStr| {
let data = line.to_bytes();
assert!(data.ends_with(b"\n"), "{line:?}");
assert!(
!data[..data.len().saturating_sub(1)].contains(&b'\n'),
"{line:?}"
);
let () = bytes.extend_from_slice(data);
});
while !to_write.is_empty() {
let cnt = max(rand() % (max(to_write.len() as u64 / 2, 1)), 1) as usize;
let data = to_write.read_slice(cnt).unwrap();
let n = writer.write(data).unwrap();
assert_ne!(n, 0);
if rand() % 2 == 1 {
let () = writer.flush().unwrap();
}
}
assert_eq!(to_write, &[] as &[u8]);
assert_eq!(bytes.as_slice(), data);
}
}