use std::{
borrow::Cow,
io::{self, IoSlice, Write},
ops::DerefMut,
os::unix::net::UnixDatagram,
str,
sync::Mutex,
time::SystemTime,
};
use tracing::{Level, Metadata};
use tracing_subscriber::fmt::MakeWriter;
fn truncate_floor(s: &str, n: usize) -> &str {
let bound = if n >= s.len() {
s.len()
} else {
let lower_bound = n.saturating_sub(3);
let new_index = (lower_bound..=n).rfind(|i| s.is_char_boundary(*i));
unsafe { new_index.unwrap_unchecked() }
};
&s[..bound]
}
enum MaybeUtf8Buf<'a> {
Bytes(&'a [u8]),
String(&'a str),
}
impl<'a> MaybeUtf8Buf<'a> {
fn new(data: &'a [u8]) -> Self {
match str::from_utf8(data) {
Ok(s) => Self::String(s),
Err(_) => Self::Bytes(data),
}
}
fn split_floor(&self, limit: usize) -> (Self, Self) {
match self {
Self::Bytes(b) => {
let chunk = &b[..limit.min(b.len())];
let remain = &b[chunk.len()..];
(Self::Bytes(chunk), Self::Bytes(remain))
}
Self::String(s) => {
let chunk = truncate_floor(s, limit);
let remain = &s[chunk.len()..];
(Self::String(chunk), Self::String(remain))
}
}
}
fn as_slice(&self) -> &'a [u8] {
match self {
Self::Bytes(b) => b,
Self::String(s) => s.as_bytes(),
}
}
}
struct Chunker<'a> {
data: MaybeUtf8Buf<'a>,
limit: usize,
}
impl<'a> Chunker<'a> {
fn new(data: &'a [u8], limit: usize) -> Self {
assert!(
limit >= 4,
"Limit cannot be smaller than largest UTF-8 code unit"
);
Self {
data: MaybeUtf8Buf::new(data),
limit,
}
}
}
impl<'a> Iterator for Chunker<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
let (chunk, remain) = self.data.split_floor(self.limit);
let chunk = chunk.as_slice();
if chunk.is_empty() {
None
} else {
self.data = remain;
Some(chunk)
}
}
}
#[derive(Debug, Clone)]
pub enum LogcatTag {
Fixed(String),
Target,
}
#[derive(Debug)]
pub struct LogcatWriter<'a> {
socket: &'a Mutex<UnixDatagram>,
tag: Cow<'a, str>,
level: Level,
}
impl Write for LogcatWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut header = [0u8; 11];
let thread_id = rustix::thread::gettid().as_raw_nonzero();
header[1..3].copy_from_slice(&(thread_id.get() as u16).to_le_bytes());
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default();
header[3..7].copy_from_slice(&(timestamp.as_secs() as u32).to_le_bytes());
header[7..11].copy_from_slice(×tamp.subsec_nanos().to_le_bytes());
let priority = match self.level {
Level::TRACE => [2u8], Level::DEBUG => [3u8], Level::INFO => [4u8], Level::WARN => [5u8], Level::ERROR => [6u8], };
let tag = truncate_floor(&self.tag, 128);
let mut iovecs = [
IoSlice::new(&header),
IoSlice::new(&priority),
IoSlice::new(tag.as_bytes()),
IoSlice::new(&[0]),
IoSlice::new(&[]),
IoSlice::new(&[0]),
];
let message_index = 4;
let max_message_len = 4068 - iovecs[1..].iter().map(|v| v.len()).sum::<usize>();
let mut socket = self.socket.lock().unwrap();
let no_newline = buf.strip_suffix(b"\n").unwrap_or(buf);
for chunk in Chunker::new(no_newline, max_message_len) {
iovecs[message_index] = IoSlice::new(chunk);
let n = rustix::io::writev(socket.deref_mut(), &iovecs)?;
if n != iovecs.iter().map(|v| v.len()).sum() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"logcat datagram was truncated",
));
}
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct LogcatMakeWriter {
tag: LogcatTag,
socket: Mutex<UnixDatagram>,
}
impl LogcatMakeWriter {
pub fn new(tag: LogcatTag) -> io::Result<Self> {
let socket = UnixDatagram::unbound()?;
socket.connect("/dev/socket/logdw")?;
Ok(Self {
tag,
socket: Mutex::new(socket),
})
}
fn get_tag(&self, meta: Option<&Metadata>) -> Cow<str> {
match &self.tag {
LogcatTag::Fixed(s) => Cow::Borrowed(s),
LogcatTag::Target => match meta {
Some(m) => Cow::Owned(m.target().to_owned()),
None => Cow::Borrowed(""),
},
}
}
}
impl<'a> MakeWriter<'a> for LogcatMakeWriter {
type Writer = LogcatWriter<'a>;
fn make_writer(&'a self) -> Self::Writer {
LogcatWriter {
socket: &self.socket,
tag: self.get_tag(None),
level: Level::INFO,
}
}
fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
LogcatWriter {
socket: &self.socket,
tag: self.get_tag(Some(meta)),
level: *meta.level(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunker() {
let mut chunker = Chunker::new(b"", 4);
assert_eq!(chunker.next(), None);
chunker = Chunker::new(b"abcd", 4);
assert_eq!(chunker.next(), Some(&b"abcd"[..]));
assert_eq!(chunker.next(), None);
chunker = Chunker::new(b"foobar", 4);
assert_eq!(chunker.next(), Some(&b"foob"[..]));
assert_eq!(chunker.next(), Some(&b"ar"[..]));
assert_eq!(chunker.next(), None);
for limit in [4, 5] {
chunker = Chunker::new("你好".as_bytes(), limit);
assert_eq!(chunker.next(), Some("你".as_bytes()));
assert_eq!(chunker.next(), Some("好".as_bytes()));
assert_eq!(chunker.next(), None);
}
chunker = Chunker::new(b"\xffNon-UTF8 \xe4\xbd\xa0\xe5\xa5\xbd", 4);
assert_eq!(chunker.next(), Some(&b"\xffNon"[..]));
assert_eq!(chunker.next(), Some(&b"-UTF"[..]));
assert_eq!(chunker.next(), Some(&b"8 \xe4\xbd"[..]));
assert_eq!(chunker.next(), Some(&b"\xa0\xe5\xa5\xbd"[..]));
assert_eq!(chunker.next(), None);
}
}