mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
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:
parent
a5afd46968
commit
bf729d7e98
@ -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;
|
||||
},
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user