(Bug 1635487) Wired up sync logging for extension pref storage r=lina,markh
☠☠ backed out by da08f29b4fbc ☠ ☠
authorlougeniac64 <lougeniaC64@users.noreply.github.com>
Sat, 27 Jun 2020 06:26:22 +0000
changeset 537702 8cd7fabbe2703b2c2a70b3139f0cc2a4cb6621b6
parent 537701 035d17b431efa2c9df104474aaacb17cb935c5ab
child 537703 0cb142657e596695752580b339d956ecfcbfa866
push id120175
push userkcambridge@mozilla.com
push dateSat, 27 Jun 2020 06:27:13 +0000
treeherderautoland@8cd7fabbe270 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslina, markh
bugs1635487
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
(Bug 1635487) Wired up sync logging for extension pref storage r=lina,markh Differential Revision: https://phabricator.services.mozilla.com/D80975
Cargo.lock
security/manager/ssl/cert_storage/src/lib.rs
services/common/app_services_logger/AppServicesLoggerComponents.h
services/common/app_services_logger/Cargo.toml
services/common/app_services_logger/components.conf
services/common/app_services_logger/src/lib.rs
services/common/moz.build
services/interfaces/moz.build
services/interfaces/mozIAppServicesLogger.idl
services/interfaces/mozIBridgedSyncEngine.idl
services/interfaces/mozIServicesLogSink.idl
services/interfaces/mozIServicesLogger.idl
services/sync/golden_gate/src/log.rs
services/sync/modules/bridged_engine.js
services/sync/modules/engines/extension-storage.js
toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
toolkit/components/places/SyncedBookmarksMirror.jsm
toolkit/components/places/bookmark_sync/src/driver.rs
toolkit/components/places/bookmark_sync/src/merger.rs
toolkit/components/places/mozISyncedBookmarksMirror.idl
toolkit/library/rust/shared/Cargo.toml
toolkit/library/rust/shared/lib.rs
toolkit/profile/xpcshell/test_register_app_services_logger.js
toolkit/profile/xpcshell/xpcshell.ini
xpcom/base/nsDebugImpl.cpp
xpcom/base/nsIDebug2.idl
xpcom/rust/gecko_logger/Cargo.toml
xpcom/rust/gecko_logger/src/lib.rs
xpcom/rust/moz_task/src/lib.rs
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,16 +23,28 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
 version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2494382e9ba43995f3c56359e518641f450f5c36feeb4632a75cde2ec297c867"
 
 [[package]]
+name = "app_services_logger"
+version = "0.1.0"
+dependencies = [
+ "golden_gate",
+ "log",
+ "nserror",
+ "nsstring",
+ "once_cell",
+ "xpcom",
+]
+
+[[package]]
 name = "app_units"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9dadc668390b373e73e4abbfc1f07238b09a25858f2f39c06cebc6d8e141d774"
 dependencies = [
  "num-traits",
  "serde",
 ]
@@ -1607,16 +1619,17 @@ dependencies = [
  "rkv 0.10.4",
  "tempfile",
 ]
 
 [[package]]
 name = "gecko_logger"
 version = "0.1.0"
 dependencies = [
+ "app_services_logger",
  "env_logger",
  "lazy_static",
  "log",
 ]
 
 [[package]]
 name = "geckodriver"
 version = "0.26.0"
@@ -1847,16 +1860,17 @@ dependencies = [
  "nsstring-gtest",
  "xpcom-gtest",
 ]
 
 [[package]]
 name = "gkrust-shared"
 version = "0.1.0"
 dependencies = [
+ "app_services_logger",
  "audio_thread_priority",
  "audioipc-client",
  "audioipc-server",
  "authenticator",
  "bitsdownload",
  "bookmark_sync",
  "cascade_bloom_filter",
  "cert_storage",
--- a/security/manager/ssl/cert_storage/src/lib.rs
+++ b/security/manager/ssl/cert_storage/src/lib.rs
@@ -1263,20 +1263,17 @@ impl CertStorage {
             &self.security_state,
             move |ss| ss.get_has_prior_data(data_type),
         )));
         let runnable = try_ns!(TaskRunnable::new("HasPriorData", task));
         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
         NS_OK
     }
 
