Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! This provides a way to direct rust logging into the gecko logger.
#[macro_use]
extern crate lazy_static;
use app_services_logger::{AppServicesLogger, LOGGERS_BY_TARGET};
use log::Log;
use log::{Level, LevelFilter};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::os::raw::c_int;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::RwLock;
use std::{cmp, env};
extern "C" {
fn ExternMozLog(tag: *const c_char, prio: c_int, text: *const c_char);
fn gfx_critical_note(msg: *const c_char);
#[cfg(target_os = "android")]
fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
}
lazy_static! {
// This could be a proper static once [1] is fixed or parking_lot's const fn
// support is not nightly-only.
//
static ref LOG_MODULE_MAP: RwLock<HashMap<String, (LevelFilter, bool)>> = RwLock::new(HashMap::new());
}
/// This tells us whether LOG_MODULE_MAP is possibly non-empty.
static LOGGING_ACTIVE: AtomicBool = AtomicBool::new(false);
/// This function searches for the module's name in the hashmap. If that is not
/// found, it proceeds to search for the parent modules.
/// It returns a tuple containing the matched string, if the matched module
/// was a pattern match, and the level we found in the hashmap.
/// If none is found, it will return the module's name and LevelFilter::Off
fn get_level_for_module<'a>(
map: &HashMap<String, (LevelFilter, bool)>,
key: &'a str,
) -> (&'a str, bool, LevelFilter) {
if let Some((level, is_pattern_match)) = map.get(key) {
return (key, *is_pattern_match, level.clone());
}
let mut mod_name = &key[..];
while let Some(pos) = mod_name.rfind("::") {
mod_name = &mod_name[..pos];
if let Some((level, is_pattern_match)) = map.get(mod_name) {
return (mod_name, *is_pattern_match, level.clone());
}
}
return (key, false, LevelFilter::Off);
}
/// This function takes a record to maybe log to Gecko.
/// It returns true if the record was handled by Gecko's logging, and false
/// otherwise.
pub fn log_to_gecko(record: &log::Record) -> bool {
if !LOGGING_ACTIVE.load(Ordering::Relaxed) {
return false;
}
let key = match record.module_path() {
Some(key) => key,
None => return false,
};
let (mod_name, is_pattern_match, level) = {
let map = LOG_MODULE_MAP.read().unwrap();
get_level_for_module(&map, &key)
};
if level == LevelFilter::Off {
return false;
}
if level < record.metadata().level() {
return false;
}
// Map the log::Level to mozilla::LogLevel.
let moz_log_level = match record.metadata().level() {
Level::Error => 1, // Error
Level::Warn => 2, // Warning
Level::Info => 3, // Info
Level::Debug => 4, // Debug
Level::Trace => 5, // Verbose
};
// If it was a pattern match, we need to append ::* to the matched string.
let (tag, msg) = if is_pattern_match {
(
CString::new(format!("{}::*", mod_name)).unwrap(),
CString::new(format!("[{}] {}", key, record.args())).unwrap(),
)
} else {
(
CString::new(key).unwrap(),
CString::new(format!("{}", record.args())).unwrap(),
)
};
unsafe {
ExternMozLog(tag.as_ptr(), moz_log_level, msg.as_ptr());
}
return true;
}
#[no_mangle]
pub unsafe extern "C" fn set_rust_log_level(module: *const c_char, level: u8) {
// Convert the Gecko level to a rust LevelFilter.
let rust_level = match level {
1 => LevelFilter::Error,
2 => LevelFilter::Warn,
3 => LevelFilter::Info,
4 => LevelFilter::Debug,
5 => LevelFilter::Trace,
_ => LevelFilter::Off,
};
// This is the name of the rust module that we're trying to log in Gecko.
let mut mod_name = CStr::from_ptr(module).to_string_lossy().into_owned();
let is_pattern_match = mod_name.ends_with("::*");
// If this is a pattern, remove the last "::*" from it so we can search it
// in the map.
if is_pattern_match {
let len = mod_name.len() - 3;
mod_name.truncate(len);
}
LOGGING_ACTIVE.store(true, Ordering::Relaxed);
let mut map = LOG_MODULE_MAP.write().unwrap();
map.insert(mod_name, (rust_level, is_pattern_match));
// Figure out the max level of all the modules.
let max = map
.values()
.map(|(lvl, _)| lvl)
.max()
.unwrap_or(&LevelFilter::Off);
log::set_max_level(*max);
}
pub struct GeckoLogger {
logger: env_logger::Logger,
}
impl GeckoLogger {
pub fn new() -> GeckoLogger {
let mut builder = env_logger::Builder::new();
let default_level = if cfg!(debug_assertions) {
"warn"
} else {
"error"
};
let logger = match env::var("RUST_LOG") {
Ok(v) => builder.parse_filters(&v).build(),
_ => builder.parse_filters(default_level).build(),
};
GeckoLogger { logger }
}
pub fn init() -> Result<(), log::SetLoggerError> {
let gecko_logger = Self::new();
// The max level may have already been set by gecko_logger. Don't
// set it to a lower level.
let level = cmp::max(log::max_level(), gecko_logger.logger.filter());
log::set_max_level(level);
log::set_boxed_logger(Box::new(gecko_logger))
}
fn should_log_to_app_services(target: &str) -> bool {
return AppServicesLogger::is_app_services_logger_registered(target.into());
}
fn maybe_log_to_app_services(&self, record: &log::Record) {
if Self::should_log_to_app_services(record.target()) {
if let Some(l) = LOGGERS_BY_TARGET.read().unwrap().get(record.target()) {
l.log(record);
}
}
}
fn should_log_to_gfx_critical_note(record: &log::Record) -> bool {
record.level() == log::Level::Error && record.target().contains("webrender")
}
fn maybe_log_to_gfx_critical_note(&self, record: &log::Record) {
if Self::should_log_to_gfx_critical_note(record) {
let msg = CString::new(format!("{}", record.args())).unwrap();
unsafe {
gfx_critical_note(msg.as_ptr());
}
}
}
#[cfg(not(target_os = "android"))]
fn log_out(&self, record: &log::Record) {
// If the log wasn't handled by the gecko platform logger, just pass it
// to the env_logger.
if !log_to_gecko(record) {
self.logger.log(record);
}
}
#[cfg(target_os = "android")]
fn log_out(&self, record: &log::Record) {
if !self.logger.matches(record) {
return;
}
let msg = CString::new(format!("{}", record.args())).unwrap();
let tag = CString::new(record.module_path().unwrap()).unwrap();
let prio = match record.metadata().level() {
Level::Error => 6, /* ERROR */
Level::Warn => 5, /* WARN */
Level::Info => 4, /* INFO */
Level::Debug => 3, /* DEBUG */
Level::Trace => 2, /* VERBOSE */
};
// Output log directly to android log, since env_logger can output log
// only to stderr or stdout.
unsafe {
__android_log_write(prio, tag.as_ptr(), msg.as_ptr());
}
}
}
impl log::Log for GeckoLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.logger.enabled(metadata) || GeckoLogger::should_log_to_app_services(metadata.target())
}
fn log(&self, record: &log::Record) {
// Forward log to gfxCriticalNote, if the log should be in gfx crash log.
self.maybe_log_to_gfx_critical_note(record);
self.maybe_log_to_app_services(record);
self.log_out(record);
}
fn flush(&self) {}
}