#[cfg(target_os = "android")]
extern crate android_log_sys as log_ffi;
use log::{Level, LevelFilter, Log, Metadata, Record};
#[cfg(target_os = "android")]
use log_ffi::LogPriority;
use std::ffi::{CStr, CString};
use std::fmt;
use std::mem::{self, MaybeUninit};
use std::ptr;
use std::sync::OnceLock;
pub use env_filter::{Builder as FilterBuilder, Filter};
pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LogId {
Main,
Radio,
Events,
System,
Crash,
Kernel,
Security,
Stats,
}
#[cfg(target_os = "android")]
impl LogId {
const fn to_native(log_id: Option<Self>) -> Option<log_ffi::log_id_t> {
match log_id {
Some(Self::Main) => Some(log_ffi::log_id_t::MAIN),
Some(Self::Radio) => Some(log_ffi::log_id_t::RADIO),
Some(Self::Events) => Some(log_ffi::log_id_t::EVENTS),
Some(Self::System) => Some(log_ffi::log_id_t::SYSTEM),
Some(Self::Crash) => Some(log_ffi::log_id_t::CRASH),
Some(Self::Kernel) => Some(log_ffi::log_id_t::KERNEL),
Some(Self::Security) => Some(log_ffi::log_id_t::SECURITY),
Some(Self::Stats) => Some(log_ffi::log_id_t::STATS),
None => None,
}
}
}
#[cfg(target_os = "android")]
fn android_log(
buf_id: Option<log_ffi::log_id_t>,
prio: log_ffi::LogPriority,
tag: &CStr,
msg: &CStr,
) {
if let Some(buf_id) = buf_id {
unsafe {
log_ffi::__android_log_buf_write(
buf_id as log_ffi::c_int,
prio as log_ffi::c_int,
tag.as_ptr() as *const log_ffi::c_char,
msg.as_ptr() as *const log_ffi::c_char,
);
};
} else {
unsafe {
log_ffi::__android_log_write(
prio as log_ffi::c_int,
tag.as_ptr() as *const log_ffi::c_char,
msg.as_ptr() as *const log_ffi::c_char,
);
};
}
}
#[cfg(not(target_os = "android"))]
fn android_log(_buf_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr) {}
pub struct AndroidLogger {
config: OnceLock<Config>,
}
impl AndroidLogger {
pub fn new(config: Config) -> AndroidLogger {
AndroidLogger {
config: OnceLock::from(config),
}
}
fn config(&self) -> &Config {
self.config.get_or_init(Config::default)
}
}
static ANDROID_LOGGER: OnceLock<AndroidLogger> = OnceLock::new();
const LOGGING_TAG_MAX_LEN: usize = 23;
const LOGGING_MSG_MAX_LEN: usize = 4000;
impl Default for AndroidLogger {
fn default() -> AndroidLogger {
AndroidLogger {
config: OnceLock::from(Config::default()),
}
}
}
impl Log for AndroidLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
let config = self.config();
metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
}
fn log(&self, record: &Record) {
let config = self.config();
if !self.enabled(record.metadata()) {
return;
}
if !config.filter_matches(record) {
return;
}
let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
let module_path = record.module_path().unwrap_or_default().to_owned();
let custom_tag = &config.tag;
let tag = custom_tag
.as_ref()
.map(|s| s.as_bytes())
.unwrap_or_else(|| module_path.as_bytes());
self.fill_tag_bytes(&mut tag_bytes, tag);
let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
let _ = match (custom_tag, &config.custom_format) {
(_, Some(format)) => format(&mut writer, record),
(Some(_), _) => fmt::write(
&mut writer,
format_args!("{}: {}", module_path, *record.args()),
),
_ => fmt::write(&mut writer, *record.args()),
};
writer.flush();
}
fn flush(&self) {}
}
impl AndroidLogger {
fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
if tag.len() > LOGGING_TAG_MAX_LEN {
for (input, output) in tag
.iter()
.take(LOGGING_TAG_MAX_LEN - 2)
.chain(b"..\0".iter())
.zip(array.iter_mut())
{
output.write(*input);
}
} else {
for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
output.write(*input);
}
}
}
}
#[derive(Default)]
pub struct Config {
log_level: Option<LevelFilter>,
buf_id: Option<LogId>,
filter: Option<env_filter::Filter>,
tag: Option<CString>,
custom_format: Option<FormatFn>,
}
impl Config {
pub fn with_max_level(mut self, level: LevelFilter) -> Self {
self.log_level = Some(level);
self
}
pub fn with_log_buffer(mut self, buf_id: LogId) -> Self {
self.buf_id = Some(buf_id);
self
}
fn filter_matches(&self, record: &Record) -> bool {
if let Some(ref filter) = self.filter {
filter.matches(record)
} else {
true
}
}
pub fn with_filter(mut self, filter: env_filter::Filter) -> Self {
self.filter = Some(filter);
self
}
pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
self
}
pub fn format<F>(mut self, format: F) -> Self
where
F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
{
self.custom_format = Some(Box::new(format));
self
}
}
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<'a> PlatformLogWriter<'a> {
#[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;
self.output_specified_len(copy_from_index);
self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
self.len = remaining_chunk_len;
} else {
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;
}
self.output_specified_len(total_len);
self.len = 0;
self.last_newline_index = 0;
}
fn output_specified_len(&mut self, len: usize) {
let mut last_byte = MaybeUninit::new(b'\0');
mem::swap(&mut last_byte, unsafe {
self.buffer.get_unchecked_mut(len)
});
let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
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<'a> fmt::Write for PlatformLogWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut incomming_bytes = s.as_bytes();
while !incomming_bytes.is_empty() {
let len = self.len;
let new_len = len + incomming_bytes.len();
let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
.iter_mut()
.zip(incomming_bytes)
.enumerate()
.fold(None, |acc, (i, (output, input))| {
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 };
incomming_bytes = &incomming_bytes[written_len..];
}
Ok(())
}
}
pub fn log(record: &Record) {
ANDROID_LOGGER
.get_or_init(AndroidLogger::default)
.log(record)
}
pub fn init_once(config: Config) {
let log_level = config.log_level;
let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
if let Err(err) = log::set_logger(logger) {
log::debug!("android_logger: log::set_logger failed: {}", err);
} else if let Some(level) = log_level {
log::set_max_level(level);
}
}
fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
unsafe { MaybeUninit::uninit().assume_init() }
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn check_config_values() {
let config = Config::default()
.with_max_level(LevelFilter::Trace)
.with_log_buffer(LogId::System)
.with_tag("my_app");
assert_eq!(config.log_level, Some(LevelFilter::Trace));
assert_eq!(config.buf_id, Some(LogId::System));
assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
}
#[test]
fn log_calls_formatter() {
static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
let config = Config::default()
.with_max_level(LevelFilter::Info)
.format(|_, _| {
FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
Ok(())
});
let logger = AndroidLogger::new(config);
logger.log(&Record::builder().level(Level::Info).build());
assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
}
#[test]
fn logger_enabled_threshold() {
let logger = AndroidLogger::new(Config::default().with_max_level(LevelFilter::Info));
assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
}
#[test]
fn config_filter_match() {
let info_record = Record::builder().level(Level::Info).build();
let debug_record = Record::builder().level(Level::Debug).build();
let info_all_filter = env_filter::Builder::new().parse("info").build();
let info_all_config = Config::default().with_filter(info_all_filter);
assert!(info_all_config.filter_matches(&info_record));
assert!(!info_all_config.filter_matches(&debug_record));
}
#[test]
fn fill_tag_bytes_truncates_long_tag() {
let logger = AndroidLogger::new(Config::default());
let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
logger.fill_tag_bytes(&mut result, &too_long_tag);
let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
expected_result.extend("..\0".as_bytes());
assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
}
#[test]
fn fill_tag_bytes_keeps_short_tag() {
let logger = AndroidLogger::new(Config::default());
let short_tag: [u8; 3] = [b'a'; 3];
let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
logger.fill_tag_bytes(&mut result, &short_tag);
let mut expected_result = short_tag.to_vec();
expected_result.push(0);
assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
}
#[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 { assume_init_slice(&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");
writer.output_specified_len(5);
assert_eq!(
unsafe { assume_init_slice(&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 { assume_init_slice(&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 { assume_init_slice(&writer.buffer[..test_string.len()]) },
test_string.as_bytes()
);
}
fn get_tag_writer() -> PlatformLogWriter<'static> {
PlatformLogWriter::new(
None,
Level::Warn,
CStr::from_bytes_with_nul(b"tag\0").unwrap(),
)
}
unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
&*(slice as *const [MaybeUninit<T>] as *const [T])
}
}