--- a/toolkit/modules/Log.jsm
+++ b/toolkit/modules/Log.jsm
@@ -9,22 +9,22 @@ var EXPORTED_SYMBOLS = ["Log"];
const ONE_BYTE = 1;
const ONE_KILOBYTE = 1024 * ONE_BYTE;
const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
const STREAM_SEGMENT_SIZE = 4096;
const PR_UINT32_MAX = 0xffffffff;
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "OS",
- "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AndroidLog: "resource://gre/modules/AndroidLog.jsm", // Only used on Android.
+ OS: "resource://gre/modules/osfile.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+ Task: "resource://gre/modules/Task.jsm",
+});
const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);
/*
* Dump a message everywhere we can if we have a failure.
*/
function dumpError(text) {
dump(text + "\n");
@@ -81,16 +81,17 @@ var Log = {
BasicFormatter,
MessageOnlyFormatter,
StructuredFormatter,
Appender,
DumpAppender,
ConsoleAppender,
StorageStreamAppender,
+ AndroidAppender,
FileAppender,
BoundedFileAppender,
ParameterFormatter,
// Logging helper:
// let logger = Log.repository.getLogger("foo");
// logger.info(Log.enumerateInterfaces(someObject).join(","));
@@ -672,16 +673,31 @@ StructuredFormatter.prototype = {
output._message = logMessage.message;
}
return JSON.stringify(output);
}
};
/**
+ * A formatter that does not prepend time/name/level information to messages,
+ * because those fields are logged separately when using the Android logger.
+ */
+function AndroidFormatter() {
+ BasicFormatter.call(this);
+}
+AndroidFormatter.prototype = Object.freeze({
+ __proto__: BasicFormatter.prototype,
+
+ format(message) {
+ return this.formatText(message);
+ },
+});
+
+/**
* Test an object to see if it is a Mozilla JS Error.
*/
function isError(aObj) {
return (aObj && typeof(aObj) == "object" && "name" in aObj && "message" in aObj &&
"fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
}
/*
@@ -1002,8 +1018,43 @@ BoundedFileAppender.prototype = {
return fileClosePromise.then(_ => {
this._size = 0;
this._file = null;
return OS.File.remove(this._path);
});
}
};
+
+/*
+ * AndroidAppender
+ * Logs to Android logcat using AndroidLog.jsm
+ */
+function AndroidAppender(aFormatter) {
+ Appender.call(this, aFormatter || new AndroidFormatter());
+ this._name = "AndroidAppender";
+}
+AndroidAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ // Map log level to AndroidLog.foo method.
+ _mapping: {
+ [Log.Level.Fatal]: "e",
+ [Log.Level.Error]: "e",
+ [Log.Level.Warn]: "w",
+ [Log.Level.Info]: "i",
+ [Log.Level.Config]: "d",
+ [Log.Level.Debug]: "d",
+ [Log.Level.Trace]: "v",
+ },
+
+ append(aMessage) {
+ if (!aMessage) {
+ return;
+ }
+
+ // AndroidLog.jsm always prepends "Gecko" to the tag, so we strip any
+ // leading "Gecko" here. Also strip dots to save space.
+ const tag = aMessage.loggerName.replace(/^Gecko|\./g, "");
+ const msg = this._formatter.format(aMessage);
+ AndroidLog[this._mapping[aMessage.level]](tag, msg);
+ },
+};