summaryrefslogtreecommitdiffstats
path: root/xpcom/rust/gecko_logger/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/rust/gecko_logger/src/lib.rs')
-rw-r--r--xpcom/rust/gecko_logger/src/lib.rs258
1 files changed, 258 insertions, 0 deletions
diff --git a/xpcom/rust/gecko_logger/src/lib.rs b/xpcom/rust/gecko_logger/src/lib.rs
new file mode 100644
index 0000000000..42d14a6846
--- /dev/null
+++ b/xpcom/rust/gecko_logger/src/lib.rs
@@ -0,0 +1,258 @@
+/* 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::boxed::Box;
+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.
+ //
+ // [1]: https://github.com/rust-lang/rust/issues/73714
+ 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) {}
+}