Bug 1480327: Part 1 - Get rid of most of Log.jsm. r=Mossop

MozReview-Commit-ID: JVKJtkLhCDS

--HG--
extra : rebase_source : 8b47dbfaa6f279901b99c93c26eee27f719b1d1d
This commit is contained in:
Kris Maglione 2018-08-01 23:41:01 -07:00
parent a5afd46968
commit bf729d7e98
5 changed files with 154 additions and 669 deletions

View File

@ -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;
},

View File

@ -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,

View File

@ -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];
}

View File

@ -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);
},
};

View File

@ -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.
*/