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