blob: 0b324b69582c43929af0138f5c98fe4f1bc95498 [file] [log] [blame]
// Copyright 2016 The android_logger Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! A logger which writes to android output.
//!
//! ## Example
//!
//! ```
//! #[macro_use] extern crate log;
//! extern crate android_logger;
//!
//! use log::LevelFilter;
//! use android_logger::Config;
//!
//! /// Android code may not have obvious "main", this is just an example.
//! fn main() {
//! android_logger::init_once(
//! Config::default().with_max_level(LevelFilter::Trace),
//! );
//!
//! debug!("this is a debug {}", "message");
//! error!("this is printed by default");
//! }
//! ```
//!
//! ## Example with module path filter
//!
//! It is possible to limit log messages to output from a specific crate,
//! and override the logcat tag name (by default, the crate name is used):
//!
//! ```
//! #[macro_use] extern crate log;
//! extern crate android_logger;
//!
//! use log::LevelFilter;
//! use android_logger::{Config,FilterBuilder};
//!
//! fn main() {
//! android_logger::init_once(
//! Config::default()
//! .with_max_level(LevelFilter::Trace)
//! .with_tag("mytag")
//! .with_filter(FilterBuilder::new().parse("debug,hello::crate=trace").build()),
//! );
//!
//! // ..
//! }
//! ```
//!
//! ## Example with a custom log formatter
//!
//! ```
//! use android_logger::Config;
//!
//! android_logger::init_once(
//! Config::default()
//! .with_max_level(log::LevelFilter::Trace)
//! .format(|f, record| write!(f, "my_app: {}", record.args()))
//! )
//! ```
#[cfg(default_log_impl)]
use crate as log;
#[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 self::arrays::{fill_tag_bytes, uninit_array};
use self::platform_log_writer::PlatformLogWriter;
pub use config::Config;
#[cfg(not(default_log_impl))]
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;
/// Outputs log to Android system.
#[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,
);
};
}
}
/// Dummy output placeholder for tests.
#[cfg(not(target_os = "android"))]
fn android_log(_buf_id: Option<LogId>, _priority: log::Level, _tag: &CStr, _msg: &CStr) {}
/// Underlying android logger backend
#[derive(Debug, Default)]
pub struct AndroidLogger {
config: OnceLock<Config>,
}
impl AndroidLogger {
/// Create new logger instance from config
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();
/// Maximum length of a tag that does not require allocation.
///
/// Tags configured explicitly in [Config] will not cause an extra allocation. When the tag is
/// derived from the module path, paths longer than this limit will trigger an allocation for each
/// log statement.
///
/// The terminating nullbyte does not count towards this limit.
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;
}
// this also checks the level, but only if a filter was
// installed.
if !config.filter_matches(record) {
return;
}
// Temporary storage for null-terminating record.module_path() if it's needed.
// Tags too long to fit here cause allocation.
let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
// In case we end up allocating, keep the CString alive.
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 {
// Tag longer than available stack buffer; allocate.
_owned_tag = CString::new(module_path.as_bytes())
.expect("record.module_path() shouldn't contain nullbytes");
_owned_tag.as_ref()
};
// message must not exceed LOGGING_MSG_MAX_LEN
// therefore split log message into multiple log calls
let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
// If a custom tag is used, add the module path to the message.
// Use PlatformLogWriter to output chunks if they exceed max size.
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()),
};
// output the remaining message (this would usually be the most common case)
writer.flush();
}
fn flush(&self) {}
}
/// Send a log record to Android logging backend.
///
/// This action does not require initialization. However, without initialization it
/// will use the default filter, which allows all logs.
pub fn log(record: &Record) {
ANDROID_LOGGER
.get_or_init(AndroidLogger::default)
.log(record)
}
/// Initializes the global logger with an android logger.
///
/// This can be called many times, but will only initialize logging once,
/// and will not replace any other previously initialized logger.
///
/// It is ok to call this at the activity creation, and it will be
/// repeatedly called on every lifecycle restart (i.e. screen rotation).
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);
}
// On Android, log crate is patched to default to LevelFilter::Trace rather than Off. Preserve
// the existing "android_logger default level is Off" behavior by explicitly setting the level.
#[cfg(target_os = "android")]
if log_level.is_none() {
log::set_max_level(log::LevelFilter::Off);
}
}