// 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(target_os = "android")] externcrate android_log_sys as log_ffi; externcrate once_cell; use once_cell::sync::OnceCell; #[macro_use] externcrate log;
externcrate env_logger;
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;
pubuse env_logger::filter::{Builder as FilterBuilder, Filter}; pubuse env_logger::fmt::Formatter;
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;
}
// tag must not exceed LOGGING_TAG_MAX_LEN letmut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
let module_path = record.module_path().unwrap_or_default().to_owned();
// If no tag was specified, use module name let custom_tag = &config.tag; let tag = custom_tag
.as_ref()
.map(|s| s.as_bytes())
.unwrap_or_else(|| module_path.as_bytes());
// truncate the tag here to fit into LOGGING_TAG_MAX_LEN self.fill_tag_bytes(&mut tag_bytes, tag); // use stack array as C string let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
// message must not exceed LOGGING_MSG_MAX_LEN // therefore split log message into multiple log calls letmut writer = PlatformLogWriter::new(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 (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()),
};
// output the remaining message (this would usually be the most common case)
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);
}
}
}
}
impl Config { // TODO: Remove on 0.13 version release. /// **DEPRECATED**, use [`Config::with_max_level()`] instead. #[deprecated(note = "use `.with_max_level()` instead")] pubfn with_min_level(self, level: Level) -> Self { self.with_max_level(level.to_level_filter())
}
/// Changes the maximum log level. /// /// Note, that `Trace` is the maximum level, because it provides the /// maximum amount of detail in the emitted logs. /// /// If `Off` level is provided, then nothing is logged at all. /// /// [`log::max_level()`] is considered as the default level. pubfn with_max_level(mutself, level: LevelFilter) -> Self { self.log_level = Some(level); self
}
#[cfg(not(target_os = "android"))] pubfn new(level: Level, tag: &CStr) -> PlatformLogWriter { #[allow(deprecated)] // created an issue #35 for this
PlatformLogWriter {
priority: level,
len: 0,
last_newline_index: 0,
tag,
buffer: uninit_array(),
}
}
/// Flush some bytes to android logger. /// /// If there is a newline, flush up to it. /// If ther was no newline, flush all. /// /// Not guaranteed to flush everything. fn temporal_flush(&mutself) { let total_len = self.len;
if total_len == 0 { return;
}
ifself.last_newline_index > 0 { let copy_from_index = self.last_newline_index; let remaining_chunk_len = total_len - copy_from_index;
/// Output buffer up until the \0 which will be placed at `len` position. fn output_specified_len(&mutself, len: usize) { letmut last_byte = MaybeUninit::new(b'\0');
while !incomming_bytes.is_empty() { let len = self.len;
// write everything possible to buffer and mark last \n 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
}
});
// update last \n index iflet Some(newline) = last_newline { self.last_newline_index = len + newline;
}
// calculate how many bytes were written let written_len = if new_len <= LOGGING_MSG_MAX_LEN { // if the len was not exceeded self.len = new_len;
new_len - len // written len
} else { // if new length was exceeded self.len = LOGGING_MSG_MAX_LEN; self.temporal_flush();
/// 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. pubfn 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). pubfn init_once(config: Config) { let log_level = config.log_level; let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
// FIXME: When `maybe_uninit_uninit_array ` is stabilized, use it instead of this helper fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] { // SAFETY: Array contains MaybeUninit, which is fine to be uninit unsafe { MaybeUninit::uninit().assume_init() }
}
#[cfg(test)] mod tests { usesuper::*; use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering};
#[test] fn check_config_values() { // Filter is checked in config_filter_match below. let config = Config::default()
.with_max_level(LevelFilter::Trace)
.with_tag("my_app");
// Test whether the filter gets called correctly. Not meant to be exhaustive for all filter // options, as these are handled directly by the filter itself. #[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_logger::filter::Builder::new().parse("info").build(); let info_all_config = Config::default().with_filter(info_all_filter);
writer
.write_str("12\n\n567\n90")
.expect("Unable to write to PlatformLogWriter");
assert_eq!(writer.len, 10);
writer.temporal_flush(); // Should have flushed up until the last newline.
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(); // Should have flushed all remaining bytes.
assert_eq!(writer.len, 0);
assert_eq!(writer.last_newline_index, 0);
}
#[test] fn flush() { letmut writer = get_tag_writer();
writer
.write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
.expect("Unable to write to PlatformLogWriter");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.