You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
5.8 KiB
177 lines
5.8 KiB
// Copyright 2021, The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Provides a universal logger interface that allows logging both on-device (using android_logger)
|
|
//! and on-host (using env_logger).
|
|
//! On-host, this allows the use of the RUST_LOG environment variable as documented in
|
|
//! https://docs.rs/env_logger.
|
|
use std::ffi::CString;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
static LOGGER_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
|
|
|
type FormatFn = Box<dyn Fn(&log::Record) -> String + Sync + Send>;
|
|
|
|
/// Logger configuration, opportunistically mapped to configuration parameters for android_logger
|
|
/// or env_logger where available.
|
|
#[derive(Default)]
|
|
pub struct Config<'a> {
|
|
log_level: Option<log::Level>,
|
|
custom_format: Option<FormatFn>,
|
|
filter: Option<&'a str>,
|
|
#[allow(dead_code)] // Field is only used on device, and ignored on host.
|
|
tag: Option<CString>,
|
|
}
|
|
|
|
/// Based on android_logger::Config
|
|
impl<'a> Config<'a> {
|
|
/// Change the minimum log level.
|
|
///
|
|
/// All values above the set level are logged. For example, if
|
|
/// `Warn` is set, the `Error` is logged too, but `Info` isn't.
|
|
pub fn with_min_level(mut self, level: log::Level) -> Self {
|
|
self.log_level = Some(level);
|
|
self
|
|
}
|
|
|
|
/// Set a log tag. Only used on device.
|
|
pub fn with_tag_on_device<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
|
|
self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
|
|
self
|
|
}
|
|
|
|
/// Set the format function for formatting the log output.
|
|
/// ```
|
|
/// # use universal_logger::Config;
|
|
/// universal_logger::init(
|
|
/// Config::default()
|
|
/// .with_min_level(log::Level::Trace)
|
|
/// .format(|record| format!("my_app: {}", record.args()))
|
|
/// )
|
|
/// ```
|
|
pub fn format<F>(mut self, format: F) -> Self
|
|
where
|
|
F: Fn(&log::Record) -> String + Sync + Send + 'static,
|
|
{
|
|
self.custom_format = Some(Box::new(format));
|
|
self
|
|
}
|
|
|
|
/// Set a filter, using the format specified in https://docs.rs/env_logger.
|
|
pub fn with_filter(mut self, filter: &'a str) -> Self {
|
|
self.filter = Some(filter);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Initializes logging on host. Returns false if logging is already initialized.
|
|
/// Config values take precedence over environment variables for host logging.
|
|
#[cfg(not(target_os = "android"))]
|
|
pub fn init(config: Config) -> bool {
|
|
// Return immediately if the logger is already initialized.
|
|
if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
|
|
return false;
|
|
}
|
|
|
|
let mut builder = env_logger::Builder::from_default_env();
|
|
if let Some(log_level) = config.log_level {
|
|
builder.filter_level(log_level.to_level_filter());
|
|
}
|
|
if let Some(custom_format) = config.custom_format {
|
|
use std::io::Write; // Trait used by write!() macro, but not in Android code
|
|
|
|
builder.format(move |f, r| {
|
|
let formatted = custom_format(r);
|
|
writeln!(f, "{}", formatted)
|
|
});
|
|
}
|
|
if let Some(filter_str) = config.filter {
|
|
builder.parse_filters(filter_str);
|
|
}
|
|
|
|
builder.init();
|
|
true
|
|
}
|
|
|
|
/// Initializes logging on device. Returns false if logging is already initialized.
|
|
#[cfg(target_os = "android")]
|
|
pub fn init(config: Config) -> bool {
|
|
// Return immediately if the logger is already initialized.
|
|
if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
|
|
return false;
|
|
}
|
|
|
|
// We do not have access to the private variables in android_logger::Config, so we have to use
|
|
// the builder instead.
|
|
let mut builder = android_logger::Config::default();
|
|
if let Some(log_level) = config.log_level {
|
|
builder = builder.with_min_level(log_level);
|
|
}
|
|
if let Some(custom_format) = config.custom_format {
|
|
builder = builder.format(move |f, r| {
|
|
let formatted = custom_format(r);
|
|
write!(f, "{}", formatted)
|
|
});
|
|
}
|
|
if let Some(filter_str) = config.filter {
|
|
let filter = env_logger::filter::Builder::new().parse(filter_str).build();
|
|
builder = builder.with_filter(filter);
|
|
}
|
|
if let Some(tag) = config.tag {
|
|
builder = builder.with_tag(tag);
|
|
}
|
|
|
|
android_logger::init_once(builder);
|
|
true
|
|
}
|
|
|
|
/// Note that the majority of tests checking behavior are under the tests/ folder, as they all
|
|
/// require independent initialization steps. The local test module just performs some basic crash
|
|
/// testing without performing initialization.
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_with_min_level() {
|
|
let config = Config::default()
|
|
.with_min_level(log::Level::Trace)
|
|
.with_min_level(log::Level::Error);
|
|
|
|
assert_eq!(config.log_level, Some(log::Level::Error));
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_filter() {
|
|
let filter = "debug,hello::crate=trace";
|
|
let config = Config::default().with_filter(filter);
|
|
|
|
assert_eq!(config.filter.unwrap(), filter)
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_tag_on_device() {
|
|
let config = Config::default().with_tag_on_device("my_app");
|
|
|
|
assert_eq!(config.tag.unwrap(), CString::new("my_app").unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_format() {
|
|
let config = Config::default().format(|record| format!("my_app: {}", record.args()));
|
|
|
|
assert!(config.custom_format.is_some());
|
|
}
|
|
}
|