paranoid_android/
writer.rs

1use core::slice;
2use std::{
3    ffi::{CStr, CString},
4    io::{self, Write},
5    os::raw::c_char,
6};
7
8use lazy_static::lazy_static;
9use sharded_slab::{pool::RefMut, Pool};
10use smallvec::SmallVec;
11use tracing_core::Metadata;
12use tracing_subscriber::fmt::MakeWriter;
13
14use crate::logging::{Buffer, Priority};
15
16/// The writer produced by [`AndroidLogMakeWriter`].
17#[derive(Debug)]
18pub struct AndroidLogWriter<'a> {
19    tag: &'a CStr,
20    message: PooledCString,
21
22    priority: Priority,
23    buffer: Buffer,
24    location: Option<Location>,
25}
26
27/// A [`MakeWriter`] suitable for writing Android logs.
28#[derive(Debug)]
29pub struct AndroidLogMakeWriter {
30    tag: CString,
31    buffer: Buffer,
32}
33
34#[derive(Debug)]
35struct Location {
36    file: PooledCString,
37    line: u32,
38}
39
40// logd truncates logs at 4096 bytes, so we chunk at 4000 to be conservative
41const MAX_LOG_LEN: usize = 4000;
42
43impl Write for AndroidLogWriter<'_> {
44    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
45        self.message.write(buf);
46        Ok(buf.len())
47    }
48
49    fn flush(&mut self) -> io::Result<()> {
50        let mut sv = SmallVec::<[PooledCString; 4]>::new();
51        let messages = if self.message.as_bytes().len() < MAX_LOG_LEN {
52            MessageIter::Single(Some(&mut self.message))
53        } else {
54            sv.extend(
55                self.message
56                    .as_bytes()
57                    .chunks(MAX_LOG_LEN)
58                    .map(PooledCString::new),
59            );
60            MessageIter::Multi(sv.as_mut().iter_mut())
61        }
62        .filter_map(PooledCString::as_ptr);
63
64        let buffer = self.buffer.as_raw().0 as i32;
65        let priority = self.priority.as_raw().0 as i32;
66        let tag = self.tag.as_ptr();
67
68        #[cfg(feature = "api-30")]
69        {
70            use std::{mem::size_of, ptr::null};
71
72            use ndk_sys::{
73                __android_log_is_loggable, __android_log_message, __android_log_write_log_message,
74            };
75
76            if unsafe { __android_log_is_loggable(priority, tag, priority) } == 0 {
77                return Ok(());
78            }
79
80            let (file, line) = match &mut self.location {
81                Some(Location { file, line }) => match file.as_ptr() {
82                    Some(ptr) => (ptr, *line),
83                    None => (null(), 0),
84                },
85                None => (null(), 0),
86            };
87
88            for message in messages {
89                let mut message = __android_log_message {
90                    struct_size: size_of::<__android_log_message>(),
91                    buffer_id: buffer,
92                    priority,
93                    tag,
94                    file,
95                    line,
96                    message,
97                };
98
99                unsafe { __android_log_write_log_message(&mut message) };
100            }
101        }
102
103        #[cfg(not(feature = "api-30"))]
104        {
105            use ndk_sys::__android_log_buf_write;
106
107            for message in messages {
108                unsafe { __android_log_buf_write(buffer, priority, tag, message) };
109            }
110        }
111
112        Ok(())
113    }
114}
115
116impl Drop for AndroidLogWriter<'_> {
117    fn drop(&mut self) {
118        self.flush().unwrap();
119    }
120}
121
122impl<'a> MakeWriter<'a> for AndroidLogMakeWriter {
123    type Writer = AndroidLogWriter<'a>;
124
125    fn make_writer(&'a self) -> Self::Writer {
126        AndroidLogWriter {
127            tag: self.tag.as_c_str(),
128            message: PooledCString::empty(),
129
130            buffer: self.buffer,
131            priority: Priority::Info,
132            location: None,
133        }
134    }
135
136    fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
137        let priority = (*meta.level()).into();
138
139        let location = match (meta.file(), meta.line()) {
140            (Some(file), Some(line)) => {
141                let file = PooledCString::new(file.as_bytes());
142                Some(Location { file, line })
143            }
144            _ => None,
145        };
146
147        AndroidLogWriter {
148            tag: self.tag.as_c_str(),
149            message: PooledCString::empty(),
150
151            buffer: self.buffer,
152            priority,
153            location,
154        }
155    }
156}
157
158impl AndroidLogMakeWriter {
159    /// Returns a new [`AndroidLogWriter`] with the given tag.
160    pub fn new(tag: String) -> Self {
161        Self::with_buffer(tag, Default::default())
162    }
163
164    /// Returns a new [`AndroidLogMakeWriter`] with the given tag and using the
165    /// given [Android log buffer](Buffer).
166    pub fn with_buffer(tag: String, buffer: Buffer) -> Self {
167        Self {
168            tag: CString::new(tag).unwrap(),
169            buffer,
170        }
171    }
172}
173
174#[derive(Debug)]
175struct PooledCString {
176    buf: RefMut<'static, Vec<u8>>,
177}
178
179enum MessageIter<'a> {
180    Single(Option<&'a mut PooledCString>),
181    Multi(slice::IterMut<'a, PooledCString>),
182}
183
184lazy_static! {
185    static ref BUFFER_POOL: Pool<Vec<u8>> = Pool::new();
186}
187
188impl PooledCString {
189    fn empty() -> Self {
190        Self {
191            buf: BUFFER_POOL.create().unwrap(),
192        }
193    }
194
195    fn new(data: &[u8]) -> Self {
196        let mut this = PooledCString::empty();
197        this.write(data);
198        this
199    }
200
201    fn write(&mut self, data: &[u8]) {
202        self.buf.extend_from_slice(data);
203    }
204
205    fn as_ptr(&mut self) -> Option<*const c_char> {
206        if self.buf.last().copied() != Some(0) {
207            self.buf.push(0);
208        }
209
210        CStr::from_bytes_with_nul(self.buf.as_ref())
211            .ok()
212            .map(CStr::as_ptr)
213    }
214
215    fn as_bytes(&self) -> &[u8] {
216        self.buf.as_ref()
217    }
218}
219
220impl Drop for PooledCString {
221    fn drop(&mut self) {
222        BUFFER_POOL.clear(self.buf.key());
223    }
224}
225
226impl<'a> Iterator for MessageIter<'a> {
227    type Item = &'a mut PooledCString;
228
229    fn next(&mut self) -> Option<Self::Item> {
230        match self {
231            MessageIter::Single(message) => message.take(),
232            MessageIter::Multi(iter) => iter.next(),
233        }
234    }
235
236    fn size_hint(&self) -> (usize, Option<usize>) {
237        match self {
238            MessageIter::Single(Some(_)) => (1, Some(1)),
239            MessageIter::Single(None) => (0, Some(0)),
240            MessageIter::Multi(iter) => iter.size_hint(),
241        }
242    }
243}