-    unsafe fn GetRemainingOperationCount(
-        &self,
-        state: *mut i32,
-    ) -> nserror::nsresult {
+    unsafe fn GetRemainingOperationCount(&self, state: *mut i32) -> nserror::nsresult {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if state.is_null() {
             return NS_ERROR_NULL_POINTER;
         }
         let ss = try_ns!(self.security_state.read());
         *state = ss.remaining_ops;
new file mode 100644
--- /dev/null
+++ b/services/common/app_services_logger/AppServicesLoggerComponents.h
@@ -0,0 +1,38 @@
+/* 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/. */
+
+#ifndef mozilla_services_AppServicesLoggerComponents_h_
+#define mozilla_services_AppServicesLoggerComponents_h_
+
+#include "mozIAppServicesLogger.h"
+#include "nsCOMPtr.h"
+
+extern "C" {
+
+// Implemented in Rust, in the `app_services_logger` crate.
+nsresult NS_NewAppServicesLogger(mozIAppServicesLogger** aResult);
+
+}  // extern "C"
+
+namespace mozilla {
+namespace appservices {
+
+// The C++ constructor for a `services.appServicesLogger` service. This wrapper
+// exists because `components.conf` requires a component class constructor to
+// return an `already_AddRefed<T>`, but Rust doesn't have such a type. So we
+// call the Rust constructor using a `nsCOMPtr` (which is compatible with Rust's
+// `xpcom::RefPtr`) out param, and return that.
+already_AddRefed<mozIAppServicesLogger> NewLogService() {
+  nsCOMPtr<mozIAppServicesLogger> logger;
+  nsresult rv = NS_NewAppServicesLogger(getter_AddRefs(logger));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+  return logger.forget();
+}
+
+}  // namespace appservices
+}  // namespace mozilla
+
+#endif  // mozilla_services_AppServicesLoggerComponents_h_
new file mode 100644
--- /dev/null
+++ b/services/common/app_services_logger/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "app_services_logger"
+version = "0.1.0"
+authors = ["lougeniac64 <lougeniaC64@users.noreply.github.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+golden_gate = { path = "../../../services/sync/golden_gate" }
+log = "0.4"
+once_cell = "1.4.0"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
new file mode 100644
--- /dev/null
+++ b/services/common/app_services_logger/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+    {
+        'cid': '{d2716568-f5fa-4989-91dd-e11599e932a1}',
+        'contract_ids': ['@mozilla.org/appservices/logger;1'],
+        'type': 'mozIAppServicesLogger',
+        'headers': ['mozilla/appservices/AppServicesLoggerComponents.h'],
+        'constructor': 'mozilla::appservices::NewLogService',
+    },
+]
new file mode 100644
--- /dev/null
+++ b/services/common/app_services_logger/src/lib.rs
@@ -0,0 +1,71 @@
+/* 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 XPCOM service to send app services logs to the desktop
+
+#[macro_use]
+extern crate xpcom;
+
+use golden_gate::log::LogSink;
+use log;
+use nserror::{nsresult, NS_OK};
+use nsstring::nsAString;
+use once_cell::sync::Lazy;
+use std::{cmp, collections::HashMap, sync::RwLock};
+use xpcom::{
+    interfaces::{mozIAppServicesLogger, mozIServicesLogSink},
+    RefPtr,
+};
+
+#[derive(xpcom)]
+#[xpimplements(mozIAppServicesLogger)]
+#[refcnt = "nonatomic"]
+pub struct InitAppServicesLogger {}
+
+pub static LOGGERS_BY_TARGET: Lazy<RwLock<HashMap<String, LogSink>>> = Lazy::new(|| {
+    let h: HashMap<String, LogSink> = HashMap::new();
+    let m = RwLock::new(h);
+    m
+});
+
+impl AppServicesLogger {
+    xpcom_method!(register => Register(target: *const nsAString, logger: *const mozIServicesLogSink));
+    fn register(&self, target: &nsAString, logger: &mozIServicesLogSink) -> Result<(), nsresult> {
+        let log_sink_logger = LogSink::with_logger(Some(logger))?;
+        let max_level = cmp::max(log::max_level(), log_sink_logger.max_level);
+
+        // Note: This will only work if the max_level is lower than the compile-time
+        // max_level_* filter.
+        log::set_max_level(max_level);
+
+        LOGGERS_BY_TARGET
+            .write()
+            .unwrap()
+            .insert(target.to_string(), log_sink_logger);
+        Ok(())
+    }
+
+    pub fn is_app_services_logger_registered(target: String) -> bool {
+        match LOGGERS_BY_TARGET.read() {
+            Ok(loggers_by_target) => loggers_by_target.contains_key(&target),
+            Err(_e) => false,
+        }
+    }
+}
+
+/// The constructor for an `AppServicesLogger` service. This uses C linkage so that it
+/// can be called from C++. See `AppServicesLoggerComponents.h` for the C++
+/// constructor that's passed to the component manager.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences `result`.
+#[no_mangle]
+pub unsafe extern "C" fn NS_NewAppServicesLogger(
+    result: *mut *const mozIAppServicesLogger,
+) -> nsresult {
+    let logger = AppServicesLogger::allocate(InitAppServicesLogger {});
+    RefPtr::new(logger.coerce::<mozIAppServicesLogger>()).forget(&mut *result);
+    NS_OK
+}
--- a/services/common/moz.build
+++ b/services/common/moz.build
@@ -4,16 +4,20 @@
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Cloud Services', 'Firefox: Common')
 
 TEST_DIRS += ['tests']
 
+EXPORTS.mozilla.appservices += [
+    'app_services_logger/AppServicesLoggerComponents.h',
+]
+
 EXTRA_COMPONENTS += [
     'servicesComponents.manifest',
 ]
 
 EXTRA_JS_MODULES['services-common'] += [
     'async.js',
     'kinto-http-client.js',
     'kinto-offline-client.js',
@@ -32,8 +36,12 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'andr
         'tokenserverclient.js',
     ]
 
 TESTING_JS_MODULES.services.common += [
     'modules-testing/logging.js',
 ]
 
 SPHINX_TREES['services'] = 'docs'
+
+XPCOM_MANIFESTS += [
+    'app_services_logger/components.conf',
+]
--- a/services/interfaces/moz.build
+++ b/services/interfaces/moz.build
@@ -7,12 +7,13 @@ with Files('**'):
 
 # Services interfaces are shared with other components (like Places and
 # WebExtension storage), so we keep them in a separate folder and build them for
 # all configurations, regardless of whether we build Sync.
 
 XPIDL_MODULE = 'services'
 
 XPIDL_SOURCES += [
+    'mozIAppServicesLogger.idl',
     'mozIBridgedSyncEngine.idl',
     'mozIInterruptible.idl',
-    'mozIServicesLogger.idl',
+    'mozIServicesLogSink.idl',
 ]
new file mode 100644
--- /dev/null
+++ b/services/interfaces/mozIAppServicesLogger.idl
@@ -0,0 +1,11 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "mozIServicesLogSink.idl"
+
+[scriptable, uuid(446dd837-fbb0-41e4-8221-f740f672b20d)]
+interface mozIAppServicesLogger : nsISupports {
+    void register(in AString target, in mozIServicesLogSink logger);
+};
--- a/services/interfaces/mozIBridgedSyncEngine.idl
+++ b/services/interfaces/mozIBridgedSyncEngine.idl
@@ -1,13 +1,13 @@
 /* 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/. */
 
-#include "mozIServicesLogger.idl"
+#include "mozIServicesLogSink.idl"
 #include "nsISupports.idl"
 
 interface nsIVariant;
 
 // A generic callback called with a result. Variants are automatically unboxed
 // in JavaScript: for example, a `UTF8String` will be passed as a string
 // argument; an `Int32` or `Int64` as a number. Methods that don't return a
 // value, like `setLastSync` or `setUploaded`, will pass a `null` variant to
@@ -58,17 +58,17 @@ interface mozIBridgedSyncEngine : nsISup
     // records is high, it's possible that we will have committed a previous
     // batch before we hit the relevant limits, so things might have been written.
     // We hope to fix this by ensuring batch limits are such that this is
     // impossible)
     readonly attribute boolean allowSkippedRecord;
 
     // Wires up the Sync logging machinery to the bridged engine. This can be
     // `null`, in which case any logs from the engine will be discarded.
-    attribute mozIServicesLogger logger;
+    attribute mozIServicesLogSink logger;
 
     // Returns the last sync time, in milliseconds, for this engine's
     // collection. This is used to build the collection URL for fetching
     // incoming records, and as the initial value of the `X-I-U-S` header on
     // upload. If the engine persists incoming records in a permanent (non-temp)
     // table, `getLastSync` can return a "high water mark" that's the newer of
     // the collection's last sync time, and the most recent record modification
     // time. This avoids redownloading incoming records that were previously
rename from services/interfaces/mozIServicesLogger.idl
rename to services/interfaces/mozIServicesLogSink.idl
--- a/services/interfaces/mozIServicesLogger.idl
+++ b/services/interfaces/mozIServicesLogSink.idl
@@ -3,22 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 // Adapts a `Log.jsm` logger so that it can be used from native (Rust) code.
 // The synced bookmarks mirror and bridged engines implement this interface
 // to hook in to the services `LogManager` infrastructure.
 [scriptable, uuid(c92bfe0d-50b7-4a7f-9686-fe5335a696b9)]
-interface mozIServicesLogger : nsISupports {
+interface mozIServicesLogSink : nsISupports {
   const short LEVEL_OFF = 0;
   const short LEVEL_ERROR = 1;
   const short LEVEL_WARN = 2;
-  const short LEVEL_DEBUG = 3;
-  const short LEVEL_TRACE = 4;
+  const short LEVEL_INFO = 3;
+  const short LEVEL_DEBUG = 4;
+  const short LEVEL_TRACE = 5;
 
   attribute short maxLevel;
 
   void error(in AString message);
   void warn(in AString message);
   void debug(in AString message);
   void trace(in AString message);
+  void info(in AString message);
 };
--- a/services/sync/golden_gate/src/log.rs
+++ b/services/sync/golden_gate/src/log.rs
@@ -3,21 +3,21 @@
  * 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::mozIServicesLogger, RefPtr};
+use xpcom::{interfaces::mozIServicesLogSink, RefPtr};
 
 pub struct LogSink {
     pub max_level: LevelFilter,
-    logger: Option<ThreadPtrHandle<mozIServicesLogger>>,
+    logger: Option<ThreadPtrHandle<mozIServicesLogSink>>,
 }
 
 impl Default for LogSink {
     fn default() -> Self {
         LogSink {
             max_level: LevelFilter::Off,
             logger: None,
         }
@@ -27,58 +27,59 @@ impl Default for LogSink {
 impl LogSink {
     /// Creates a log sink that adapts the Rust `log` crate to the Sync
     /// `Log.jsm` 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<mozIServicesLogger>) -> LogSink {
+    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<&mozIServicesLogger>) -> Result<LogSink, nsresult> {
+    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 as i64 {
-                    mozIServicesLogger::LEVEL_ERROR => LevelFilter::Error,
-                    mozIServicesLogger::LEVEL_WARN => LevelFilter::Warn,
-                    mozIServicesLogger::LEVEL_DEBUG => LevelFilter::Debug,
-                    mozIServicesLogger::LEVEL_TRACE => LevelFilter::Trace,
+                    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!("mozIServicesLogger"), RefPtr::new(logger))?,
+                ThreadPtrHolder::new(cstr!("mozIServicesLogSink"), RefPtr::new(logger))?,
             )
         } else {
             LogSink::default()
         })
     }
 
-    /// Returns a reference to the underlying `mozIServicesLogger`.
-    pub fn logger(&self) -> Option<&mozIServicesLogger> {
+    /// 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.
     ///
@@ -126,17 +127,17 @@ impl Log for LogSink {
     }
 
     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<mozIServicesLogger>,
+    logger: ThreadPtrHandle<mozIServicesLogSink>,
     level: Level,
     message: nsString,
 }
 
 impl Task for LogTask {
     fn run(&self) {
         let logger = self.logger.get().unwrap();
         match self.level {
@@ -147,16 +148,18 @@ impl Task for LogTask {
                 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(())
     }
 }
--- a/services/sync/modules/bridged_engine.js
+++ b/services/sync/modules/bridged_engine.js
@@ -25,17 +25,17 @@ const { RawCryptoWrapper } = ChromeUtils
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   Async: "resource://services-common/async.js",
   Log: "resource://gre/modules/Log.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
 });
 
-var EXPORTED_SYMBOLS = ["BridgedEngine"];
+var EXPORTED_SYMBOLS = ["BridgedEngine", "LogAdapter"];
 
 /**
  * A stub store that converts between raw decrypted incoming records and
  * envelopes. Since the interface we need is so minimal, this class doesn't
  * inherit from the base `Store` implementation...it would take more code to
  * override all those behaviors!
  *
  * This class isn't meant to be subclassed, because bridged engines shouldn't
@@ -146,39 +146,39 @@ class BridgeError extends Error {
 class InterruptedError extends Error {
   constructor(message) {
     super(message);
     this.name = "InterruptedError";
   }
 }
 
 /**
- * Adapts a `Log.jsm` logger to a `mozIServicesLogger`. This class is copied
+ * Adapts a `Log.jsm` logger to a `mozIServicesLogSink`. This class is copied
  * from `SyncedBookmarksMirror.jsm`.
  */
 class LogAdapter {
   constructor(log) {
     this.log = log;
   }
 
   get maxLevel() {
     let level = this.log.level;
     if (level <= Log.Level.All) {
-      return Ci.mozIServicesLogger.LEVEL_TRACE;
+      return Ci.mozIServicesLogSink.LEVEL_TRACE;
     }
     if (level <= Log.Level.Info) {
-      return Ci.mozIServicesLogger.LEVEL_DEBUG;
+      return Ci.mozIServicesLogSink.LEVEL_DEBUG;
     }
     if (level <= Log.Level.Warn) {
-      return Ci.mozIServicesLogger.LEVEL_WARN;
+      return Ci.mozIServicesLogSink.LEVEL_WARN;
     }
     if (level <= Log.Level.Error) {
-      return Ci.mozIServicesLogger.LEVEL_ERROR;
+      return Ci.mozIServicesLogSink.LEVEL_ERROR;
     }
-    return Ci.mozIServicesLogger.LEVEL_OFF;
+    return Ci.mozIServicesLogSink.LEVEL_OFF;
   }
 
   trace(message) {
     this.log.trace(message);
   }
 
   debug(message) {
     this.log.debug(message);
@@ -209,17 +209,16 @@ class LogAdapter {
  * may want to override is `_trackerObj`. Even then, using the default (no-op)
  * tracker is fine, because the shape of the `Tracker` interface may not make
  * sense for all engines.
  */
 function BridgedEngine(bridge, name, service) {
   SyncEngine.call(this, name, service);
 
   this._bridge = bridge;
-  this._bridge.logger = new LogAdapter(this._log);
 }
 
 BridgedEngine.prototype = {
   __proto__: SyncEngine.prototype,
 
   /**
    * The tracker class for this engine. Subclasses may want to override this
    * with their own tracker, though using the default `Tracker` is fine.
--- a/services/sync/modules/engines/extension-storage.js
+++ b/services/sync/modules/engines/extension-storage.js
@@ -10,16 +10,17 @@ var EXPORTED_SYMBOLS = [
 ];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BridgedEngine: "resource://services-sync/bridged_engine.js",
+  LogAdapter: "resource://services-sync/bridged_engine.js",
   extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.jsm",
   Observers: "resource://services-common/observers.js",
   Svc: "resource://services-sync/util.js",
   SyncEngine: "resource://services-sync/engines.js",
   Tracker: "resource://services-sync/engines.js",
   SCORE_INCREMENT_MEDIUM: "resource://services-sync/constants.js",
   MULTI_DEVICE_THRESHOLD: "resource://services-sync/constants.js",
 });
@@ -68,16 +69,22 @@ function setEngineEnabled(enabled) {
     Svc.Prefs.set(PREF_FORCE_ENABLE, enabled);
   }
 }
 
 // A "bridged engine" to our webext-storage component.
 function ExtensionStorageEngineBridge(service) {
   let bridge = StorageSyncService.getInterface(Ci.mozIBridgedSyncEngine);
   BridgedEngine.call(this, bridge, "Extension-Storage", service);
+
+  let app_services_logger = Cc["@mozilla.org/appservices/logger;1"].getService(
+    Ci.mozIAppServicesLogger
+  );
+  let logger_target = "app-services:webext_storage:sync";
+  app_services_logger.register(logger_target, new LogAdapter(this._log));
 }
 
 ExtensionStorageEngineBridge.prototype = {
   __proto__: BridgedEngine.prototype,
   syncPriority: 10,
   // we don't support repair at all!
   _skipPercentageChance: 100,
 
--- a/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
+++ b/toolkit/components/extensions/storage/webext_storage_bridge/src/area.rs
@@ -16,17 +16,17 @@ use golden_gate::{ApplyTask, FerryTask};
 use moz_task::{self, DispatchOptions, TaskRunnable};
 use nserror::{nsresult, NS_OK};
 use nsstring::{nsACString, nsCString, nsString};
 use thin_vec::ThinVec;
 use webext_storage::STORAGE_VERSION;
 use xpcom::{
     interfaces::{
         mozIBridgedSyncEngineApplyCallback, mozIBridgedSyncEngineCallback,
-        mozIExtensionStorageCallback, mozIServicesLogger, nsIFile, nsISerialEventTarget,
+        mozIExtensionStorageCallback, mozIServicesLogSink, nsIFile, nsISerialEventTarget,
     },
     RefPtr,
 };
 
 use crate::error::{Error, Result};
 use crate::punt::{Punt, PuntTask, TeardownTask};
 use crate::store::{LazyStore, LazyStoreConfig};
 
@@ -306,23 +306,23 @@ impl StorageSyncArea {
     fn interrupt(&self) -> Result<()> {
         self.store()?.interrupt();
         Ok(())
     }
 }
 
 /// `mozIBridgedSyncEngine` implementation.
 impl StorageSyncArea {
-    xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogger);
-    fn get_logger(&self) -> Result<RefPtr<mozIServicesLogger>> {
+    xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogSink);
+    fn get_logger(&self) -> Result<RefPtr<mozIServicesLogSink>> {
         Err(NS_OK)?
     }
 
-    xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogger));
-    fn set_logger(&self, _logger: Option<&mozIServicesLogger>) -> Result<()> {
+    xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogSink));
+    fn set_logger(&self, _logger: Option<&mozIServicesLogSink>) -> Result<()> {
         Ok(())
     }
 
     xpcom_method!(get_storage_version => GetStorageVersion() -> i32);
     fn get_storage_version(&self) -> Result<i32> {
         Ok(STORAGE_VERSION.try_into().unwrap())
     }
 
--- a/toolkit/components/places/SyncedBookmarksMirror.jsm
+++ b/toolkit/components/places/SyncedBookmarksMirror.jsm
@@ -83,37 +83,37 @@ const DB_TITLE_LENGTH_MAX = 4096;
 // migration code to `migrateMirrorSchema`.
 const MIRROR_SCHEMA_VERSION = 7;
 
 const DEFAULT_MAX_FRECENCIES_TO_RECALCULATE = 400;
 
 // Use a shared jankYielder in these functions
 XPCOMUtils.defineLazyGetter(this, "yieldState", () => Async.yieldState());
 
-/** Adapts a `Log.jsm` logger to a `mozIServicesLogger`. */
+/** Adapts a `Log.jsm` logger to a `mozIServicesLogSink`. */
 class LogAdapter {
   constructor(log) {
     this.log = log;
   }
 
   get maxLevel() {
     let level = this.log.level;
     if (level <= Log.Level.All) {
-      return Ci.mozIServicesLogger.LEVEL_TRACE;
+      return Ci.mozIServicesLogSink.LEVEL_TRACE;
     }
     if (level <= Log.Level.Info) {
-      return Ci.mozIServicesLogger.LEVEL_DEBUG;
+      return Ci.mozIServicesLogSink.LEVEL_DEBUG;
     }
     if (level <= Log.Level.Warn) {
-      return Ci.mozIServicesLogger.LEVEL_WARN;
+      return Ci.mozIServicesLogSink.LEVEL_WARN;
     }
     if (level <= Log.Level.Error) {
-      return Ci.mozIServicesLogger.LEVEL_ERROR;
+      return Ci.mozIServicesLogSink.LEVEL_ERROR;
     }
-    return Ci.mozIServicesLogger.LEVEL_OFF;
+    return Ci.mozIServicesLogSink.LEVEL_OFF;
   }
 
   trace(message) {
     this.log.trace(message);
   }
 
   debug(message) {
     this.log.debug(message);
--- a/toolkit/components/places/bookmark_sync/src/driver.rs
+++ b/toolkit/components/places/bookmark_sync/src/driver.rs
@@ -9,17 +9,17 @@ use std::{
 };
 
 use dogear::{AbortSignal, Guid, ProblemCounts, StructureCounts, TelemetryEvent};
 use log::{Level, LevelFilter, Log, Metadata, Record};
 use moz_task::{Task, TaskRunnable, ThreadPtrHandle};
 use nserror::nsresult;
 use nsstring::{nsACString, nsCString, nsString};
 use storage_variant::HashPropertyBag;
-use xpcom::interfaces::{mozIServicesLogger, mozISyncedBookmarksMirrorProgressListener};
+use xpcom::interfaces::{mozIServicesLogSink, mozISyncedBookmarksMirrorProgressListener};
 
 extern "C" {
     fn NS_GeneratePlacesGUID(guid: *mut nsACString) -> nsresult;
 }
 
 fn generate_guid() -> Result<nsCString, nsresult> {
     let mut guid = nsCString::new();
     let rv = unsafe { NS_GeneratePlacesGUID(&mut *guid) };
@@ -102,24 +102,24 @@ impl dogear::Driver for Driver {
             )
             .and_then(|r| TaskRunnable::dispatch(r, progress.owning_thread()));
         }
     }
 }
 
 pub struct Logger {
     pub max_level: LevelFilter,
-    logger: Option<ThreadPtrHandle<mozIServicesLogger>>,
+    logger: Option<ThreadPtrHandle<mozIServicesLogSink>>,
 }
 
 impl Logger {
     #[inline]
     pub fn new(
         max_level: LevelFilter,
-        logger: Option<ThreadPtrHandle<mozIServicesLogger>>,
+        logger: Option<ThreadPtrHandle<mozIServicesLogSink>>,
     ) -> Logger {
         Logger { max_level, logger }
     }
 }
 
 impl Log for Logger {
     #[inline]
     fn enabled(&self, meta: &Metadata) -> bool {
@@ -148,17 +148,17 @@ impl Log for Logger {
     }
 
     fn flush(&self) {}
 }
 
 /// Logs a message to the mirror logger. This task is created on the async
 /// thread, and dispatched to the main thread.
 struct LogTask {
-    logger: ThreadPtrHandle<mozIServicesLogger>,
+    logger: ThreadPtrHandle<mozIServicesLogSink>,
     level: Level,
     message: nsString,
 }
 
 impl Task for LogTask {
     fn run(&self) {
         let logger = self.logger.get().unwrap();
         match self.level {
--- a/toolkit/components/places/bookmark_sync/src/merger.rs
+++ b/toolkit/components/places/bookmark_sync/src/merger.rs
@@ -9,32 +9,32 @@ use dogear::Store;
 use log::LevelFilter;
 use moz_task::{Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder};
 use nserror::{nsresult, NS_ERROR_NOT_AVAILABLE, NS_OK};
 use nsstring::nsString;
 use storage::Conn;
 use thin_vec::ThinVec;
 use xpcom::{
     interfaces::{
-        mozIPlacesPendingOperation, mozIServicesLogger, mozIStorageConnection,
+        mozIPlacesPendingOperation, mozIServicesLogSink, mozIStorageConnection,
         mozISyncedBookmarksMirrorCallback, mozISyncedBookmarksMirrorProgressListener,
     },
     RefPtr, XpCom,
 };
 
 use crate::driver::{AbortController, Driver, Logger};
 use crate::error;
 use crate::store;
 
 #[derive(xpcom)]
 #[xpimplements(mozISyncedBookmarksMerger)]
 #[refcnt = "nonatomic"]
 pub struct InitSyncedBookmarksMerger {
     db: RefCell<Option<Conn>>,
-    logger: RefCell<Option<RefPtr<mozIServicesLogger>>>,
+    logger: RefCell<Option<RefPtr<mozIServicesLogSink>>>,
 }
 
 impl SyncedBookmarksMerger {
     pub fn new() -> RefPtr<SyncedBookmarksMerger> {
         SyncedBookmarksMerger::allocate(InitSyncedBookmarksMerger {
             db: RefCell::default(),
             logger: RefCell::default(),
         })
@@ -51,26 +51,26 @@ impl SyncedBookmarksMerger {
 
     xpcom_method!(set_db => SetDb(connection: *const mozIStorageConnection));
     fn set_db(&self, connection: Option<&mozIStorageConnection>) -> Result<(), nsresult> {
         self.db
             .replace(connection.map(|connection| Conn::wrap(RefPtr::new(connection))));
         Ok(())
     }
 
-    xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogger);
-    fn get_logger(&self) -> Result<RefPtr<mozIServicesLogger>, nsresult> {
+    xpcom_method!(get_logger => GetLogger() -> *const mozIServicesLogSink);
+    fn get_logger(&self) -> Result<RefPtr<mozIServicesLogSink>, nsresult> {
         match *self.logger.borrow() {
             Some(ref logger) => Ok(logger.clone()),
             None => Err(NS_OK),
         }
     }
 
-    xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogger));
-    fn set_logger(&self, logger: Option<&mozIServicesLogger>) -> Result<(), nsresult> {
+    xpcom_method!(set_logger => SetLogger(logger: *const mozIServicesLogSink));
+    fn set_logger(&self, logger: Option<&mozIServicesLogSink>) -> Result<(), nsresult> {
         self.logger.replace(logger.map(RefPtr::new));
         Ok(())
     }
 
     xpcom_method!(
         merge => Merge(
             local_time_seconds: i64,
             remote_time_seconds: i64,
@@ -120,52 +120,52 @@ impl SyncedBookmarksMerger {
         Ok(())
     }
 }
 
 struct MergeTask {
     db: Conn,
     controller: Arc<AbortController>,
     max_log_level: LevelFilter,
-    logger: Option<ThreadPtrHandle<mozIServicesLogger>>,
+    logger: Option<ThreadPtrHandle<mozIServicesLogSink>>,
     local_time_millis: i64,
     remote_time_millis: i64,
     weak_uploads: Vec<nsString>,
     progress: Option<ThreadPtrHandle<mozISyncedBookmarksMirrorProgressListener>>,
     callback: ThreadPtrHandle<mozISyncedBookmarksMirrorCallback>,
     result: AtomicRefCell<error::Result<store::ApplyStatus>>,
 }
 
 impl MergeTask {
     fn new(
         db: &Conn,
         controller: Arc<AbortController>,
-        logger: Option<RefPtr<mozIServicesLogger>>,
+        logger: Option<RefPtr<mozIServicesLogSink>>,
         local_time_seconds: i64,
         remote_time_seconds: i64,
         weak_uploads: Vec<nsString>,
         callback: RefPtr<mozISyncedBookmarksMirrorCallback>,
     ) -> Result<MergeTask, nsresult> {
         let max_log_level = logger
             .as_ref()
             .and_then(|logger| {
                 let mut level = 0i16;
                 unsafe { logger.GetMaxLevel(&mut level) }.to_result().ok()?;
                 Some(level)
             })
             .map(|level| match level as i64 {
-                mozIServicesLogger::LEVEL_ERROR => LevelFilter::Error,
-                mozIServicesLogger::LEVEL_WARN => LevelFilter::Warn,
-                mozIServicesLogger::LEVEL_DEBUG => LevelFilter::Debug,
-                mozIServicesLogger::LEVEL_TRACE => LevelFilter::Trace,
+                mozIServicesLogSink::LEVEL_ERROR => LevelFilter::Error,
+                mozIServicesLogSink::LEVEL_WARN => LevelFilter::Warn,
+                mozIServicesLogSink::LEVEL_DEBUG => LevelFilter::Debug,
+                mozIServicesLogSink::LEVEL_TRACE => LevelFilter::Trace,
                 _ => LevelFilter::Off,
             })
             .unwrap_or(LevelFilter::Off);
         let logger = match logger {
-            Some(logger) => Some(ThreadPtrHolder::new(cstr!("mozIServicesLogger"), logger)?),
+            Some(logger) => Some(ThreadPtrHolder::new(cstr!("mozIServicesLogSink"), logger)?),
             None => None,
         };
         let progress = callback
             .query_interface::<mozISyncedBookmarksMirrorProgressListener>()
             .and_then(|p| {
                 ThreadPtrHolder::new(cstr!("mozISyncedBookmarksMirrorProgressListener"), p).ok()
             });
         Ok(MergeTask {
--- a/toolkit/components/places/mozISyncedBookmarksMirror.idl
+++ b/toolkit/components/places/mozISyncedBookmarksMirror.idl
@@ -1,13 +1,13 @@
 /* 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/. */
 
-#include "mozIServicesLogger.idl"
+#include "mozIServicesLogSink.idl"
 #include "nsISupports.idl"
 
 interface mozIPlacesPendingOperation;
 interface mozIStorageConnection;
 interface nsIPropertyBag;
 
 // A progress listener used to record telemetry.
 [scriptable, uuid(6239ffe3-6ffd-49ac-8b1d-958407395bf9)]
@@ -74,17 +74,17 @@ interface mozISyncedBookmarksMerger : ns
 
   // The mirror database connection to use for merging. The merge runs on the
   // connection's async thread, to avoid blocking the main thread. The
   // connection must be open, and the database schema, temp tables, and
   // triggers must be set up before calling `merge`.
   attribute mozIStorageConnection db;
 
   // Optional; used for logging.
-  attribute mozIServicesLogger logger;
+  attribute mozIServicesLogSink logger;
 
   // Merges the local and remote bookmark trees, applies the merged tree to
   // Places, and stages locally changed and reconciled items for upload. When
   // the merge finishes, either `callback.handleSuccess` or
   // `callback.handleError` are called. If `callback` also implements
   // `mozISyncedBookmarksMergerProgressListener`, the merger reports progress
   // after each step. Returns an object with a `cancel` method that can be used
   // to interrupt the merge.
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -44,16 +44,17 @@ chardetng_c = "0.1.1"
 audio_thread_priority = "0.22.1"
 mdns_service = { path="../../../../media/mtransport/mdns_service", optional = true }
 neqo_glue = { path = "../../../../netwerk/socket/neqo_glue" }
 rlbox_lucet_sandbox = { version = "0.1.0", optional = true }
 wgpu_bindings = { path = "../../../../gfx/wgpu_bindings", optional = true }
 mapped_hyph = { git = "https://github.com/jfkthame/mapped_hyph.git", tag = "v0.3.0" }
 remote = { path = "../../../../remote", optional = true }
 fog = { path = "../../../components/glean", optional = true }
+app_services_logger = { path = "../../../../services/common/app_services_logger" }
 
 unic-langid = { version = "0.8", features = ["likelysubtags"] }
 unic-langid-ffi = { path = "../../../../intl/locale/rust/unic-langid-ffi" }
 fluent-langneg = { version = "0.12.1", features = ["cldr"] }
 fluent-langneg-ffi = { path = "../../../../intl/locale/rust/fluent-langneg-ffi" }
 
 # Note: `modern_sqlite` means rusqlite's bindings file be for a sqlite with
 # version less than or equal to what we link to. This isn't a problem because we
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -1,16 +1,17 @@
 // 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/.
 
 #![cfg_attr(feature = "oom_with_hook", feature(alloc_error_hook))]
 
 extern crate geckoservo;
 
+extern crate app_services_logger;
 #[cfg(feature = "cubeb-remoting")]
 extern crate audioipc_client;
 #[cfg(feature = "cubeb-remoting")]
 extern crate audioipc_server;
 extern crate authenticator;
 #[cfg(feature = "bitsdownload")]
 extern crate bitsdownload;
 #[cfg(feature = "moz_places")]
@@ -77,18 +78,20 @@ extern crate fluent_ffi;
 #[cfg(not(target_os = "android"))]
 extern crate viaduct;
 
 #[cfg(feature = "remote")]
 extern crate remote;
 
 extern crate gecko_logger;
 
-use std::ffi::CStr;
-use std::os::raw::c_char;
+extern crate log;
+use log::info;
+
+use std::{ffi::CStr, os::raw::c_char};
 
 use gecko_logger::GeckoLogger;
 
 #[no_mangle]
 pub extern "C" fn GkRust_Init() {
     // Initialize logging.
     let _ = GeckoLogger::init();
 }
@@ -97,16 +100,25 @@ pub extern "C" fn GkRust_Init() {
 pub extern "C" fn GkRust_Shutdown() {}
 
 /// Used to implement `nsIDebug2::RustPanic` for testing purposes.
 #[no_mangle]
 pub extern "C" fn intentional_panic(message: *const c_char) {
     panic!("{}", unsafe { CStr::from_ptr(message) }.to_string_lossy());
 }
 
+/// Used to implement `nsIDebug2::rustLog` for testing purposes.
+#[no_mangle]
+pub extern "C" fn debug_log(target: *const c_char, message: *const c_char) {
+    unsafe {
+        // NOTE: The `info!` log macro is used here because we have the `release_max_level_info` feature set.
+        info!(target: CStr::from_ptr(target).to_str().unwrap(), "{}", CStr::from_ptr(message).to_str().unwrap());
+    }
+}
+
 #[cfg(feature = "oom_with_hook")]
 mod oom_hook {
     use std::alloc::{set_alloc_error_hook, Layout};
 
     extern "C" {
         fn GeckoHandleOOM(size: usize) -> !;
     }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_register_app_services_logger.js
@@ -0,0 +1,26 @@
+let waitForDebugLog = target =>
+  new Promise(resolve => {
+    Cc["@mozilla.org/appservices/logger;1"]
+      .getService(Ci.mozIAppServicesLogger)
+      .register(target, {
+        maxLevel: Ci.mozIServicesLogSink.LEVEL_INFO,
+        info: resolve,
+      });
+  });
+
+let rustLog = (target, message) => {
+  Cc["@mozilla.org/xpcom/debug;1"]
+    .getService(Ci.nsIDebug2)
+    .rustLog(target, message);
+};
+
+add_task(async () => {
+  let target = "app-services:webext_storage:sync";
+  let expectedMessage = "info error: uh oh";
+  let promiseMessage = waitForDebugLog(target);
+
+  rustLog(target, expectedMessage);
+
+  let actualMessage = await promiseMessage;
+  Assert.ok(actualMessage.includes(expectedMessage));
+});
--- a/toolkit/profile/xpcshell/xpcshell.ini
+++ b/toolkit/profile/xpcshell/xpcshell.ini
@@ -39,8 +39,9 @@ skip-if = devedition
 [test_conflict_installs.js]
 [test_invalid_descriptor.js]
 [test_legacy_empty.js]
 [test_legacy_select.js]
 [test_fix_directory_case.js]
 [test_ignore_legacy_directory.js]
 [test_select_profile_argument.js]
 [test_select_profile_argument_new.js]
+[test_register_app_services_logger.js]
--- a/xpcom/base/nsDebugImpl.cpp
+++ b/xpcom/base/nsDebugImpl.cpp
@@ -145,16 +145,25 @@ nsDebugImpl::CrashWithOOM() {
 extern "C" void intentional_panic(const char* message);
 
 NS_IMETHODIMP
 nsDebugImpl::RustPanic(const char* aMessage) {
   intentional_panic(aMessage);
   return NS_OK;
 }
 
+// From toolkit/library/rust/lib.rs
+extern "C" void debug_log(const char* target, const char* message);
+
+NS_IMETHODIMP
+nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) {
+  debug_log(aTarget, aMessage);
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsDebugImpl::GetIsDebugBuild(bool* aResult) {
 #ifdef DEBUG
   *aResult = true;
 #else
   *aResult = false;
 #endif
   return NS_OK;
--- a/xpcom/base/nsIDebug2.idl
+++ b/xpcom/base/nsIDebug2.idl
@@ -83,12 +83,21 @@ interface nsIDebug2 : nsISupports
     /**
      * Request the process to trigger a fatal panic!() from Rust code.
      *
      * @param aMessage the string to pass to panic!().
      */
     void rustPanic(in string aMessage);
 
     /**
+     * Request the process to log a message for a target and level from Rust code.
+     *
+     * @param aTarget the string representing the log target.
+     * @param aMessage the string representing the log message.
+     */
+    void rustLog(in string aTarget,
+                 in string aMessage);
+
+    /**
      * Cause an Out of Memory Crash.
      */
     void crashWithOOM();
 };
--- a/xpcom/rust/gecko_logger/Cargo.toml
+++ b/xpcom/rust/gecko_logger/Cargo.toml
@@ -4,8 +4,9 @@ version = "0.1.0"
 authors = ["nobody@mozilla.com"]
 edition = "2018"
 license = "MPL-2.0"
 
 [dependencies]
 lazy_static = "1"
 log = {version = "0.4", features = ["release_max_level_info"]}
 env_logger = {version = "0.6", default-features = false} # disable `regex` to reduce code size
+app_services_logger = { path = "../../../services/common/app_services_logger" }
--- a/xpcom/rust/gecko_logger/src/lib.rs
+++ b/xpcom/rust/gecko_logger/src/lib.rs
@@ -2,17 +2,17 @@
  * 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;
 
-#[cfg(not(target_os = "android"))]
+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};
@@ -180,16 +180,28 @@ impl GeckoLogger {
 
         // 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 {
@@ -227,19 +239,20 @@ impl GeckoLogger {
         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)
+        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) {}
 }
--- a/xpcom/rust/moz_task/src/lib.rs
+++ b/xpcom/rust/moz_task/src/lib.rs
@@ -211,17 +211,16 @@ impl TaskRunnable {
 
     xpcom_method!(run => Run());
     fn run(&self) -> Result<(), nsresult> {
         match self
             .has_run
             .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
         {
             Ok(_) => {
-                assert!(!is_current_thread(&self.original_thread));
                 self.task.run();
                 Self::dispatch(RefPtr::new(self), &self.original_thread)
             }
             Err(_) => {
                 assert!(is_current_thread(&self.original_thread));
                 self.task.done()
             }
         }