1use std::{
63 borrow::Cow,
64 io::{self, IoSlice, Write},
65 ops::DerefMut,
66 os::unix::net::UnixDatagram,
67 str,
68 sync::Mutex,
69 time::SystemTime,
70};
71
72use tracing::{Level, Metadata};
73use tracing_subscriber::fmt::MakeWriter;
74
75fn truncate_floor(s: &str, n: usize) -> &str {
79 let bound = if n >= s.len() {
82 s.len()
83 } else {
84 let lower_bound = n.saturating_sub(3);
85 let new_index = (lower_bound..=n).rfind(|i| s.is_char_boundary(*i));
86
87 unsafe { new_index.unwrap_unchecked() }
89 };
90
91 &s[..bound]
92}
93
94enum MaybeUtf8Buf<'a> {
95 Bytes(&'a [u8]),
96 String(&'a str),
97}
98
99impl<'a> MaybeUtf8Buf<'a> {
100 fn new(data: &'a [u8]) -> Self {
101 match str::from_utf8(data) {
102 Ok(s) => Self::String(s),
103 Err(_) => Self::Bytes(data),
104 }
105 }
106
107 fn split_floor(&self, limit: usize) -> (Self, Self) {
108 match self {
109 Self::Bytes(b) => {
110 let chunk = &b[..limit.min(b.len())];
111 let remain = &b[chunk.len()..];
112
113 (Self::Bytes(chunk), Self::Bytes(remain))
114 }
115 Self::String(s) => {
116 let chunk = truncate_floor(s, limit);
117 let remain = &s[chunk.len()..];
118
119 (Self::String(chunk), Self::String(remain))
120 }
121 }
122 }
123
124 fn as_slice(&self) -> &'a [u8] {
125 match self {
126 Self::Bytes(b) => b,
127 Self::String(s) => s.as_bytes(),
128 }
129 }
130}
131
132struct Chunker<'a> {
136 data: MaybeUtf8Buf<'a>,
137 limit: usize,
138}
139
140impl<'a> Chunker<'a> {
141 fn new(data: &'a [u8], limit: usize) -> Self {
142 assert!(
143 limit >= 4,
144 "Limit cannot be smaller than largest UTF-8 code unit"
145 );
146
147 Self {
148 data: MaybeUtf8Buf::new(data),
149 limit,
150 }
151 }
152}
153
154impl<'a> Iterator for Chunker<'a> {
155 type Item = &'a [u8];
156
157 fn next(&mut self) -> Option<Self::Item> {
158 let (chunk, remain) = self.data.split_floor(self.limit);
159 let chunk = chunk.as_slice();
160
161 if chunk.is_empty() {
162 None
163 } else {
164 self.data = remain;
165
166 Some(chunk)
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
173pub enum LogcatTag {
174 Fixed(String),
176 Target,
178}
179
180#[derive(Debug)]
182pub struct LogcatWriter<'a> {
183 socket: &'a Mutex<UnixDatagram>,
184 tag: Cow<'a, str>,
185 level: Level,
186}
187
188impl Write for LogcatWriter<'_> {
189 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
190 let mut header = [0u8; 11];
192
193 let thread_id = rustix::thread::gettid().as_raw_nonzero();
194 header[1..3].copy_from_slice(&(thread_id.get() as u16).to_le_bytes());
195
196 let timestamp = SystemTime::now()
197 .duration_since(SystemTime::UNIX_EPOCH)
198 .unwrap_or_default();
199 header[3..7].copy_from_slice(&(timestamp.as_secs() as u32).to_le_bytes());
200 header[7..11].copy_from_slice(×tamp.subsec_nanos().to_le_bytes());
201
202 let priority = match self.level {
203 Level::TRACE => [2u8], Level::DEBUG => [3u8], Level::INFO => [4u8], Level::WARN => [5u8], Level::ERROR => [6u8], };
209
210 let tag = truncate_floor(&self.tag, 128);
213
214 let mut iovecs = [
216 IoSlice::new(&header),
217 IoSlice::new(&priority),
218 IoSlice::new(tag.as_bytes()),
219 IoSlice::new(&[0]),
220 IoSlice::new(&[]),
221 IoSlice::new(&[0]),
222 ];
223 let message_index = 4;
224
225 let max_message_len = 4068 - iovecs[1..].iter().map(|v| v.len()).sum::<usize>();
227
228 let mut socket = self.socket.lock().unwrap();
230
231 let no_newline = buf.strip_suffix(b"\n").unwrap_or(buf);
233
234 for chunk in Chunker::new(no_newline, max_message_len) {
236 iovecs[message_index] = IoSlice::new(chunk);
237
238 let n = rustix::io::writev(socket.deref_mut(), &iovecs)?;
240 if n != iovecs.iter().map(|v| v.len()).sum() {
241 return Err(io::Error::new(
242 io::ErrorKind::UnexpectedEof,
243 "logcat datagram was truncated",
244 ));
245 }
246 }
247
248 Ok(buf.len())
249 }
250
251 fn flush(&mut self) -> io::Result<()> {
252 Ok(())
253 }
254}
255
256#[derive(Debug)]
259pub struct LogcatMakeWriter {
260 tag: LogcatTag,
261 socket: Mutex<UnixDatagram>,
262}
263
264impl LogcatMakeWriter {
265 pub fn new(tag: LogcatTag) -> io::Result<Self> {
267 let socket = UnixDatagram::unbound()?;
268 socket.connect("/dev/socket/logdw")?;
269
270 Ok(Self {
271 tag,
272 socket: Mutex::new(socket),
273 })
274 }
275
276 fn get_tag(&self, meta: Option<&Metadata>) -> Cow<str> {
277 match &self.tag {
278 LogcatTag::Fixed(s) => Cow::Borrowed(s),
279 LogcatTag::Target => match meta {
280 Some(m) => Cow::Owned(m.target().to_owned()),
281 None => Cow::Borrowed(""),
282 },
283 }
284 }
285}
286
287impl<'a> MakeWriter<'a> for LogcatMakeWriter {
288 type Writer = LogcatWriter<'a>;
289
290 fn make_writer(&'a self) -> Self::Writer {
291 LogcatWriter {
292 socket: &self.socket,
293 tag: self.get_tag(None),
294 level: Level::INFO,
295 }
296 }
297
298 fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
299 LogcatWriter {
300 socket: &self.socket,
301 tag: self.get_tag(Some(meta)),
302 level: *meta.level(),
303 }
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[test]
312 fn chunker() {
313 let mut chunker = Chunker::new(b"", 4);
314 assert_eq!(chunker.next(), None);
315
316 chunker = Chunker::new(b"abcd", 4);
317 assert_eq!(chunker.next(), Some(&b"abcd"[..]));
318 assert_eq!(chunker.next(), None);
319
320 chunker = Chunker::new(b"foobar", 4);
321 assert_eq!(chunker.next(), Some(&b"foob"[..]));
322 assert_eq!(chunker.next(), Some(&b"ar"[..]));
323 assert_eq!(chunker.next(), None);
324
325 for limit in [4, 5] {
326 chunker = Chunker::new("你好".as_bytes(), limit);
327 assert_eq!(chunker.next(), Some("你".as_bytes()));
328 assert_eq!(chunker.next(), Some("好".as_bytes()));
329 assert_eq!(chunker.next(), None);
330 }
331
332 chunker = Chunker::new(b"\xffNon-UTF8 \xe4\xbd\xa0\xe5\xa5\xbd", 4);
333 assert_eq!(chunker.next(), Some(&b"\xffNon"[..]));
334 assert_eq!(chunker.next(), Some(&b"-UTF"[..]));
335 assert_eq!(chunker.next(), Some(&b"8 \xe4\xbd"[..]));
336 assert_eq!(chunker.next(), Some(&b"\xa0\xe5\xa5\xbd"[..]));
337 assert_eq!(chunker.next(), None);
338 }
339}