#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]
#[cfg(feature = "std")]
extern crate std;
use core::{cmp, mem, ptr, fmt};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum LogPriority {
UNKNOWN = 0,
DEFAULT = 1,
VERBOSE = 2,
DEBUG = 3,
INFO = 4,
WARN = 5,
ERROR = 6,
FATAL = 7,
SILENT = 8,
}
const TAG_MAX_LEN: usize = 23;
const BUFFER_CAPACITY: usize = 4000;
const DEFAULT_TAG: &str = "Rust";
#[cfg(not(test))]
#[link(name = "log")]
extern "C" {
fn __android_log_write(prio: i32, tag: *const i8, text: *const i8) -> i32;
}
#[cfg(test)]
fn __android_log_write(_: i32, _: *const i8, _: *const i8) -> i32 {
0
}
pub struct Writer {
tag: mem::MaybeUninit<[u8; TAG_MAX_LEN + 1]>,
prio: LogPriority,
buffer: mem::MaybeUninit<[u8; BUFFER_CAPACITY + 1]>,
len: usize,
}
impl Writer {
#[inline(always)]
pub const fn new_default(prio: LogPriority) -> Self {
let mut tag = [0u8; TAG_MAX_LEN + 1];
tag[0] = DEFAULT_TAG.as_bytes()[0];
tag[1] = DEFAULT_TAG.as_bytes()[1];
tag[2] = DEFAULT_TAG.as_bytes()[2];
tag[3] = DEFAULT_TAG.as_bytes()[3];
unsafe {
Self::from_raw_parts(mem::MaybeUninit::new(tag), prio)
}
}
#[inline]
pub fn new(tag: &str, prio: LogPriority) -> Self {
let mut tag_buffer = mem::MaybeUninit::<[u8; TAG_MAX_LEN + 1]>::zeroed();
unsafe {
ptr::copy_nonoverlapping(tag.as_ptr(), tag_buffer.as_mut_ptr() as *mut u8, cmp::min(tag.len(), TAG_MAX_LEN));
Self::from_raw_parts(tag_buffer, prio)
}
}
#[inline]
pub const unsafe fn from_raw_parts(tag: mem::MaybeUninit<[u8; TAG_MAX_LEN + 1]>, prio: LogPriority) -> Self {
Self {
tag,
prio,
buffer: mem::MaybeUninit::uninit(),
len: 0,
}
}
#[inline(always)]
pub fn buffer(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(self.buffer.as_ptr() as *const u8, self.len)
}
}
#[inline(always)]
fn as_mut_ptr(&mut self) -> *mut u8 {
self.buffer.as_mut_ptr() as _
}
#[inline(always)]
pub fn flush(&mut self) {
if self.len > 0 {
self.inner_flush();
}
}
fn inner_flush(&mut self) {
unsafe {
(self.buffer.as_mut_ptr() as *mut u8).add(self.len).write(0);
__android_log_write(self.prio as _, self.tag.as_ptr() as _, self.buffer.as_ptr() as *const _);
}
self.len = 0;
}
#[inline]
fn copy_data<'a>(&mut self, text: &'a [u8]) -> &'a [u8] {
let mut write_len = cmp::min(BUFFER_CAPACITY.saturating_sub(self.len), text.len());
#[inline(always)]
fn is_char_boundary(text: &[u8], idx: usize) -> bool {
if idx == 0 {
return true;
}
match text.get(idx) {
None => idx == text.len(),
Some(&byte) => (byte as i8) >= -0x40
}
}
#[inline(never)]
#[cold]
fn shift_by_char_boundary(text: &[u8], mut size: usize) -> usize {
while !is_char_boundary(text, size) {
size -= 1;
}
size
}
if !is_char_boundary(text, write_len) {
write_len = shift_by_char_boundary(text, write_len - 1);
}
unsafe {
ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().add(self.len), write_len);
}
self.len += write_len;
&text[write_len..]
}
pub fn write_data(&mut self, mut data: &[u8]) {
loop {
data = self.copy_data(data);
if data.is_empty() {
break;
} else {
self.flush();
}
}
}
}
impl fmt::Write for Writer {
#[inline]
fn write_str(&mut self, text: &str) -> fmt::Result {
self.write_data(text.as_bytes());
Ok(())
}
}
#[cfg(feature = "std")]
impl std::io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_data(buf);
Ok(buf.len())
}
#[inline(always)]
fn flush(&mut self) -> std::io::Result<()> {
self.flush();
Ok(())
}
}
impl Drop for Writer {
#[inline]
fn drop(&mut self) {
self.flush();
}
}
#[macro_export]
macro_rules! println {
() => {{
$crate::println!(" ");
}};
($($arg:tt)*) => {{
use core::fmt::Write;
let mut writer = $crate::Writer::new_default($crate::LogPriority::INFO);
let _ = write!(writer, $($arg)*);
drop(writer);
}}
}
#[macro_export]
macro_rules! eprintln {
() => {{
$crate::println!(" ");
}};
($($arg:tt)*) => {{
use core::fmt::Write;
let mut writer = $crate::Writer::new_default($crate::LogPriority::ERROR);
let _ = write!(writer, $($arg)*);
drop(writer);
}}
}
#[cfg(test)]
mod tests {
use super::{LogPriority, Writer, TAG_MAX_LEN, DEFAULT_TAG};
const TAG: &str = "Test";
const TAG_OVERFLOW: &str = "123456789123456789123456789";
#[test]
fn should_truncate_tag() {
let writer = Writer::new(TAG_OVERFLOW, LogPriority::WARN);
assert!(TAG_OVERFLOW.len() > TAG_MAX_LEN);
let tag = unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, TAG_MAX_LEN) };
assert_eq!(tag, TAG_OVERFLOW[..TAG_MAX_LEN].as_bytes());
}
#[test]
fn should_normal_write() {
let mut writer = Writer::new_default(LogPriority::WARN);
let tag = unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, DEFAULT_TAG.len()) };
assert_eq!(tag, DEFAULT_TAG.as_bytes());
assert_eq!(writer.prio, LogPriority::WARN);
let data = TAG_OVERFLOW.as_bytes();
writer.write_data(data);
assert_eq!(writer.len, data.len());
assert_eq!(writer.buffer(), data);
writer.write_data(b" ");
writer.write_data(data);
let expected = format!("{} {}", TAG_OVERFLOW, TAG_OVERFLOW);
assert_eq!(writer.len, expected.len());
assert_eq!(writer.buffer(), expected.as_bytes());
}
#[test]
fn should_handle_write_overflow() {
let mut writer = Writer::new(TAG, LogPriority::WARN);
let data = TAG_OVERFLOW.as_bytes();
assert_eq!(unsafe { core::slice::from_raw_parts(writer.tag.as_ptr() as *const u8, TAG.len() + 1) }, &b"Test\0"[..]);
for idx in 1..=148 {
writer.write_data(data);
assert_eq!(writer.len, data.len() * idx);
}
writer.write_data(data);
assert_eq!(writer.len, 23);
}
#[test]
fn should_handle_write_overflow_outside_of_char_boundary() {
let mut writer = Writer::new(TAG, LogPriority::WARN);
let data = b"1234567891";
for idx in 1..400 {
writer.write_data(data);
assert_eq!(writer.len, data.len() * idx);
}
assert_eq!(3990, writer.len);
writer.write_data(b"12345678");
assert_eq!(3998, writer.len);
let unicode = "ロリ";
writer.write_data(unicode.as_bytes());
assert_eq!(writer.len, unicode.len());
assert_eq!(writer.buffer(), unicode.as_bytes());
}
}