use crate::arrays::slice_assume_init_ref;
use crate::{LOGGING_MSG_MAX_LEN, LogId, android_log, uninit_array};
use log::Level;
#[cfg(target_os = "android")]
use log_ffi::LogPriority;
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::{fmt, mem, ptr};
pub struct PlatformLogWriter<'a> {
#[cfg(target_os = "android")]
priority: LogPriority,
#[cfg(not(target_os = "android"))]
priority: Level,
#[cfg(target_os = "android")]
buf_id: Option<log_ffi::log_id_t>,
#[cfg(not(target_os = "android"))]
buf_id: Option<LogId>,
len: usize,
last_newline_index: usize,
tag: &'a CStr,
buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
}
impl PlatformLogWriter<'_> {
#[cfg(target_os = "android")]
pub fn new_with_priority(
buf_id: Option<LogId>,
priority: log_ffi::LogPriority,
tag: &CStr,
) -> PlatformLogWriter<'_> {
#[allow(deprecated)] PlatformLogWriter {
priority,
buf_id: LogId::to_native(buf_id),
len: 0,
last_newline_index: 0,
tag,
buffer: uninit_array(),
}
}
#[cfg(target_os = "android")]
pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
PlatformLogWriter::new_with_priority(
buf_id,
match level {
Level::Warn => LogPriority::WARN,
Level::Info => LogPriority::INFO,
Level::Debug => LogPriority::DEBUG,
Level::Error => LogPriority::ERROR,
Level::Trace => LogPriority::VERBOSE,
},
tag,
)
}
#[cfg(not(target_os = "android"))]
pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
#[allow(deprecated)] PlatformLogWriter {
priority: level,
buf_id,
len: 0,
last_newline_index: 0,
tag,
buffer: uninit_array(),
}
}
fn temporal_flush(&mut self) {
let total_len = self.len;
if total_len == 0 {
return;
}
if self.last_newline_index > 0 {
let copy_from_index = self.last_newline_index;
let remaining_chunk_len = total_len - copy_from_index;
unsafe { self.output_specified_len(copy_from_index) };
self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
self.len = remaining_chunk_len;
} else {
unsafe { self.output_specified_len(total_len) };
self.len = 0;
}
self.last_newline_index = 0;
}
pub fn flush(&mut self) {
let total_len = self.len;
if total_len == 0 {
return;
}
unsafe { self.output_specified_len(total_len) };
self.len = 0;
self.last_newline_index = 0;
}
unsafe fn output_specified_len(&mut self, len: usize) {
let mut last_byte = MaybeUninit::new(b'\0');
mem::swap(
&mut last_byte,
self.buffer.get_mut(len).expect("`len` is out of bounds"),
);
let initialized = unsafe { slice_assume_init_ref(&self.buffer[..len + 1]) };
let msg = CStr::from_bytes_with_nul(initialized)
.expect("Unreachable: nul terminator was placed at `len`");
android_log(self.buf_id, self.priority, self.tag, msg);
unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
}
fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
let dst = self.buffer.as_mut_ptr();
let src = unsafe { self.buffer.as_ptr().add(index) };
unsafe { ptr::copy(src, dst, len) };
}
}
impl fmt::Write for PlatformLogWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut incoming_bytes = s.as_bytes();
while !incoming_bytes.is_empty() {
let len = self.len;
let new_len = len + incoming_bytes.len();
let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
.iter_mut()
.zip(incoming_bytes)
.enumerate()
.fold(None, |acc, (i, (output, input))| {
if *input == b'\0' {
output.write(b' ');
} else {
output.write(*input);
}
if *input == b'\n' { Some(i) } else { acc }
});
if let Some(newline) = last_newline {
self.last_newline_index = len + newline;
}
let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
self.len = new_len;
new_len - len } else {
self.len = LOGGING_MSG_MAX_LEN;
self.temporal_flush();
LOGGING_MSG_MAX_LEN - len };
incoming_bytes = &incoming_bytes[written_len..];
}
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use crate::arrays::slice_assume_init_ref;
use crate::platform_log_writer::PlatformLogWriter;
use log::Level;
use std::ffi::CStr;
use std::fmt::Write;
#[test]
fn platform_log_writer_init_values() {
let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
let writer = PlatformLogWriter::new(None, Level::Warn, tag);
assert_eq!(writer.tag, tag);
#[cfg(not(target_os = "android"))]
assert_eq!(writer.priority, Level::Warn);
}
#[test]
fn temporal_flush() {
let mut writer = get_tag_writer();
writer
.write_str("12\n\n567\n90")
.expect("Unable to write to PlatformLogWriter");
assert_eq!(writer.len, 10);
writer.temporal_flush();
assert_eq!(writer.len, 3);
assert_eq!(writer.last_newline_index, 0);
assert_eq!(
unsafe { slice_assume_init_ref(&writer.buffer[..writer.len]) },
"\n90".as_bytes()
);
writer.temporal_flush();
assert_eq!(writer.len, 0);
assert_eq!(writer.last_newline_index, 0);
}
#[test]
fn flush() {
let mut writer = get_tag_writer();
writer
.write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
.expect("Unable to write to PlatformLogWriter");
writer.flush();
assert_eq!(writer.last_newline_index, 0);
assert_eq!(writer.len, 0);
}
#[test]
fn last_newline_index() {
let mut writer = get_tag_writer();
writer
.write_str("12\n\n567\n90")
.expect("Unable to write to PlatformLogWriter");
assert_eq!(writer.last_newline_index, 7);
}
#[test]
fn output_specified_len_leaves_buffer_unchanged() {
let mut writer = get_tag_writer();
let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
writer
.write_str(log_string)
.expect("Unable to write to PlatformLogWriter");
unsafe { writer.output_specified_len(5) };
assert_eq!(
unsafe { slice_assume_init_ref(&writer.buffer[..log_string.len()]) },
log_string.as_bytes()
);
}
#[test]
fn copy_bytes_to_start() {
let mut writer = get_tag_writer();
writer
.write_str("0123456789")
.expect("Unable to write to PlatformLogWriter");
writer.copy_bytes_to_start(3, 2);
assert_eq!(
unsafe { slice_assume_init_ref(&writer.buffer[..10]) },
"3423456789".as_bytes()
);
}
#[test]
fn copy_bytes_to_start_nop() {
let test_string = "Test_string_with\n\n\n\nnewlines\n";
let mut writer = get_tag_writer();
writer
.write_str(test_string)
.expect("Unable to write to PlatformLogWriter");
writer.copy_bytes_to_start(0, 20);
writer.copy_bytes_to_start(10, 0);
assert_eq!(
unsafe { slice_assume_init_ref(&writer.buffer[..test_string.len()]) },
test_string.as_bytes()
);
}
#[test]
fn writer_substitutes_nullbytes_with_spaces() {
let test_string = "Test_string_with\0\0\0\0nullbytes\0";
let mut writer = get_tag_writer();
writer
.write_str(test_string)
.expect("Unable to write to PlatformLogWriter");
assert_eq!(
unsafe { slice_assume_init_ref(&writer.buffer[..test_string.len()]) },
test_string.replace("\0", " ").as_bytes()
);
}
fn get_tag_writer() -> PlatformLogWriter<'static> {
PlatformLogWriter::new(
None,
Level::Warn,
CStr::from_bytes_with_nul(b"tag\0").unwrap(),
)
}
}