Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
/**
* Update logging lives in its own module for several reasons. First, it avoids
* having some very similar logging code duplicated in several places. And
* second, we only want to open the update messages file once. Opening it
* multiple times from multiple files does not result in all those messages
* ending up in the same log file.
*
* Note that simply importing this module can cause the value of the
* `app.update.log.file` pref to change. This may seem a bit weird, but we are
* going to consider it to be acceptable because any other module that wants to
* interact with that pref really ought to be doing it by interacting with this
* module instead, making the pref more of an internal implementation detail.
* This option was chosen over doing this work more lazily (say, on a log
* message) because this initialization step has user-visible consequences
* (the log file moves) and we'd prefer that those happen close to startup so
* that the user doesn't observe it happening at a seemingly random time.
*/
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_LOG_FILE = "app.update.log.file";
const FILE_UPDATE_MESSAGES = "update_messages.log";
const FILE_BACKUP_MESSAGES = "update_messages_old.log";
const KEY_PROFILE_DIR = "ProfD";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
ChromeUtils.defineLazyGetter(
lazy,
"BinaryOutputStream",
function ul_GetBinaryStream() {
return Components.Constructor(
"@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream"
);
}
);
let gLogEnabled;
let gLogFileEnabled;
let gLogFileOutputStream;
let gLogFileBinaryStream;
let gChangeListeners = [];
function updateLogEnabledVars() {
let prevLogEnabled = gLogEnabled;
let prefLogFileEnabled = gLogFileEnabled;
gLogFileEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
if (gLogFileEnabled) {
gLogEnabled = true;
} else {
gLogEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
}
if (gLogEnabled != prevLogEnabled || gLogFileEnabled != prefLogFileEnabled) {
// Since we use this function during initialization, technically any present
// listeners will be fired at that point. But it's not really possible for
// any listeners to have been added yet.
for (const listener of gChangeListeners) {
try {
listener();
} catch (ex) {
LOG(`Listener error: ${ex}`);
}
}
}
}
updateLogEnabledVars();
function shutdown() {
Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, updateLogEnabledVars);
Services.obs.removeObserver(shutdown, "quit-application");
// Release references to listeners.
gChangeListeners = [];
if (gLogFileOutputStream) {
gLogFileOutputStream.QueryInterface(Ci.nsISafeOutputStream);
gLogFileOutputStream.finish();
}
}
Services.obs.addObserver(shutdown, "quit-application");
// This one call observes PREF_APP_UPDATE_LOG and PREF_APP_UPDATE_LOG_FILE
Services.prefs.addObserver(PREF_APP_UPDATE_LOG, updateLogEnabledVars);
function logPrefixedString(prefix, message) {
message = message.toString();
if (gLogEnabled) {
dump("*** " + prefix + " " + message + "\n");
if (!Cu.isInAutomation) {
Services.console.logStringMessage(prefix + " " + message);
}
if (gLogFileEnabled) {
if (!gLogFileOutputStream) {
let logfile = Services.dirsvc.get(KEY_PROFILE_DIR, Ci.nsIFile);
logfile.append(FILE_UPDATE_MESSAGES);
gLogFileOutputStream =
lazy.FileUtils.openAtomicFileOutputStream(logfile);
}
if (!gLogFileBinaryStream) {
gLogFileBinaryStream = new lazy.BinaryOutputStream(
gLogFileOutputStream
);
}
try {
let encoded = new TextEncoder().encode(prefix + " " + message + "\n");
gLogFileBinaryStream.writeByteArray(encoded);
gLogFileOutputStream.flush();
} catch (e) {
dump(
"*** " + prefix + " Unable to write to messages file: " + e + "\n"
);
Services.console.logStringMessage(
prefix + " Unable to write to messages file: " + e
);
}
}
}
}
// Prevent file logging from persisting for more than a session by disabling
// it on startup.
function deactivateUpdateLogFile() {
if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false)) {
return;
}
Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
LOG("Application update file logging being automatically turned off");
let logFile = Services.dirsvc.get(KEY_PROFILE_DIR, Ci.nsIFile);
logFile.append(FILE_UPDATE_MESSAGES);
try {
logFile.moveTo(null, FILE_BACKUP_MESSAGES);
} catch (e) {
LOG(
"Failed to backup update messages log (" +
e +
"). Attempting to " +
"remove it."
);
try {
logFile.remove(false);
} catch (e) {
LOG("Also failed to remove the update messages log: " + e);
}
}
}
// This can potentially cause I/O at startup, which isn't great. But it's pretty
// rare to have this option turned on, especially since we automatically turn it
// off.
deactivateUpdateLogFile();
/**
* Logs a string to the error console.
* @param string
* The string to write to the error console.
*/
function LOG(string) {
logPrefixedString("AUS:LOG", string);
}
export const UpdateLog = {
logPrefixedString,
get enabled() {
return gLogEnabled;
},
get logFileEnabled() {
return gLogFileEnabled;
},
/**
* Adds a callback function to be called when `UpdateLog.enabled` or
* `UpdateLog.logFileEnabled` change values.
*
* Adding listeners here is preferable to adding pref listeners to the
* underlying prefs both because it keeps callers out of the implementation
* details and because this file also uses those listeners. Since it's hard to
* guarantee what order the listeners run in, the actual logging behavior may
* not have changed yet when another pref listener is invoked.
*
* @param listener
* The callback function that will be called when the configuration
* changes. It will be called with no arguments.
*/
addConfigChangeListener(listener) {
gChangeListeners.push(listener);
},
};