summaryrefslogtreecommitdiffstats
path: root/services/sync/golden_gate/src/log.rs
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/golden_gate/src/log.rs')
-rw-r--r--services/sync/golden_gate/src/log.rs161
1 files changed, 161 insertions, 0 deletions
diff --git a/services/sync/golden_gate/src/log.rs b/services/sync/golden_gate/src/log.rs
new file mode 100644
index 0000000000..de7fd0dfc3
--- /dev/null
+++ b/services/sync/golden_gate/src/log.rs
@@ -0,0 +1,161 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::{self, Write};
+
+use log::{Level, LevelFilter, Log, Metadata, Record};
+use moz_task::{Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder};
+use nserror::nsresult;
+use nsstring::nsString;
+use xpcom::{interfaces::mozIServicesLogSink, RefPtr};
+
+pub struct LogSink {
+ pub max_level: LevelFilter,
+ logger: Option<ThreadPtrHandle<mozIServicesLogSink>>,
+}
+
+impl Default for LogSink {
+ fn default() -> Self {
+ LogSink {
+ max_level: LevelFilter::Off,
+ logger: None,
+ }
+ }
+}
+
+impl LogSink {
+ /// Creates a log sink that adapts the Rust `log` crate to the Sync
+ /// `Log.sys.mjs` logger.
+ ///
+ /// This is copied from `bookmark_sync::Logger`. It would be nice to share
+ /// these, but, for now, we've just duplicated it to make prototyping
+ /// easier.
+ #[inline]
+ pub fn new(max_level: LevelFilter, logger: ThreadPtrHandle<mozIServicesLogSink>) -> LogSink {
+ LogSink {
+ max_level,
+ logger: Some(logger),
+ }
+ }
+
+ /// Creates a log sink using the given Services `logger` as the
+ /// underlying implementation. The `logger` will always be called
+ /// asynchronously on its owning thread; it doesn't need to be
+ /// thread-safe.
+ pub fn with_logger(logger: Option<&mozIServicesLogSink>) -> Result<LogSink, nsresult> {
+ Ok(if let Some(logger) = logger {
+ // Fetch the maximum log level while we're on the main thread, so
+ // that `LogSink::enabled()` can check it while on the background
+ // thread. Otherwise, we'd need to dispatch a `LogTask` for every
+ // log message, only to discard most of them when the task calls
+ // into the logger on the main thread.
+ let mut raw_max_level = 0i16;
+ let rv = unsafe { logger.GetMaxLevel(&mut raw_max_level) };
+ let max_level = if rv.succeeded() {
+ match raw_max_level {
+ mozIServicesLogSink::LEVEL_ERROR => LevelFilter::Error,
+ mozIServicesLogSink::LEVEL_WARN => LevelFilter::Warn,
+ mozIServicesLogSink::LEVEL_DEBUG => LevelFilter::Debug,
+ mozIServicesLogSink::LEVEL_TRACE => LevelFilter::Trace,
+ mozIServicesLogSink::LEVEL_INFO => LevelFilter::Info,
+ _ => LevelFilter::Off,
+ }
+ } else {
+ LevelFilter::Off
+ };
+ LogSink::new(
+ max_level,
+ ThreadPtrHolder::new(cstr!("mozIServicesLogSink"), RefPtr::new(logger))?,
+ )
+ } else {
+ LogSink::default()
+ })
+ }
+
+ /// Returns a reference to the underlying `mozIServicesLogSink`.
+ pub fn logger(&self) -> Option<&mozIServicesLogSink> {
+ self.logger.as_ref().and_then(|l| l.get())
+ }
+
+ /// Logs a message to the Sync logger, if one is set. This would be better
+ /// implemented as a macro, as Dogear does, so that we can pass variadic
+ /// arguments without manually invoking `fmt_args!()` every time we want
+ /// to log a message.
+ ///
+ /// The `log` crate's macros aren't suitable here, because those log to the
+ /// global logger. However, we don't want to set the global logger in our
+ /// crate, because that will log _everything_ that uses the Rust `log` crate
+ /// to the Sync logs, including WebRender and audio logging.
+ pub fn debug(&self, args: fmt::Arguments) {
+ let meta = Metadata::builder()
+ .level(Level::Debug)
+ .target(module_path!())
+ .build();
+ if self.enabled(&meta) {
+ self.log(&Record::builder().args(args).metadata(meta).build());
+ }
+ }
+}
+
+impl Log for LogSink {
+ #[inline]
+ fn enabled(&self, meta: &Metadata) -> bool {
+ self.logger.is_some() && meta.level() <= self.max_level
+ }
+
+ fn log(&self, record: &Record) {
+ if !self.enabled(record.metadata()) {
+ return;
+ }
+ if let Some(logger) = &self.logger {
+ let mut message = nsString::new();
+ if write!(message, "{}", record.args()).is_ok() {
+ let task = LogTask {
+ logger: logger.clone(),
+ level: record.metadata().level(),
+ message,
+ };
+ let _ = TaskRunnable::new("extension_storage_sync::Logger::log", Box::new(task))
+ .and_then(|r| TaskRunnable::dispatch(r, logger.owning_thread()));
+ }
+ }
+ }
+
+ fn flush(&self) {}
+}
+
+/// Logs a message to the mirror logger. This task is created on the background
+/// thread queue, and dispatched to the main thread.
+struct LogTask {
+ logger: ThreadPtrHandle<mozIServicesLogSink>,
+ level: Level,
+ message: nsString,
+}
+
+impl Task for LogTask {
+ fn run(&self) {
+ let logger = self.logger.get().unwrap();
+ match self.level {
+ Level::Error => unsafe {
+ logger.Error(&*self.message);
+ },
+ Level::Warn => unsafe {
+ logger.Warn(&*self.message);
+ },
+ Level::Debug => unsafe {
+ logger.Debug(&*self.message);
+ },
+ Level::Trace => unsafe {
+ logger.Trace(&*self.message);
+ },
+ Level::Info => unsafe {
+ logger.Info(&*self.message);
+ },
+ }
+ }
+
+ fn done(&self) -> Result<(), nsresult> {
+ Ok(())
+ }
+}