#[cfg(target_os = "android")]
extern crate android_log_sys as log_ffi;
use log::{Log, Metadata, Record};
use std::ffi::{CStr, CString};
use std::fmt;
use std::mem::MaybeUninit;
use std::sync::OnceLock;
use crate::arrays::{fill_tag_bytes, uninit_array};
use crate::platform_log_writer::PlatformLogWriter;
pub use config::Config;
pub use env_filter::{Builder as FilterBuilder, Filter};
pub use id::LogId;
pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
mod arrays;
mod config;
mod id;
mod platform_log_writer;
#[cfg(test)]
mod tests;
#[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: log::Level, _tag: &CStr, _msg: &CStr) {}
#[derive(Debug, Default)]
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 = 127;
const LOGGING_MSG_MAX_LEN: usize = 4000;
impl Log for AndroidLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
self.config()
.is_loggable(metadata.target(), metadata.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 _owned_tag;
let module_path = record.module_path().unwrap_or_default();
let tag = if let Some(tag) = &config.tag {
tag
} else if module_path.len() < tag_bytes.len() {
fill_tag_bytes(&mut tag_bytes, module_path.as_bytes())
} else {
_owned_tag = CString::new(module_path.as_bytes())
.expect("record.module_path() shouldn't contain nullbytes");
_owned_tag.as_ref()
};
let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
let _ = match (&config.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) {}
}
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);
}
}