diff options
Diffstat (limited to 'toolkit/mozapps/update/UpdateLog.sys.mjs')
-rw-r--r-- | toolkit/mozapps/update/UpdateLog.sys.mjs | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/UpdateLog.sys.mjs b/toolkit/mozapps/update/UpdateLog.sys.mjs new file mode 100644 index 0000000000..d9b13aac66 --- /dev/null +++ b/toolkit/mozapps/update/UpdateLog.sys.mjs @@ -0,0 +1,206 @@ +/* -*- 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, { + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", +}); + +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); + }, +}; |