--- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm
@@ -1,23 +1,76 @@
/* 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 strict";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
+ AndroidLog: "resource://gre/modules/AndroidLog.jsm",
EventDispatcher: "resource://gre/modules/Messaging.jsm",
Log: "resource://gre/modules/Log.jsm",
Services: "resource://gre/modules/Services.jsm",
});
var EXPORTED_SYMBOLS = ["GeckoViewUtils"];
+var {Appender, BasicFormatter} = Log;
+
+/**
+ * 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);
+ },
+});
+
+/*
+ * 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);
+ },
+};
+
var GeckoViewUtils = {
/**
* Define a lazy getter that loads an object from external code, and
* optionally handles observer and/or message manager notifications for the
* object, so the object only loads when a notification is received.
*
* @param scope Scope for holding the loaded object.
* @param name Name of the object to load.
@@ -350,17 +403,17 @@ var GeckoViewUtils = {
});
}
return aScope;
},
get rootLogger() {
if (!this._rootLogger) {
this._rootLogger = Log.repository.getLogger("GeckoView");
- this._rootLogger.addAppender(new Log.AndroidAppender());
+ this._rootLogger.addAppender(new AndroidAppender());
}
return this._rootLogger;
},
_log: function(aLogger, aLevel, aStrings, aExprs) {
if (!Array.isArray(aStrings)) {
const [, file, line] =
(new Error()).stack.match(/.*\n.*\n.*@(.*):(\d+):/);
--- a/services/common/logmanager.js
+++ b/services/common/logmanager.js
@@ -37,33 +37,125 @@ const DEFAULT_MAX_ERROR_AGE = 20 * 24 *
// Singletons used by each instance.
var formatter;
var dumpAppender;
var consoleAppender;
// A set of all preference roots used by all instances.
var allBranches = new Set();
+let {Appender} = 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;
+
+/**
+ * Append to an nsIStorageStream
+ *
+ * This writes logging output to an in-memory stream which can later be read
+ * back as an nsIInputStream. It can be used to avoid expensive I/O operations
+ * during logging. Instead, one can periodically consume the input stream and
+ * e.g. write it to disk asynchronously.
+ */
+function StorageStreamAppender(formatter) {
+ Appender.call(this, formatter);
+ this._name = "StorageStreamAppender";
+}
+
+StorageStreamAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ _converterStream: null, // holds the nsIConverterOutputStream
+ _outputStream: null, // holds the underlying nsIOutputStream
+
+ _ss: null,
+
+ get outputStream() {
+ if (!this._outputStream) {
+ // First create a raw stream. We can bail out early if that fails.
+ this._outputStream = this.newOutputStream();
+ if (!this._outputStream) {
+ return null;
+ }
+
+ // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
+ // the instance if we already have one.
+ if (!this._converterStream) {
+ this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ }
+ this._converterStream.init(this._outputStream, "UTF-8");
+ }
+ return this._converterStream;
+ },
+
+ newOutputStream: function newOutputStream() {
+ let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
+ .createInstance(Ci.nsIStorageStream);
+ ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
+ return ss.getOutputStream(0);
+ },
+
+ getInputStream: function getInputStream() {
+ if (!this._ss) {
+ return null;
+ }
+ return this._ss.newInputStream(0);
+ },
+
+ reset: function reset() {
+ if (!this._outputStream) {
+ return;
+ }
+ this.outputStream.close();
+ this._outputStream = null;
+ this._ss = null;
+ },
+
+ doAppend(formatted) {
+ if (!formatted) {
+ return;
+ }
+ try {
+ this.outputStream.writeString(formatted + "\n");
+ } catch (ex) {
+ if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
+ // The underlying output stream is closed, so let's open a new one
+ // and try again.
+ this._outputStream = null;
+ } try {
+ this.outputStream.writeString(formatted + "\n");
+ } catch (ex) {
+ // Ah well, we tried, but something seems to be hosed permanently.
+ }
+ }
+ }
+};
+
// A storage appender that is flushable to a file on disk. Policies for
// when to flush, to what file, log rotation etc are up to the consumer
// (although it does maintain a .sawError property to help the consumer decide
// based on its policies)
function FlushableStorageAppender(formatter) {
- Log.StorageStreamAppender.call(this, formatter);
+ StorageStreamAppender.call(this, formatter);
this.sawError = false;
}
FlushableStorageAppender.prototype = {
- __proto__: Log.StorageStreamAppender.prototype,
+ __proto__: StorageStreamAppender.prototype,
append(message) {
if (message.level >= Log.Level.Error) {
this.sawError = true;
}
- Log.StorageStreamAppender.prototype.append.call(this, message);
+ StorageStreamAppender.prototype.append.call(this, message);
},
reset() {
Log.StorageStreamAppender.prototype.reset.call(this);
this.sawError = false;
},
// Flush the current stream to a file. Somewhat counter-intuitively, you
@@ -128,16 +220,18 @@ FlushableStorageAppender.prototype = {
};
// The public LogManager object.
function LogManager(prefRoot, logNames, logFilePrefix) {
this._prefObservers = [];
this.init(prefRoot, logNames, logFilePrefix);
}
+LogManager.StorageStreamAppender = StorageStreamAppender;
+
LogManager.prototype = {
_cleaningUpFileLogs: false,
init(prefRoot, logNames, logFilePrefix) {
if (prefRoot instanceof Preferences) {
this._prefs = prefRoot;
} else {
this._prefs = new Preferences(prefRoot);
--- a/services/common/tests/unit/test_logmanager.js
+++ b/services/common/tests/unit/test_logmanager.js
@@ -10,17 +10,17 @@ ChromeUtils.import("resource://gre/modul
// Returns an array of [consoleAppender, dumpAppender, [fileAppenders]] for
// the specified log. Note that fileAppenders will usually have length=1
function getAppenders(log) {
let capps = log.appenders.filter(app => app instanceof Log.ConsoleAppender);
equal(capps.length, 1, "should only have one console appender");
let dapps = log.appenders.filter(app => app instanceof Log.DumpAppender);
equal(dapps.length, 1, "should only have one dump appender");
- let fapps = log.appenders.filter(app => app instanceof Log.StorageStreamAppender);
+ let fapps = log.appenders.filter(app => app instanceof LogManager.StorageStreamAppender);
return [capps[0], dapps[0], fapps];
}
// Test that the correct thing happens when no prefs exist for the log manager.
add_task(async function test_noPrefs() {
// tell the log manager to init with a pref branch that doesn't exist.
let lm = new LogManager("no-such-branch.", ["TestLog"], "test");
--- a/toolkit/modules/Log.jsm
+++ b/toolkit/modules/Log.jsm
@@ -1,27 +1,18 @@
/* 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 strict";
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");
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.
@@ -72,66 +63,23 @@ var Log = {
delete Log.repository;
Log.repository = value;
},
LogMessage,
Logger,
LoggerRepository,
- Formatter,
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(","));
- enumerateInterfaces: function Log_enumerateInterfaces(aObject) {
- let interfaces = [];
-
- for (let i in Ci) {
- try {
- aObject.QueryInterface(Ci[i]);
- interfaces.push(i);
- } catch (ex) {}
- }
-
- return interfaces;
- },
-
- // Logging helper:
- // let logger = Log.repository.getLogger("foo");
- // logger.info(Log.enumerateProperties(someObject).join(","));
- enumerateProperties(aObject, aExcludeComplexTypes) {
- let properties = [];
-
- for (let p in aObject) {
- try {
- if (aExcludeComplexTypes &&
- (typeof(aObject[p]) == "object" || typeof(aObject[p]) == "function"))
- continue;
- properties.push(p + " = " + aObject[p]);
- } catch (ex) {
- properties.push(p + " = " + ex);
- }
- }
-
- return properties;
- },
_formatError: function _formatError(e) {
let result = e.toString();
if (e.fileName) {
result += " (" + e.fileName;
if (e.lineNumber) {
result += ":" + e.lineNumber;
}
@@ -150,17 +98,17 @@ var Log = {
return "" + e;
}
if (e instanceof Ci.nsIException) {
return e.toString() + " " + Log.stackTrace(e);
} else if (isError(e)) {
return Log._formatError(e);
}
// else
- let message = e.message ? e.message : e;
+ let message = e.message || e;
return message + " " + Log.stackTrace(e);
},
stackTrace: function stackTrace(e) {
// Wrapped nsIException
if (e.location) {
let frame = e.location;
let output = [];
@@ -374,55 +322,16 @@ Logger.prototype = {
let index = this.ownAppenders.indexOf(appender);
if (index == -1) {
return;
}
this.ownAppenders.splice(index, 1);
this.updateAppenders();
},
- /**
- * Logs a structured message object.
- *
- * @param action
- * (string) A message action, one of a set of actions known to the
- * log consumer.
- * @param params
- * (object) Parameters to be included in the message.
- * If _level is included as a key and the corresponding value
- * is a number or known level name, the message will be logged
- * at the indicated level. If _message is included as a key, the
- * value is used as the descriptive text for the message.
- */
- logStructured(action, params) {
- if (!action) {
- throw "An action is required when logging a structured message.";
- }
- if (!params) {
- this.log(this.level, undefined, {"action": action});
- return;
- }
- if (typeof(params) != "object") {
- throw "The params argument is required to be an object.";
- }
-
- let level = params._level;
- if (level) {
- let ulevel = level.toUpperCase();
- if (ulevel in Log.Level.Numbers) {
- level = Log.Level.Numbers[ulevel];
- }
- } else {
- level = this.level;
- }
-
- params.action = action;
- this.log(level, params._message, params);
- },
-
_unpackTemplateLiteral(string, params) {
if (!Array.isArray(params)) {
// Regular log() call.
return [string, params];
}
if (!Array.isArray(string)) {
// Not using template literal. However params was packed into an array by
@@ -591,35 +500,26 @@ LoggerRepository.prototype = {
};
return proxy;
},
};
/*
* Formatters
* These massage a LogMessage into whatever output is desired.
- * BasicFormatter and StructuredFormatter are implemented here.
*/
-// Abstract formatter
-function Formatter() {}
-Formatter.prototype = {
- format: function Formatter_format(message) {}
-};
-
// Basic formatter that doesn't do anything fancy.
function BasicFormatter(dateFormat) {
if (dateFormat) {
this.dateFormat = dateFormat;
}
this.parameterFormatter = new ParameterFormatter();
}
BasicFormatter.prototype = {
- __proto__: Formatter.prototype,
-
/**
* Format the text of a message with optional parameters.
* If the text contains ${identifier}, replace that with
* the value of params[identifier]; if ${}, replace that with
* the entire params object. If no params have been substituted
* into the text, format the entire object and append that
* to the message.
*/
@@ -670,74 +570,16 @@ BasicFormatter.prototype = {
return message.time + "\t" +
message.loggerName + "\t" +
message.levelDesc + "\t" +
this.formatText(message);
}
};
/**
- * A formatter that only formats the string message component.
- */
-function MessageOnlyFormatter() {
-}
-MessageOnlyFormatter.prototype = Object.freeze({
- __proto__: Formatter.prototype,
-
- format(message) {
- return message.message;
- },
-});
-
-// Structured formatter that outputs JSON based on message data.
-// This formatter will format unstructured messages by supplying
-// default values.
-function StructuredFormatter() { }
-StructuredFormatter.prototype = {
- __proto__: Formatter.prototype,
-
- format(logMessage) {
- let output = {
- _time: logMessage.time,
- _namespace: logMessage.loggerName,
- _level: logMessage.levelDesc
- };
-
- for (let key in logMessage.params) {
- output[key] = logMessage.params[key];
- }
-
- if (!output.action) {
- output.action = "UNKNOWN";
- }
-
- if (!output._message && logMessage.message) {
- 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);
}
/*
@@ -794,31 +636,30 @@ ParameterFormatter.prototype = {
/*
* Appenders
* These can be attached to Loggers to log to different places
* Simply subclass and override doAppend to implement a new one
*/
function Appender(formatter) {
this._name = "Appender";
- this._formatter = formatter ? formatter : new BasicFormatter();
+ this._formatter = formatter || new BasicFormatter();
}
Appender.prototype = {
level: Log.Level.All,
append: function App_append(message) {
if (message) {
this.doAppend(this._formatter.format(message));
}
},
toString: function App_toString() {
return this._name + " [level=" + this.level +
", formatter=" + this._formatter + "]";
},
- doAppend: function App_doAppend(formatted) {}
};
/*
* DumpAppender
* Logs to standard out
*/
function DumpAppender(formatter) {
@@ -856,245 +697,8 @@ ConsoleAppender.prototype = {
this.doAppend(m);
}
},
doAppend: function CApp_doAppend(formatted) {
Services.console.logStringMessage(formatted);
}
};
-
-/**
- * Append to an nsIStorageStream
- *
- * This writes logging output to an in-memory stream which can later be read
- * back as an nsIInputStream. It can be used to avoid expensive I/O operations
- * during logging. Instead, one can periodically consume the input stream and
- * e.g. write it to disk asynchronously.
- */
-function StorageStreamAppender(formatter) {
- Appender.call(this, formatter);
- this._name = "StorageStreamAppender";
-}
-
-StorageStreamAppender.prototype = {
- __proto__: Appender.prototype,
-
- _converterStream: null, // holds the nsIConverterOutputStream
- _outputStream: null, // holds the underlying nsIOutputStream
-
- _ss: null,
-
- get outputStream() {
- if (!this._outputStream) {
- // First create a raw stream. We can bail out early if that fails.
- this._outputStream = this.newOutputStream();
- if (!this._outputStream) {
- return null;
- }
-
- // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
- // the instance if we already have one.
- if (!this._converterStream) {
- this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
- .createInstance(Ci.nsIConverterOutputStream);
- }
- this._converterStream.init(this._outputStream, "UTF-8");
- }
- return this._converterStream;
- },
-
- newOutputStream: function newOutputStream() {
- let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
- .createInstance(Ci.nsIStorageStream);
- ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
- return ss.getOutputStream(0);
- },
-
- getInputStream: function getInputStream() {
- if (!this._ss) {
- return null;
- }
- return this._ss.newInputStream(0);
- },
-
- reset: function reset() {
- if (!this._outputStream) {
- return;
- }
- this.outputStream.close();
- this._outputStream = null;
- this._ss = null;
- },
-
- doAppend(formatted) {
- if (!formatted) {
- return;
- }
- try {
- this.outputStream.writeString(formatted + "\n");
- } catch (ex) {
- if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
- // The underlying output stream is closed, so let's open a new one
- // and try again.
- this._outputStream = null;
- } try {
- this.outputStream.writeString(formatted + "\n");
- } catch (ex) {
- // Ah well, we tried, but something seems to be hosed permanently.
- }
- }
- }
-};
-
-/**
- * File appender
- *
- * Writes output to file using OS.File.
- */
-function FileAppender(path, formatter) {
- Appender.call(this, formatter);
- this._name = "FileAppender";
- this._encoder = new TextEncoder();
- this._path = path;
- this._file = null;
- this._fileReadyPromise = null;
-
- // This is a promise exposed for testing/debugging the logger itself.
- this._lastWritePromise = null;
-}
-
-FileAppender.prototype = {
- __proto__: Appender.prototype,
-
- _openFile() {
- return (async () => {
- try {
- this._file = await OS.File.open(this._path,
- {truncate: true});
- } catch (err) {
- if (err instanceof OS.File.Error) {
- this._file = null;
- } else {
- throw err;
- }
- }
- })();
- },
-
- _getFile() {
- if (!this._fileReadyPromise) {
- this._fileReadyPromise = this._openFile();
- }
-
- return this._fileReadyPromise;
- },
-
- doAppend(formatted) {
- let array = this._encoder.encode(formatted + "\n");
- if (this._file) {
- this._lastWritePromise = this._file.write(array);
- } else {
- this._lastWritePromise = this._getFile().then(_ => {
- this._fileReadyPromise = null;
- if (this._file) {
- return this._file.write(array);
- }
- return undefined;
- });
- }
- },
-
- reset() {
- let fileClosePromise = this._file.close();
- return fileClosePromise.then(_ => {
- this._file = null;
- return OS.File.remove(this._path);
- });
- }
-};
-
-/**
- * Bounded File appender
- *
- * Writes output to file using OS.File. After the total message size
- * (as defined by formatted.length) exceeds maxSize, existing messages
- * will be discarded, and subsequent writes will be appended to a new log file.
- */
-function BoundedFileAppender(path, formatter, maxSize = 2 * ONE_MEGABYTE) {
- FileAppender.call(this, path, formatter);
- this._name = "BoundedFileAppender";
- this._size = 0;
- this._maxSize = maxSize;
- this._closeFilePromise = null;
-}
-
-BoundedFileAppender.prototype = {
- __proto__: FileAppender.prototype,
-
- doAppend(formatted) {
- if (!this._removeFilePromise) {
- if (this._size < this._maxSize) {
- this._size += formatted.length;
- return FileAppender.prototype.doAppend.call(this, formatted);
- }
- this._removeFilePromise = this.reset();
- }
- this._removeFilePromise.then(_ => {
- this._removeFilePromise = null;
- this.doAppend(formatted);
- });
- return undefined;
- },
-
- reset() {
- let fileClosePromise;
- if (this._fileReadyPromise) {
- // An attempt to open the file may still be in progress.
- fileClosePromise = this._fileReadyPromise.then(_ => {
- return this._file.close();
- });
- } else {
- fileClosePromise = this._file.close();
- }
-
- 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);
- },
-};
--- a/toolkit/modules/tests/xpcshell/test_Log.js
+++ b/toolkit/modules/tests/xpcshell/test_Log.js
@@ -59,38 +59,16 @@ add_task(function test_Logger_parent() {
grandparentLog.addAppender(gpAppender);
childLog.info("child info test");
Log.repository.rootLogger.info("this shouldn't show up in gpAppender");
Assert.equal(gpAppender.messages.length, 1);
Assert.ok(gpAppender.messages[0].indexOf("child info test") > 0);
});
-add_test(function test_LoggerWithMessagePrefix() {
- let log = Log.repository.getLogger("test.logger.prefix");
- let appender = new MockAppender(new Log.MessageOnlyFormatter());
- log.addAppender(appender);
-
- let prefixed = Log.repository.getLoggerWithMessagePrefix(
- "test.logger.prefix", "prefix: ");
-
- log.warn("no prefix");
- prefixed.warn("with prefix");
- prefixed.warn `with prefix`;
-
- Assert.equal(appender.messages.length, 3, "3 messages were logged.");
- Assert.deepEqual(appender.messages, [
- "no prefix",
- "prefix: with prefix",
- "prefix: with prefix",
- ], "Prefix logger works.");
-
- run_next_test();
-});
-
/*
* A utility method for checking object equivalence.
* Fields with a reqular expression value in expected will be tested
* against the corresponding value in actual. Otherwise objects
* are expected to have the same keys and equal values.
*/
function checkObjects(expected, actual) {
Assert.ok(expected instanceof Object);
@@ -106,235 +84,16 @@ function checkObjects(expected, actual)
}
}
for (let key in actual) {
Assert.notEqual(expected[key], undefined);
}
}
-add_task(function test_StructuredLogCommands() {
- let appender = new MockAppender(new Log.StructuredFormatter());
- let logger = Log.repository.getLogger("test.StructuredOutput");
- logger.addAppender(appender);
- logger.level = Log.Level.Info;
-
- logger.logStructured("test_message", {_message: "message string one"});
- logger.logStructured("test_message", {_message: "message string two",
- _level: "ERROR",
- source_file: "test_Log.js"});
- logger.logStructured("test_message");
- logger.logStructured("test_message", {source_file: "test_Log.js",
- message_position: 4});
-
- let messageOne = {"_time": /\d+/,
- "_namespace": "test.StructuredOutput",
- "_level": "INFO",
- "_message": "message string one",
- "action": "test_message"};
-
- let messageTwo = {"_time": /\d+/,
- "_namespace": "test.StructuredOutput",
- "_level": "ERROR",
- "_message": "message string two",
- "action": "test_message",
- "source_file": "test_Log.js"};
-
- let messageThree = {"_time": /\d+/,
- "_namespace": "test.StructuredOutput",
- "_level": "INFO",
- "action": "test_message"};
-
- let messageFour = {"_time": /\d+/,
- "_namespace": "test.StructuredOutput",
- "_level": "INFO",
- "action": "test_message",
- "source_file": "test_Log.js",
- "message_position": 4};
-
- checkObjects(messageOne, JSON.parse(appender.messages[0]));
- checkObjects(messageTwo, JSON.parse(appender.messages[1]));
- checkObjects(messageThree, JSON.parse(appender.messages[2]));
- checkObjects(messageFour, JSON.parse(appender.messages[3]));
-
- let errored = false;
- try {
- logger.logStructured("", {_message: "invalid message"});
- } catch (e) {
- errored = true;
- Assert.equal(e, "An action is required when logging a structured message.");
- } finally {
- Assert.ok(errored);
- }
-
- errored = false;
- try {
- logger.logStructured("message_action", "invalid params");
- } catch (e) {
- errored = true;
- Assert.equal(e, "The params argument is required to be an object.");
- } finally {
- Assert.ok(errored);
- }
-
- // Logging with unstructured interface should produce the same messages
- // as the structured interface for these cases.
- appender = new MockAppender(new Log.StructuredFormatter());
- logger = Log.repository.getLogger("test.StructuredOutput1");
- messageOne._namespace = "test.StructuredOutput1";
- messageTwo._namespace = "test.StructuredOutput1";
- logger.addAppender(appender);
- logger.level = Log.Level.All;
- logger.info("message string one", {action: "test_message"});
- logger.error("message string two", {action: "test_message",
- source_file: "test_Log.js"});
-
- checkObjects(messageOne, JSON.parse(appender.messages[0]));
- checkObjects(messageTwo, JSON.parse(appender.messages[1]));
-});
-
-add_task(function test_StorageStreamAppender() {
- let appender = new Log.StorageStreamAppender(testFormatter);
- Assert.equal(appender.getInputStream(), null);
-
- // Log to the storage stream and verify the log was written and can be
- // read back.
- let logger = Log.repository.getLogger("test.StorageStreamAppender");
- logger.addAppender(appender);
- logger.info("OHAI");
- let inputStream = appender.getInputStream();
- let data = NetUtil.readInputStreamToString(inputStream,
- inputStream.available());
- Assert.equal(data, "test.StorageStreamAppender\tINFO\tOHAI\n");
-
- // We can read it again even.
- let sndInputStream = appender.getInputStream();
- let sameData = NetUtil.readInputStreamToString(sndInputStream,
- sndInputStream.available());
- Assert.equal(data, sameData);
-
- // Reset the appender and log some more.
- appender.reset();
- Assert.equal(appender.getInputStream(), null);
- logger.debug("wut?!?");
- inputStream = appender.getInputStream();
- data = NetUtil.readInputStreamToString(inputStream,
- inputStream.available());
- Assert.equal(data, "test.StorageStreamAppender\tDEBUG\twut?!?\n");
-});
-
-function fileContents(path) {
- let decoder = new TextDecoder();
- return OS.File.read(path).then(array => {
- return decoder.decode(array);
- });
-}
-
-add_task(async function test_FileAppender() {
- // This directory does not exist yet
- let dir = OS.Path.join(do_get_profile().path, "test_Log");
- Assert.equal(false, await OS.File.exists(dir));
- let path = OS.Path.join(dir, "test_FileAppender");
- let appender = new Log.FileAppender(path, testFormatter);
- let logger = Log.repository.getLogger("test.FileAppender");
- logger.addAppender(appender);
-
- // Logging to a file that can't be created won't do harm.
- Assert.equal(false, await OS.File.exists(path));
- logger.info("OHAI!");
-
- await OS.File.makeDir(dir);
- logger.info("OHAI");
- await appender._lastWritePromise;
-
- Assert.equal((await fileContents(path)),
- "test.FileAppender\tINFO\tOHAI\n");
-
- logger.info("OHAI");
- await appender._lastWritePromise;
-
- Assert.equal((await fileContents(path)),
- "test.FileAppender\tINFO\tOHAI\n" +
- "test.FileAppender\tINFO\tOHAI\n");
-
- // Reset the appender and log some more.
- await appender.reset();
- Assert.equal(false, await OS.File.exists(path));
-
- logger.debug("O RLY?!?");
- await appender._lastWritePromise;
- Assert.equal((await fileContents(path)),
- "test.FileAppender\tDEBUG\tO RLY?!?\n");
-
- await appender.reset();
- logger.debug("1");
- logger.info("2");
- logger.info("3");
- logger.info("4");
- logger.info("5");
- // Waiting on only the last promise should account for all of these.
- await appender._lastWritePromise;
-
- // Messages ought to be logged in order.
- Assert.equal((await fileContents(path)),
- "test.FileAppender\tDEBUG\t1\n" +
- "test.FileAppender\tINFO\t2\n" +
- "test.FileAppender\tINFO\t3\n" +
- "test.FileAppender\tINFO\t4\n" +
- "test.FileAppender\tINFO\t5\n");
-});
-
-add_task(async function test_BoundedFileAppender() {
- let dir = OS.Path.join(do_get_profile().path, "test_Log");
-
- if (!(await OS.File.exists(dir))) {
- await OS.File.makeDir(dir);
- }
-
- let path = OS.Path.join(dir, "test_BoundedFileAppender");
- // This appender will hold about two lines at a time.
- let appender = new Log.BoundedFileAppender(path, testFormatter, 40);
- let logger = Log.repository.getLogger("test.BoundedFileAppender");
- logger.addAppender(appender);
-
- logger.info("ONE");
- logger.info("TWO");
- await appender._lastWritePromise;
-
- Assert.equal((await fileContents(path)),
- "test.BoundedFileAppender\tINFO\tONE\n" +
- "test.BoundedFileAppender\tINFO\tTWO\n");
-
- logger.info("THREE");
- logger.info("FOUR");
-
- Assert.notEqual(appender._removeFilePromise, undefined);
- await appender._removeFilePromise;
- await appender._lastWritePromise;
-
- Assert.equal((await fileContents(path)),
- "test.BoundedFileAppender\tINFO\tTHREE\n" +
- "test.BoundedFileAppender\tINFO\tFOUR\n");
-
- await appender.reset();
- logger.info("ONE");
- logger.info("TWO");
- logger.info("THREE");
- logger.info("FOUR");
-
- Assert.notEqual(appender._removeFilePromise, undefined);
- await appender._removeFilePromise;
- await appender._lastWritePromise;
-
- Assert.equal((await fileContents(path)),
- "test.BoundedFileAppender\tINFO\tTHREE\n" +
- "test.BoundedFileAppender\tINFO\tFOUR\n");
-
-});
-
/*
* Test parameter formatting.
*/
add_task(async function log_message_with_params() {
let formatter = new Log.BasicFormatter();
function formatMessage(text, params) {
let full = formatter.format(new Log.LogMessage("test.logger", Log.Level.Warn, text, params));
@@ -507,41 +266,16 @@ add_task(async function test_log_err_onl
log.error(e);
let msg = appender.messages.pop();
Assert.equal(msg.message, null);
Assert.equal(msg.params, e);
}
});
/*
- * Test logStructured() messages through basic formatter.
- */
-add_task(async function test_structured_basic() {
- let log = Log.repository.getLogger("test.logger");
- let appender = new MockAppender(new Log.BasicFormatter());
-
- log.level = Log.Level.Info;
- appender.level = Log.Level.Info;
- log.addAppender(appender);
-
- // A structured entry with no _message is treated the same as log./level/(null, params)
- // except the 'action' field is added to the object.
- log.logStructured("action", {data: "structure"});
- Assert.equal(appender.messages.length, 1);
- Assert.ok(appender.messages[0].includes('{"data":"structure","action":"action"}'));
-
- // A structured entry with _message and substitution is treated the same as
- // log./level/(null, params).
- log.logStructured("action", {_message: "Structured sub ${data}", data: "structure"});
- Assert.equal(appender.messages.length, 2);
- info(appender.messages[1]);
- Assert.ok(appender.messages[1].includes("Structured sub structure"));
-});
-
-/*
* Test that all the basic logger methods pass the message and params through to the appender.
*/
add_task(async function log_message_with_params() {
let log = Log.repository.getLogger("error.logger");
let mockFormatter = { format: msg => msg };
let appender = new MockAppender(mockFormatter);
log.addAppender(appender);