androidy_log/
lib.rs

1//! Minimal wrapper over Android logging facilities.
2//!
3//! ## Features:
4//!
5//! - `std` - Enables `std::io::Write` implementation.
6//!
7//! ## Usage
8//!
9//! ```rust,no_run
10//! use androidy_log::{LogPriority, Writer};
11//!
12//! use core::fmt::Write;
13//!
14//! let mut writer = Writer::new("MyTag", LogPriority::INFO);
15//! let _ = write!(writer, "Hellow World!");
16//! drop(writer); //or writer.flush();
17//!
18//! androidy_log::println!("Hello via macro!");
19//! androidy_log::eprintln!("Error via macro!");
20//! ```
21//!
22
23#![cfg_attr(not(test), no_std)]
24#![warn(missing_docs)]
25
26#[cfg(feature = "std")]
27extern crate std;
28
29use core::{cmp, mem, ptr, fmt};
30
31///Priority of the log message.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[repr(i32)]
34pub enum LogPriority {
35    ///For internal use only.
36    UNKNOWN = 0,
37    ///The default priority, for internal use only.
38    DEFAULT = 1,
39    ///Verbose logging.
40    VERBOSE = 2,
41    ///Debug logging.
42    DEBUG = 3,
43    ///Informational logging.
44    INFO = 4,
45    ///Warning logging.
46    ///
47    ///For use with recoverable failures.
48    WARN = 5,
49    ///Error logging.
50    ///
51    ///For use with unrecoverable failures.
52    ERROR = 6,
53    ///Fatal logging.
54    ///
55    ///For use when aborting.
56    FATAL = 7,
57    ///For internal use only.
58    SILENT = 8,
59}
60
61const TAG_MAX_LEN: usize = 23;
62//Re-check NDK sources, I think internally kernel limits to 4076, but
63//it includes some overhead of logcat machinery, hence 4000
64//Don't remember details
65const BUFFER_CAPACITY: usize = 4000;
66const DEFAULT_TAG: &str = "Rust";
67
68#[cfg(not(test))]
69#[link(name = "log")]
70extern "C" {
71    fn __android_log_write(prio: i32, tag: *const i8, text: *const i8) -> i32;
72}
73
74#[cfg(test)]
75fn __android_log_write(_: i32, _: *const i8, _: *const i8) -> i32 {
76    0
77}
78
79///Android log writer.
80///
81///By default every write is buffer unless buffer overflow happens.
82///Buffered input is flushed on `Drop` or via manual call.
83pub struct Writer {
84    //Null character is not within limit
85    tag: mem::MaybeUninit<[u8; TAG_MAX_LEN + 1]>,
86    prio: LogPriority,
87    //Null character is not within limit
88    buffer: mem::MaybeUninit<[u8; BUFFER_CAPACITY + 1]>,
89    len: usize,
90}
91
92impl Writer {
93    #[inline(always)]
94    ///Creates new instance using default tag `Rust`
95    ///
96    ///- `prio` - Logging priority.
97    pub const fn new_default(prio: LogPriority) -> Self {
98        let mut tag = [0u8; TAG_MAX_LEN + 1];
99
100        tag[0] = DEFAULT_TAG.as_bytes()[0];
101        tag[1] = DEFAULT_TAG.as_bytes()[1];
102        tag[2] = DEFAULT_TAG.as_bytes()[2];
103        tag[3] = DEFAULT_TAG.as_bytes()[3];
104        unsafe {
105            Self::from_raw_parts(mem::MaybeUninit::new(tag), prio)
106        }
107    }
108
109    #[inline]
110    ///Creates new instance using:
111    ///
112    ///- `tag` - Log message tag, truncated to first 23 characters.
113    ///- `prio` - Logging priority
114    pub fn new(tag: &str, prio: LogPriority) -> Self {
115        let mut tag_buffer = mem::MaybeUninit::<[u8; TAG_MAX_LEN + 1]>::zeroed();
116        unsafe {
117            ptr::copy_nonoverlapping(tag.as_ptr(), tag_buffer.as_mut_ptr() as *mut u8, cmp::min(tag.len(), TAG_MAX_LEN));
118            Self::from_raw_parts(tag_buffer, prio)
119        }
120    }
121
122    #[inline]
123    ///Creates new instance with:
124    ///
125    ///- `tag` - Log message's tag as raw C string, that must be ending with 0. It is UB to pass anything else.
126    ///- `prio` - Logging priority
127    pub const unsafe fn from_raw_parts(tag: mem::MaybeUninit<[u8; TAG_MAX_LEN + 1]>, prio: LogPriority) -> Self {
128        Self {
129            tag,
130            prio,
131            buffer: mem::MaybeUninit::uninit(),
132            len: 0,
133        }
134    }
135
136    #[inline(always)]
137    ///Returns content of written buffer.
138    pub fn buffer(&self) -> &[u8] {
139        unsafe {
140            core::slice::from_raw_parts(self.buffer.as_ptr() as *const u8, self.len)
141        }
142    }
143
144    #[inline(always)]
145    fn as_mut_ptr(&mut self) -> *mut u8 {
146        self.buffer.as_mut_ptr() as _
147    }
148
149    #[inline(always)]
150    ///Flushes internal buffer, if any data is available.
151    ///
152    ///Namely it dumps stored data in buffer via `__android_log_write`.
153    ///And resets buffered length to 0.
154    pub fn flush(&mut self) {
155        if self.len > 0 {
156            self.inner_flush();
157        }
158    }
159
160    fn inner_flush(&mut self) {
161        unsafe {
162            (self.buffer.as_mut_ptr() as *mut u8).add(self.len).write(0);
163            __android_log_write(self.prio as _, self.tag.as_ptr() as _, self.buffer.as_ptr() as *const _);
164        }
165        self.len = 0;
166    }
167
168    #[inline]
169    fn copy_data<'a>(&mut self, text: &'a [u8]) -> &'a [u8] {
170        let mut write_len = cmp::min(BUFFER_CAPACITY.saturating_sub(self.len), text.len());
171
172        #[inline(always)]
173        fn is_char_boundary(text: &[u8], idx: usize) -> bool {
174            if idx == 0 {
175                return true;
176            }
177
178            match text.get(idx) {
179                None => idx == text.len(),
180                Some(&byte) => (byte as i8) >= -0x40
181            }
182        }
183
184        #[inline(never)]
185        #[cold]
186        fn shift_by_char_boundary(text: &[u8], mut size: usize) -> usize {
187            while !is_char_boundary(text, size) {
188                size -= 1;
189            }
190            size
191        }
192
193        if !is_char_boundary(text, write_len) {
194            //0 is always char boundary so 0 - 1 is impossible
195            write_len = shift_by_char_boundary(text, write_len - 1);
196        }
197
198        unsafe {
199            ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().add(self.len), write_len);
200        }
201        self.len += write_len;
202        &text[write_len..]
203    }
204
205    ///Writes supplied text to the buffer.
206    ///
207    ///On buffer overflow, data is logged via `__android_log_write`
208    ///and buffer is filled with the rest of `data`
209    pub fn write_data(&mut self, mut data: &[u8]) {
210        loop {
211            data = self.copy_data(data);
212
213            if data.is_empty() {
214                break;
215            } else {
216                self.flush();
217            }
218        }
219    }
220}
221
222impl fmt::Write for Writer {
223    #[inline]
224    fn write_str(&mut self, text: &str) -> fmt::Result {
225        self.write_data(text.as_bytes());
226
227        Ok(())
228    }
229}
230
231#[cfg(feature = "std")]
232impl std::io::Write for Writer {
233    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
234        self.write_data(buf);
235        Ok(buf.len())
236    }
237
238    #[inline(always)]
239    fn flush(&mut self) -> std::io::Result<()> {
240        self.flush();
241        Ok(())
242    }
243}
244
245impl Drop for Writer {
246    #[inline]
247    fn drop(&mut self) {
248        self.flush();
249    }
250}
251
252#[macro_export]
253///`println` alternative to write message with INFO priority.
254macro_rules! println {
255    () => {{
256        $crate::println!(" ");
257    }};
258    ($($arg:tt)*) => {{
259        use core::fmt::Write;
260        let mut writer = $crate::Writer::new_default($crate::LogPriority::INFO);
261        let _ = write!(writer, $($arg)*);
262        drop(writer);
263    }}
264}
265
266#[macro_export]
267///`eprintln` alternative to write message with ERROR priority.
268macro_rules! eprintln {
269    () => {{
270        $crate::println!(" ");
271    }};
272    ($($arg:tt)*) => {{
273        use core::fmt::Write;
274        let mut writer = $crate::Writer::new_default($crate::LogPriority::ERROR);
275        let _ = write!(writer, $($arg)*);
276        drop(writer);
277    }}
278}
279
280#[cfg(test)]
281mod tests {
282    use super::{LogPriority, Writer, TAG_MAX_LEN, DEFAULT_TAG};
283    const TAG: &str = "Test";
284    const TAG_OVERFLOW: &str = "123456789123456789123456789";
285
286    #[test]
287    fn should_truncate_tag() {
288        let writer = Writer::new(TAG_OVERFLOW, LogPriority::WARN);
289        assert!(TAG_OVERFLOW.len() > TAG_MAX_LEN);
290        let tag = unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, TAG_MAX_LEN) };
291        assert_eq!(tag, TAG_OVERFLOW[..TAG_MAX_LEN].as_bytes());
292    }
293
294    #[test]
295    fn should_normal_write() {
296        let mut writer = Writer::new_default(LogPriority::WARN);
297
298        let tag = unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, DEFAULT_TAG.len()) };
299        assert_eq!(tag, DEFAULT_TAG.as_bytes());
300        assert_eq!(writer.prio, LogPriority::WARN);
301
302        let data = TAG_OVERFLOW.as_bytes();
303
304        writer.write_data(data);
305        assert_eq!(writer.len, data.len());
306        assert_eq!(writer.buffer(), data);
307
308        writer.write_data(b" ");
309        writer.write_data(data);
310        let expected = format!("{} {}", TAG_OVERFLOW, TAG_OVERFLOW);
311        assert_eq!(writer.len, expected.len());
312        assert_eq!(writer.buffer(), expected.as_bytes());
313    }
314
315    #[test]
316    fn should_handle_write_overflow() {
317        let mut writer = Writer::new(TAG, LogPriority::WARN);
318        let data = TAG_OVERFLOW.as_bytes();
319        assert_eq!(unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, TAG.len() + 1) }, &b"Test\0"[..]);
320
321        //BUFFER_CAPACITY / TAG_OVERFLOW.len() = 148.xxx
322        for idx in 1..=148 {
323            writer.write_data(data);
324            assert_eq!(writer.len, data.len() * idx);
325        }
326
327        writer.write_data(data);
328        assert_eq!(writer.len, 23);
329    }
330
331    #[test]
332    fn should_handle_write_overflow_outside_of_char_boundary() {
333        let mut writer = Writer::new(TAG, LogPriority::WARN);
334        let data = b"1234567891";
335
336        for idx in 1..400 {
337            writer.write_data(data);
338            assert_eq!(writer.len, data.len() * idx);
339        }
340
341        assert_eq!(3990, writer.len);
342
343        writer.write_data(b"12345678");
344        assert_eq!(3998, writer.len);
345
346        let unicode = "ロリ";
347        writer.write_data(unicode.as_bytes());
348        assert_eq!(writer.len, unicode.len());
349        assert_eq!(writer.buffer(), unicode.as_bytes());
350    }
351
352}