diff --git a/mobile/android/modules/geckoview/GeckoViewUtils.jsm b/mobile/android/modules/geckoview/GeckoViewUtils.jsm index 74c70ab9b029..9be4534db105 100644 --- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm +++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm @@ -6,6 +6,7 @@ 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", @@ -13,6 +14,58 @@ XPCOMUtils.defineLazyModuleGetters(this, { 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 @@ -355,7 +408,7 @@ var GeckoViewUtils = { 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; }, diff --git a/services/common/logmanager.js b/services/common/logmanager.js index 14e9a3652b87..0c6264b00d80 100644 --- a/services/common/logmanager.js +++ b/services/common/logmanager.js @@ -42,23 +42,115 @@ 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() { @@ -133,6 +225,8 @@ function LogManager(prefRoot, logNames, logFilePrefix) { this.init(prefRoot, logNames, logFilePrefix); } +LogManager.StorageStreamAppender = StorageStreamAppender; + LogManager.prototype = { _cleaningUpFileLogs: false, diff --git a/services/common/tests/unit/test_logmanager.js b/services/common/tests/unit/test_logmanager.js index 51e7242cff7a..5d707a5cb8c1 100644 --- a/services/common/tests/unit/test_logmanager.js +++ b/services/common/tests/unit/test_logmanager.js @@ -15,7 +15,7 @@ function getAppenders(log) { 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]; } diff --git a/toolkit/modules/Log.jsm b/toolkit/modules/Log.jsm index f07e71d66c9a..8caae2a030c7 100644 --- a/toolkit/modules/Log.jsm +++ b/toolkit/modules/Log.jsm @@ -6,17 +6,8 @@ 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", }); @@ -77,56 +68,13 @@ var Log = { 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(); @@ -155,7 +103,7 @@ var Log = { return Log._formatError(e); } // else - let message = e.message ? e.message : e; + let message = e.message || e; return message + " " + Log.stackTrace(e); }, @@ -379,45 +327,6 @@ Logger.prototype = { 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. @@ -596,15 +505,8 @@ LoggerRepository.prototype = { /* * 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) { @@ -613,8 +515,6 @@ function BasicFormatter(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 @@ -674,64 +574,6 @@ BasicFormatter.prototype = { } }; -/** - * 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. */ @@ -799,7 +641,7 @@ ParameterFormatter.prototype = { function Appender(formatter) { this._name = "Appender"; - this._formatter = formatter ? formatter : new BasicFormatter(); + this._formatter = formatter || new BasicFormatter(); } Appender.prototype = { level: Log.Level.All, @@ -813,7 +655,6 @@ Appender.prototype = { return this._name + " [level=" + this.level + ", formatter=" + this._formatter + "]"; }, - doAppend: function App_doAppend(formatted) {} }; /* @@ -861,240 +702,3 @@ ConsoleAppender.prototype = { 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); - }, -}; diff --git a/toolkit/modules/tests/xpcshell/test_Log.js b/toolkit/modules/tests/xpcshell/test_Log.js index 2e38be4f9b69..4ec2cd37c660 100644 --- a/toolkit/modules/tests/xpcshell/test_Log.js +++ b/toolkit/modules/tests/xpcshell/test_Log.js @@ -64,28 +64,6 @@ add_task(function test_Logger_parent() { 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 @@ -111,225 +89,6 @@ function checkObjects(expected, actual) { } } -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. */ @@ -511,31 +270,6 @@ add_task(async function test_log_err_only() { } }); -/* - * 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. */