diff options
Diffstat (limited to 'components/weave/src/common/logmanager.js')
-rw-r--r-- | components/weave/src/common/logmanager.js | 331 |
1 files changed, 0 insertions, 331 deletions
diff --git a/components/weave/src/common/logmanager.js b/components/weave/src/common/logmanager.js deleted file mode 100644 index c501229a9..000000000 --- a/components/weave/src/common/logmanager.js +++ /dev/null @@ -1,331 +0,0 @@ -/* 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/. */ -"use strict;" - -var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Log", - "resource://gre/modules/Log.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", - "resource://gre/CommonUtils.jsm"); - -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); - -this.EXPORTED_SYMBOLS = [ - "LogManager", -]; - -const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days - -// "shared" logs (ie, where the same log name is used by multiple LogManager -// instances) are a fact of life here - eg, FirefoxAccounts logs are used by -// both Sync and Reading List. -// However, different instances have different pref branches, so we need to -// handle when one pref branch says "Debug" and the other says "Error" -// So we (a) keep singleton console and dump appenders and (b) keep track -// of the minimum (ie, most verbose) level and use that. -// This avoids (a) the most recent setter winning (as that is indeterminate) -// and (b) multiple dump/console appenders being added to the same log multiple -// times, which would cause messages to appear twice. - -// Singletons used by each instance. -var formatter; -var dumpAppender; -var consoleAppender; - -// A set of all preference roots used by all instances. -var allBranches = new Set(); - -// 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); - this.sawError = false; -} - -FlushableStorageAppender.prototype = { - __proto__: Log.StorageStreamAppender.prototype, - - append(message) { - if (message.level >= Log.Level.Error) { - this.sawError = true; - } - Log.StorageStreamAppender.prototype.append.call(this, message); - }, - - reset() { - Log.StorageStreamAppender.prototype.reset.call(this); - this.sawError = false; - }, - - // Flush the current stream to a file. Somewhat counter-intuitively, you - // must pass a log which will be written to with details of the operation. - flushToFile: Task.async(function* (subdirArray, filename, log) { - let inStream = this.getInputStream(); - this.reset(); - if (!inStream) { - log.debug("Failed to flush log to a file - no input stream"); - return; - } - log.debug("Flushing file log"); - log.trace("Beginning stream copy to " + filename + ": " + Date.now()); - try { - yield this._copyStreamToFile(inStream, subdirArray, filename, log); - log.trace("onCopyComplete", Date.now()); - } catch (ex) { - log.error("Failed to copy log stream to file", ex); - } - }), - - /** - * Copy an input stream to the named file, doing everything off the main - * thread. - * subDirArray is an array of path components, relative to the profile - * directory, where the file will be created. - * outputFileName is the filename to create. - * Returns a promise that is resolved on completion or rejected with an error. - */ - _copyStreamToFile: Task.async(function* (inputStream, subdirArray, outputFileName, log) { - // The log data could be large, so we don't want to pass it all in a single - // message, so use BUFFER_SIZE chunks. - const BUFFER_SIZE = 8192; - - // get a binary stream - let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream); - binaryStream.setInputStream(inputStream); - - let outputDirectory = OS.Path.join(OS.Constants.Path.profileDir, ...subdirArray); - yield OS.File.makeDir(outputDirectory, { ignoreExisting: true, from: OS.Constants.Path.profileDir }); - let fullOutputFileName = OS.Path.join(outputDirectory, outputFileName); - let output = yield OS.File.open(fullOutputFileName, { write: true} ); - try { - while (true) { - let available = binaryStream.available(); - if (!available) { - break; - } - let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE)); - yield output.write(new Uint8Array(chunk)); - } - } finally { - try { - binaryStream.close(); // inputStream is closed by the binaryStream - yield output.close(); - } catch (ex) { - log.error("Failed to close the input stream", ex); - } - } - log.trace("finished copy to", fullOutputFileName); - }), -} - -// The public LogManager object. -function LogManager(prefRoot, logNames, logFilePrefix) { - this._prefObservers = []; - this.init(prefRoot, logNames, logFilePrefix); -} - -LogManager.prototype = { - _cleaningUpFileLogs: false, - - init(prefRoot, logNames, logFilePrefix) { - if (prefRoot instanceof Preferences) { - this._prefs = prefRoot; - } else { - this._prefs = new Preferences(prefRoot); - } - - this.logFilePrefix = logFilePrefix; - if (!formatter) { - // Create a formatter and various appenders to attach to the logs. - formatter = new Log.BasicFormatter(); - consoleAppender = new Log.ConsoleAppender(formatter); - dumpAppender = new Log.DumpAppender(formatter); - } - - allBranches.add(this._prefs._branchStr); - // We create a preference observer for all our prefs so they are magically - // reflected if the pref changes after creation. - let setupAppender = (appender, prefName, defaultLevel, findSmallest = false) => { - let observer = newVal => { - let level = Log.Level[newVal] || defaultLevel; - if (findSmallest) { - // As some of our appenders have global impact (ie, there is only one - // place 'dump' goes to), we need to find the smallest value from all - // prefs controlling this appender. - // For example, if consumerA has dump=Debug then consumerB sets - // dump=Error, we need to keep dump=Debug so consumerA is respected. - for (let branch of allBranches) { - let lookPrefBranch = new Preferences(branch); - let lookVal = Log.Level[lookPrefBranch.get(prefName)]; - if (lookVal && lookVal < level) { - level = lookVal; - } - } - } - appender.level = level; - } - this._prefs.observe(prefName, observer, this); - this._prefObservers.push([prefName, observer]); - // and call the observer now with the current pref value. - observer(this._prefs.get(prefName)); - return observer; - } - - this._observeConsolePref = setupAppender(consoleAppender, "log.appender.console", Log.Level.Fatal, true); - this._observeDumpPref = setupAppender(dumpAppender, "log.appender.dump", Log.Level.Error, true); - - // The file appender doesn't get the special singleton behaviour. - let fapp = this._fileAppender = new FlushableStorageAppender(formatter); - // the stream gets a default of Debug as the user must go out of their way - // to see the stuff spewed to it. - this._observeStreamPref = setupAppender(fapp, "log.appender.file.level", Log.Level.Debug); - - // now attach the appenders to all our logs. - for (let logName of logNames) { - let log = Log.repository.getLogger(logName); - for (let appender of [fapp, dumpAppender, consoleAppender]) { - log.addAppender(appender); - } - } - // and use the first specified log as a "root" for our log. - this._log = Log.repository.getLogger(logNames[0] + ".LogManager"); - }, - - /** - * Cleanup this instance - */ - finalize() { - for (let [name, pref] of this._prefObservers) { - this._prefs.ignore(name, pref, this); - } - this._prefObservers = []; - try { - allBranches.delete(this._prefs._branchStr); - } catch (e) {} - this._prefs = null; - }, - - get _logFileSubDirectoryEntries() { - // At this point we don't allow a custom directory for the logs, nor allow - // it to be outside the profile directory. - // This returns an array of the the relative directory entries below the - // profile dir, and is the directory about:sync-log uses. - return ["weave", "logs"]; - }, - - get sawError() { - return this._fileAppender.sawError; - }, - - // Result values for resetFileLog. - SUCCESS_LOG_WRITTEN: "success-log-written", - ERROR_LOG_WRITTEN: "error-log-written", - - /** - * Possibly generate a log file for all accumulated log messages and refresh - * the input & output streams. - * Whether a "success" or "error" log is written is determined based on - * whether an "Error" log entry was written to any of the logs. - * Returns a promise that resolves on completion with either null (for no - * file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was - * written, or ERROR_LOG_WRITTEN if an "error" log was written. - */ - resetFileLog: Task.async(function* () { - try { - let flushToFile; - let reasonPrefix; - let reason; - if (this._fileAppender.sawError) { - reason = this.ERROR_LOG_WRITTEN; - flushToFile = this._prefs.get("log.appender.file.logOnError", true); - reasonPrefix = "error"; - } else { - reason = this.SUCCESS_LOG_WRITTEN; - flushToFile = this._prefs.get("log.appender.file.logOnSuccess", false); - reasonPrefix = "success"; - } - - // might as well avoid creating an input stream if we aren't going to use it. - if (!flushToFile) { - this._fileAppender.reset(); - return null; - } - - // We have reasonPrefix at the start of the filename so all "error" - // logs are grouped in about:sync-log. - let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt"; - yield this._fileAppender.flushToFile(this._logFileSubDirectoryEntries, filename, this._log); - - // It's not completely clear to markh why we only do log cleanups - // for errors, but for now the Sync semantics have been copied... - // (one theory is that only cleaning up on error makes it less - // likely old error logs would be removed, but that's not true if - // there are occasional errors - let's address this later!) - if (reason == this.ERROR_LOG_WRITTEN && !this._cleaningUpFileLogs) { - this._log.trace("Scheduling cleanup."); - // Note we don't return/yield or otherwise wait on this promise - it - // continues in the background - this.cleanupLogs().catch(err => { - this._log.error("Failed to cleanup logs", err); - }); - } - return reason; - } catch (ex) { - this._log.error("Failed to resetFileLog", ex); - return null; - } - }), - - /** - * Finds all logs older than maxErrorAge and deletes them using async I/O. - */ - cleanupLogs: Task.async(function* () { - this._cleaningUpFileLogs = true; - let logDir = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries); - let iterator = new OS.File.DirectoryIterator(logDir.path); - let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE); - let threshold = Date.now() - 1000 * maxAge; - - this._log.debug("Log cleanup threshold time: " + threshold); - yield iterator.forEach(Task.async(function* (entry) { - // Note that we don't check this.logFilePrefix is in the name - we cleanup - // all files in this directory regardless of that prefix so old logfiles - // for prefixes no longer in use are still cleaned up. See bug 1279145. - if (!entry.name.startsWith("error-") && - !entry.name.startsWith("success-")) { - return; - } - try { - // need to call .stat() as the enumerator doesn't give that to us on *nix. - let info = yield OS.File.stat(entry.path); - if (info.lastModificationDate.getTime() >= threshold) { - return; - } - this._log.trace(" > Cleanup removing " + entry.name + - " (" + info.lastModificationDate.getTime() + ")"); - yield OS.File.remove(entry.path); - this._log.trace("Deleted " + entry.name); - } catch (ex) { - this._log.debug("Encountered error trying to clean up old log file " - + entry.name, ex); - } - }.bind(this))); - iterator.close(); - this._cleaningUpFileLogs = false; - this._log.debug("Done deleting files."); - // This notification is used only for tests. - Services.obs.notifyObservers(null, "services-tests:common:log-manager:cleanup-logs", null); - }), -} |