diff options
Diffstat (limited to 'mailnews/db/gloda/modules/log4moz.js')
-rw-r--r-- | mailnews/db/gloda/modules/log4moz.js | 932 |
1 files changed, 932 insertions, 0 deletions
diff --git a/mailnews/db/gloda/modules/log4moz.js b/mailnews/db/gloda/modules/log4moz.js new file mode 100644 index 000000000..379c669e1 --- /dev/null +++ b/mailnews/db/gloda/modules/log4moz.js @@ -0,0 +1,932 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ['Log4Moz']; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +var MODE_RDONLY = 0x01; +var MODE_WRONLY = 0x02; +var MODE_CREATE = 0x08; +var MODE_APPEND = 0x10; +var MODE_TRUNCATE = 0x20; + +var PERMS_FILE = parseInt("0644", 8); +var PERMS_DIRECTORY = parseInt("0755", 8); + +var ONE_BYTE = 1; +var ONE_KILOBYTE = 1024 * ONE_BYTE; +var ONE_MEGABYTE = 1024 * ONE_KILOBYTE; + +var DEFAULT_NETWORK_TIMEOUT_DELAY = 5; + +var CDATA_START = "<![CDATA["; +var CDATA_END = "]]>"; +var CDATA_ESCAPED_END = CDATA_END + "]]>" + CDATA_START; + +var Log4Moz = { + Level: { + Fatal: 70, + Error: 60, + Warn: 50, + Info: 40, + Config: 30, + Debug: 20, + Trace: 10, + All: 0, + Desc: { + 70: "FATAL", + 60: "ERROR", + 50: "WARN", + 40: "INFO", + 30: "CONFIG", + 20: "DEBUG", + 10: "TRACE", + 0: "ALL" + } + }, + + /** + * Create a logger and configure it with dump and console appenders as + * specified by prefs based on the logger name. + * + * E.g., if the loggername is foo, then look for prefs + * foo.logging.console + * foo.logging.dump + * + * whose values can be empty: no logging of that type; or any of + * 'Fatal', 'Error', 'Warn', 'Info', 'Config', 'Debug', 'Trace', 'All', + * in which case the logging level for each appender will be set accordingly + * + * Parameters: + * + * @param loggername The name of the logger + * @param level (optional) the level of the logger itself + * @param consoleLevel (optional) the level of the console appender + * @param dumpLevel (optional) the level of the dump appender + * + * As described above, well-named prefs override the last two parameters + **/ + + getConfiguredLogger: function(loggername, level, consoleLevel, dumpLevel) { + let log = Log4Moz.repository.getLogger(loggername); + if (log._configured) + return log + + let formatter = new Log4Moz.BasicFormatter(); + + level = level || Log4Moz.Level.Error; + + consoleLevel = consoleLevel || -1; + dumpLevel = dumpLevel || -1; + let branch = Services.prefs.getBranch(loggername + ".logging."); + if (branch) + { + try { + // figure out if event-driven indexing should be enabled... + let consoleLevelString = branch.getCharPref("console"); + if (consoleLevelString) { + // capitalize to fit with Log4Moz.Level expectations + consoleLevelString = consoleLevelString.charAt(0).toUpperCase() + + consoleLevelString.substr(1).toLowerCase(); + consoleLevel = (consoleLevelString == 'None') ? + 100 : Log4Moz.Level[consoleLevelString]; + } + } catch (ex) { + // Ignore if preference is not found + } + try { + let dumpLevelString = branch.getCharPref("dump"); + if (dumpLevelString) { + // capitalize to fit with Log4Moz.Level expectations + dumpLevelString = dumpLevelString.charAt(0).toUpperCase() + + dumpLevelString.substr(1).toLowerCase(); + dumpLevel = (dumpLevelString == 'None') ? + 100 : Log4Moz.Level[dumpLevelString]; + } + } catch (ex) { + // Ignore if preference is not found + } + } + + if (consoleLevel != 100) { + if (consoleLevel == -1) + consoleLevel = Log4Moz.Level.Error; + let capp = new Log4Moz.ConsoleAppender(formatter); + capp.level = consoleLevel; + log.addAppender(capp); + } + + if (dumpLevel != 100) { + if (dumpLevel == -1) + dumpLevel = Log4Moz.Level.Error; + let dapp = new Log4Moz.DumpAppender(formatter); + dapp.level = dumpLevel; + log.addAppender(dapp); + } + + log.level = Math.min(level, Math.min(consoleLevel, dumpLevel)); + + log._configured = true; + + return log; + }, + + get repository() { + delete Log4Moz.repository; + Log4Moz.repository = new LoggerRepository(); + return Log4Moz.repository; + }, + set repository(value) { + delete Log4Moz.repository; + Log4Moz.repository = value; + }, + + get LogMessage() { return LogMessage; }, + get Logger() { return Logger; }, + get LoggerRepository() { return LoggerRepository; }, + + get Formatter() { return Formatter; }, + get BasicFormatter() { return BasicFormatter; }, + get XMLFormatter() { return XMLFormatter; }, + get JSONFormatter() { return JSONFormatter; }, + get Appender() { return Appender; }, + get DumpAppender() { return DumpAppender; }, + get ConsoleAppender() { return ConsoleAppender; }, + get TimeAwareMemoryBucketAppender() { return TimeAwareMemoryBucketAppender; }, + get FileAppender() { return FileAppender; }, + get SocketAppender() { return SocketAppender; }, + get RotatingFileAppender() { return RotatingFileAppender; }, + get ThrowingAppender() { return ThrowingAppender; }, + + // Logging helper: + // let logger = Log4Moz.repository.getLogger("foo"); + // logger.info(Log4Moz.enumerateInterfaces(someObject).join(",")); + enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) { + let interfaces = []; + + for (i in Ci) { + try { + aObject.QueryInterface(Ci[i]); + interfaces.push(i); + } + catch(ex) {} + } + + return interfaces; + }, + + // Logging helper: + // let logger = Log4Moz.repository.getLogger("foo"); + // logger.info(Log4Moz.enumerateProperties(someObject).join(",")); + enumerateProperties: function Log4Moz_enumerateProps(aObject, + aExcludeComplexTypes) { + let properties = []; + + for (var 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; + } +}; + +function LoggerContext() { + this._started = this._lastStateChange = Date.now(); + this._state = "started"; +} +LoggerContext.prototype = { + _jsonMe: true, + _id: "unknown", + setState: function LoggerContext_state(aState) { + this._state = aState; + this._lastStateChange = Date.now(); + return this; + }, + finish: function LoggerContext_finish() { + this._finished = Date.now(); + this._state = "finished"; + return this; + }, + toString: function LoggerContext_toString() { + return "[Context: " + this._id + " state: " + this._state + "]"; + } +}; + + +/* + * LogMessage + * Encapsulates a single log event's data + */ +function LogMessage(loggerName, level, messageObjects){ + this.loggerName = loggerName; + this.messageObjects = messageObjects; + this.level = level; + this.time = Date.now(); +} +LogMessage.prototype = { + get levelDesc() { + if (this.level in Log4Moz.Level.Desc) + return Log4Moz.Level.Desc[this.level]; + return "UNKNOWN"; + }, + + toString: function LogMsg_toString(){ + return "LogMessage [" + this.time + " " + this.level + " " + + this.messageObjects + "]"; + } +}; + +/* + * Logger + * Hierarchical version. Logs to all appenders, assigned or inherited + */ + +function Logger(name, repository) { + this._init(name, repository); +} +Logger.prototype = { + _init: function Logger__init(name, repository) { + if (!repository) + repository = Log4Moz.repository; + this._name = name; + this.children = []; + this.ownAppenders = []; + this.appenders = []; + this._repository = repository; + }, + + get name() { + return this._name; + }, + + _level: null, + get level() { + if (this._level != null) + return this._level; + if (this.parent) + return this.parent.level; + dump("log4moz warning: root logger configuration error: no level defined\n"); + return Log4Moz.Level.All; + }, + set level(level) { + this._level = level; + }, + + _parent: null, + get parent() { return this._parent; }, + set parent(parent) { + if (this._parent == parent) { + return; + } + // Remove ourselves from parent's children + if (this._parent) { + let index = this._parent.children.indexOf(this); + if (index != -1) { + this._parent.children.splice(index, 1); + } + } + this._parent = parent; + parent.children.push(this); + this.updateAppenders(); + }, + + updateAppenders: function updateAppenders() { + if (this._parent) { + let notOwnAppenders = this._parent.appenders.filter(function(appender) { + return this.ownAppenders.indexOf(appender) == -1; + }, this); + this.appenders = notOwnAppenders.concat(this.ownAppenders); + } else { + this.appenders = this.ownAppenders.slice(); + } + + // Update children's appenders. + for (let i = 0; i < this.children.length; i++) { + this.children[i].updateAppenders(); + } + }, + + addAppender: function Logger_addAppender(appender) { + if (this.ownAppenders.indexOf(appender) != -1) { + return; + } + this.ownAppenders.push(appender); + this.updateAppenders(); + }, + + _nextContextId: 0, + newContext: function Logger_newContext(objWithProps) { + if (!("_id" in objWithProps)) + objWithProps._id = this._name + ":" + (++this._nextContextId); + + let c = new LoggerContext(); + c._isContext = true; + for (let key in objWithProps) { + c[key] = objWithProps[key]; + } + return c; + }, + + log: function Logger_log(message) { + if (this.level > message.level) + return; + let appenders = this.appenders; + for (let i = 0; i < appenders.length; i++){ + appenders[i].append(message); + } + }, + + removeAppender: function Logger_removeAppender(appender) { + let index = this.ownAppenders.indexOf(appender); + if (index == -1) { + return; + } + this.ownAppenders.splice(index, 1); + this.updateAppenders(); + }, + + log: function Logger_log(level, args) { + if (this.level > level) + return; + + // Hold off on creating the message object until we actually have + // an appender that's responsible. + let message; + let appenders = this.appenders; + for (let i = 0; i < appenders.length; i++){ + let appender = appenders[i]; + if (appender.level > level) + continue; + + if (!message) + message = new LogMessage(this._name, level, + Array.prototype.slice.call(args)); + + appender.append(message); + } + }, + + fatal: function Logger_fatal() { + this.log(Log4Moz.Level.Fatal, arguments); + }, + error: function Logger_error() { + this.log(Log4Moz.Level.Error, arguments); + }, + warn: function Logger_warn() { + this.log(Log4Moz.Level.Warn, arguments); + }, + info: function Logger_info(string) { + this.log(Log4Moz.Level.Info, arguments); + }, + config: function Logger_config(string) { + this.log(Log4Moz.Level.Config, arguments); + }, + debug: function Logger_debug(string) { + this.log(Log4Moz.Level.Debug, arguments); + }, + trace: function Logger_trace(string) { + this.log(Log4Moz.Level.Trace, arguments); + } +}; + +/* + * LoggerRepository + * Implements a hierarchy of Loggers + */ + +function LoggerRepository() {} +LoggerRepository.prototype = { + _loggers: {}, + + _rootLogger: null, + get rootLogger() { + if (!this._rootLogger) { + this._rootLogger = new Logger("root", this); + this._rootLogger.level = Log4Moz.Level.All; + } + return this._rootLogger; + }, + set rootLogger(logger) { + throw "Cannot change the root logger"; + }, + + _updateParents: function LogRep__updateParents(name) { + let pieces = name.split('.'); + let cur, parent; + + // find the closest parent + // don't test for the logger name itself, as there's a chance it's already + // there in this._loggers + for (let i = 0; i < pieces.length - 1; i++) { + if (cur) + cur += '.' + pieces[i]; + else + cur = pieces[i]; + if (cur in this._loggers) + parent = cur; + } + + // if we didn't assign a parent above, there is no parent + if (!parent) + this._loggers[name].parent = this.rootLogger; + else + this._loggers[name].parent = this._loggers[parent]; + + // trigger updates for any possible descendants of this logger + for (let logger in this._loggers) { + if (logger != name && logger.indexOf(name) == 0) + this._updateParents(logger); + } + }, + + getLogger: function LogRep_getLogger(name) { + if (name in this._loggers) + return this._loggers[name]; + this._loggers[name] = new Logger(name, this); + this._updateParents(name); + return this._loggers[name]; + } +}; + +/* + * Formatters + * These massage a LogMessage into whatever output is desired + * Only the BasicFormatter is currently implemented + */ + +// Abstract formatter +function Formatter() {} +Formatter.prototype = { + format: function Formatter_format(message) {} +}; + +// services' log4moz lost the date formatting default... +function BasicFormatter(dateFormat) { + if (dateFormat) + this.dateFormat = dateFormat; +} +BasicFormatter.prototype = { + __proto__: Formatter.prototype, + + _dateFormat: null, + + get dateFormat() { + if (!this._dateFormat) + this._dateFormat = "%Y-%m-%d %H:%M:%S"; + return this._dateFormat; + }, + + set dateFormat(format) { + this._dateFormat = format; + }, + + format: function BF_format(message) { + let date = new Date(message.time); + // The trick below prevents errors further down because mo is null or + // undefined. + let messageString = message.messageObjects.map(mo => "" + mo).join(" "); + return date.toLocaleFormat(this.dateFormat) + "\t" + + message.loggerName + "\t" + message.levelDesc + "\t" + + messageString + "\n"; + } +}; + +/* + * XMLFormatter + * Format like log4j's XMLLayout. The intent is that you can hook this up to + * a SocketAppender and point them at a Chainsaw GUI running with an + * XMLSocketReceiver running. Then your output comes out in Chainsaw. + * (Chainsaw is log4j's GUI that displays log output with niceties such as + * filtering and conditional coloring.) + */ + +function XMLFormatter() {} +XMLFormatter.prototype = { + __proto__: Formatter.prototype, + + format: function XF_format(message) { + let cdataEscapedMessage = + message.messageObjects + .map(mo => (typeof(mo) == "object") ? mo.toString() : mo) + .join(" ") + .split(CDATA_END).join(CDATA_ESCAPED_END); + return "<log4j:event logger='" + message.loggerName + "' " + + "level='" + message.levelDesc + "' thread='unknown' " + + "timestamp='" + message.time + "'>" + + "<log4j:message><![CDATA[" + cdataEscapedMessage + "]]></log4j:message>" + + "</log4j:event>"; + } +}; + +function JSONFormatter() { +} +JSONFormatter.prototype = { + __proto__: Formatter.prototype, + + format: function JF_format(message) { + // XXX I did all kinds of questionable things in here; they should be + // resolved... + // 1) JSON does not walk the __proto__ chain; there is no need to clobber + // it. + // 2) Our net mutation is sorta redundant messageObjects alongside + // msgObjects, although we only serialize one. + let origMessageObjects = message.messageObjects; + message.messageObjects = []; + let reProto = []; + for (let messageObject of origMessageObjects) { + if (messageObject) + if (messageObject._jsonMe) { + message.messageObjects.push(messageObject); +// FIXME: the commented out code should be fixed in a better way. +// See bug 984539: find a good way to avoid JSONing the impl in log4moz +// // temporarily strip the prototype to avoid JSONing the impl. +// reProto.push([messageObject, messageObject.__proto__]); +// messageObject.__proto__ = undefined; + } + else + message.messageObjects.push(messageObject.toString()); + else + message.messageObjects.push(messageObject); + } + let encoded = JSON.stringify(message) + "\r\n"; + message.msgObjects = origMessageObjects; +// for (let objectAndProtoPair of reProto) { +// objectAndProtoPair[0].__proto__ = objectAndProtoPair[1]; +// } + return encoded; + } +}; + + +/* + * 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(); +} +Appender.prototype = { + _level: Log4Moz.Level.All, + + append: function App_append(message) { + this.doAppend(this._formatter.format(message)); + }, + toString: function App_toString() { + return this._name + " [level=" + this._level + + ", formatter=" + this._formatter + "]"; + }, + doAppend: function App_doAppend(message) {} +}; + +/* + * DumpAppender + * Logs to standard out + */ + +function DumpAppender(formatter) { + this._name = "DumpAppender"; + this._formatter = formatter? formatter : new BasicFormatter(); +} +DumpAppender.prototype = { + __proto__: Appender.prototype, + + doAppend: function DApp_doAppend(message) { + dump(message); + } +}; + +/** + * An in-memory appender that always logs to its in-memory bucket and associates + * each message with a timestamp. Whoever creates us is responsible for causing + * us to switch to a new bucket using whatever criteria is appropriate. + * + * This is intended to be used roughly like an in-memory circular buffer. The + * expectation is that we are being used for unit tests and that each unit test + * function will get its own bucket. In the event that a test fails we would + * be asked for the contents of the current bucket and some portion of the + * previous bucket using up to some duration. + */ +function TimeAwareMemoryBucketAppender() { + this._name = "TimeAwareMemoryBucketAppender"; + this._level = Log4Moz.Level.All; + + this._lastBucket = null; + // to minimize object construction, even indices are timestamps, odd indices + // are the message objects. + this._curBucket = []; + this._curBucketStartedAt = Date.now(); +} +TimeAwareMemoryBucketAppender.prototype = { + get level() { return this._level; }, + set level(level) { this._level = level; }, + + append: function TAMBA_append(message) { + if (this._level <= message.level) + this._curBucket.push(message); + }, + + newBucket: function() { + this._lastBucket = this._curBucket; + this._curBucketStartedAt = Date.now(); + this._curBucket = []; + }, + + getPreviousBucketEvents: function(aNumMS) { + let lastBucket = this._lastBucket; + if (lastBucket == null || !lastBucket.length) + return []; + let timeBound = this._curBucketStartedAt - aNumMS; + // seek backwards through the list... + let i; + for (i = lastBucket.length - 1; i >= 0; i --) { + if (lastBucket[i].time < timeBound) + break; + } + return lastBucket.slice(i+1); + }, + + getBucketEvents: function() { + return this._curBucket.concat(); + }, + + toString: function() { + return "[TimeAwareMemoryBucketAppender]"; + }, +}; + +/* + * ConsoleAppender + * Logs to the javascript console + */ + +function ConsoleAppender(formatter) { + this._name = "ConsoleAppender"; + this._formatter = formatter; +} +ConsoleAppender.prototype = { + __proto__: Appender.prototype, + + // override to send Error and higher level messages to Components.utils.reportError() + append: function CApp_append(message) { + let stringMessage = this._formatter.format(message); + if (message.level > Log4Moz.Level.Warn) { + Cu.reportError(stringMessage); + } + this.doAppend(stringMessage); + }, + + doAppend: function CApp_doAppend(message) { + Services.console.logStringMessage(message); + } +}; + +/* + * FileAppender + * Logs to a file + */ + +function FileAppender(file, formatter) { + this._name = "FileAppender"; + this._file = file; // nsIFile + this._formatter = formatter? formatter : new BasicFormatter(); +} +FileAppender.prototype = { + __proto__: Appender.prototype, + + __fos: null, + get _fos() { + if (!this.__fos) + this.openStream(); + return this.__fos; + }, + + openStream: function FApp_openStream() { + this.__fos = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + let flags = MODE_WRONLY | MODE_CREATE | MODE_APPEND; + this.__fos.init(this._file, flags, PERMS_FILE, 0); + }, + + closeStream: function FApp_closeStream() { + if (!this.__fos) + return; + try { + this.__fos.close(); + this.__fos = null; + } catch(e) { + dump("Failed to close file output stream\n" + e); + } + }, + + doAppend: function FApp_doAppend(message) { + if (message === null || message.length <= 0) + return; + try { + this._fos.write(message, message.length); + } catch(e) { + dump("Error writing file:\n" + e); + } + }, + + clear: function FApp_clear() { + this.closeStream(); + this._file.remove(false); + } +}; + +/* + * RotatingFileAppender + * Similar to FileAppender, but rotates logs when they become too large + */ + +function RotatingFileAppender(file, formatter, maxSize, maxBackups) { + if (maxSize === undefined) + maxSize = ONE_MEGABYTE * 2; + + if (maxBackups === undefined) + maxBackups = 0; + + this._name = "RotatingFileAppender"; + this._file = file; // nsIFile + this._formatter = formatter? formatter : new BasicFormatter(); + this._maxSize = maxSize; + this._maxBackups = maxBackups; +} +RotatingFileAppender.prototype = { + __proto__: FileAppender.prototype, + + doAppend: function RFApp_doAppend(message) { + if (message === null || message.length <= 0) + return; + try { + this.rotateLogs(); + this._fos.write(message, message.length); + } catch(e) { + dump("Error writing file:\n" + e); + } + }, + rotateLogs: function RFApp_rotateLogs() { + if(this._file.exists() && + this._file.fileSize < this._maxSize) + return; + + this.closeStream(); + + for (let i = this.maxBackups - 1; i > 0; i--){ + let backup = this._file.parent.clone(); + backup.append(this._file.leafName + "." + i); + if (backup.exists()) + backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1)); + } + + let cur = this._file.clone(); + if (cur.exists()) + cur.moveTo(cur.parent, cur.leafName + ".1"); + + // Note: this._file still points to the same file + } +}; + +/* + * SocketAppender + * Logs via TCP to a given host and port. Attempts to automatically reconnect + * when the connection drops or cannot be initially re-established. Connection + * attempts will happen at most every timeoutDelay seconds (has a sane default + * if left blank). Messages are dropped when there is no connection. + */ + +function SocketAppender(host, port, formatter, timeoutDelay) { + this._name = "SocketAppender"; + this._host = host; + this._port = port; + this._formatter = formatter? formatter : new BasicFormatter(); + this._timeout_delay = timeoutDelay || DEFAULT_NETWORK_TIMEOUT_DELAY; + + this._socketService = Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Ci.nsISocketTransportService); + this._mainThread = Services.tm.mainThread; +} +SocketAppender.prototype = { + __proto__: Appender.prototype, + + __nos: null, + get _nos() { + if (!this.__nos) + this.openStream(); + return this.__nos; + }, + _nextCheck: 0, + openStream: function SApp_openStream() { + let now = Date.now(); + if (now <= this._nextCheck) { + return; + } + this._nextCheck = now + this._timeout_delay * 1000; + try { + this._transport = this._socketService.createTransport( + null, 0, // default socket type + this._host, this._port, + null); // no proxy + this._transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, + this._timeout_delay); + // do not set a timeout for TIMEOUT_READ_WRITE. The timeout is not + // entirely intuitive; your socket will time out if no one reads or + // writes to the socket within the timeout. That, as you can imagine, + // is not what we want. + this._transport.setEventSink(this, this._mainThread); + + let outputStream = this._transport.openOutputStream( + 0, // neither blocking nor unbuffered operation is desired + 0, // default buffer size is fine + 0 // default buffer count is fine + ); + + let uniOutputStream = Cc["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Ci.nsIConverterOutputStream); + uniOutputStream.init(outputStream, "utf-8", 0, 0x0000); + + this.__nos = uniOutputStream; + } catch (ex) { + dump("Unexpected SocketAppender connection problem: " + + ex.fileName + ":" + ex.lineNumber + ": " + ex + "\n"); + } + }, + + closeStream: function SApp_closeStream() { + if (!this._transport) + return; + try { + this._connected = false; + this._transport = null; + let nos = this.__nos; + this.__nos = null; + nos.close(); + } catch(e) { + // this shouldn't happen, but no one cares + } + }, + + doAppend: function SApp_doAppend(message) { + if (message === null || message.length <= 0) + return; + try { + let nos = this._nos; + if (nos) + nos.writeString(message); + } catch(e) { + if (this._transport && !this._transport.isAlive()) { + this.closeStream(); + } + } + }, + + clear: function SApp_clear() { + this.closeStream(); + }, + + /* nsITransportEventSink */ + onTransportStatus: function SApp_onTransportStatus(aTransport, aStatus, + aProgress, aProgressMax) { + if (aStatus == 0x804b0004) // STATUS_CONNECTED_TO is not a constant. + this._connected = true; + }, +}; + +/** + * Throws an exception whenever it gets a message. Intended to be used in + * automated testing situations where the code would normally log an error but + * not die in a fatal manner. + */ +function ThrowingAppender(thrower, formatter) { + this._name = "ThrowingAppender"; + this._formatter = formatter? formatter : new BasicFormatter(); + this._thrower = thrower; +} +ThrowingAppender.prototype = { + __proto__: Appender.prototype, + + doAppend: function TApp_doAppend(message) { + if (this._thrower) + this._thrower(message); + else + throw message; + } +}; |