# HG changeset patch # User Myk Melez # Date 1201225618 28800 # Node ID fba2b2c6ef1963b22a2fd07ee685956371102bde # Parent fb8c8fbdcfe5f1f1e9ed956dcfeeddc85db9b7cd log4moz from the weave project for better logging diff -r fb8c8fbdcfe5 -r fba2b2c6ef19 extension/modules/log4moz.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extension/modules/log4moz.js Thu Jan 24 17:46:58 2008 -0800 @@ -0,0 +1,499 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is log4moz + * + * The Initial Developer of the Original Code is + * Michael Johnston + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Michael Johnston + * Dan Mills + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const EXPORTED_SYMBOLS = ['Log4Moz']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const MODE_RDONLY = 0x01; +const MODE_WRONLY = 0x02; +const MODE_CREATE = 0x08; +const MODE_APPEND = 0x10; +const MODE_TRUNCATE = 0x20; + +const PERMS_FILE = 0644; +const PERMS_DIRECTORY = 0755; + +const ONE_BYTE = 1; +const ONE_KILOBYTE = 1024 * ONE_BYTE; +const ONE_MEGABYTE = 1024 * ONE_KILOBYTE; + +const Log4Moz = {}; +Log4Moz.Level = {}; +Log4Moz.Level.Fatal = 70; +Log4Moz.Level.Error = 60; +Log4Moz.Level.Warn = 50; +Log4Moz.Level.Info = 40; +Log4Moz.Level.Config = 30; +Log4Moz.Level.Debug = 20; +Log4Moz.Level.Trace = 10; +Log4Moz.Level.All = 0; + +Log4Moz.Level.Desc = { + 70: "FATAL", + 60: "ERROR", + 50: "WARN", + 40: "INFO", + 30: "CONFIG", + 20: "DEBUG", + 10: "TRACE", + 0: "ALL" +}; + +/* + * LogMessage + * Encapsulates a single log event's data + */ +function LogMessage(loggerName, level, message){ + this.loggerName = loggerName; + this.message = message; + this.level = level; + this.time = Date.now(); +} +LogMessage.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + + get levelDesc() { + if (this.level in Log4Moz.Level.Desc) + return Log4Moz.Level.Desc[this.level]; + return "UNKNOWN"; + }, + + toString: function LogMsg_toString(){ + return "LogMessage [" + this._date + " " + this.level + " " + + this.message + "]"; + } +}; + +/* + * Logger + * Hierarchical version. Logs to all appenders, assigned or inherited + */ + +function Logger(name, repository) { + this._name = name; + this._repository = repository; + this._appenders = []; +} +Logger.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + + parent: null, + + _level: null, + get level() { + if (this._level != null) + return this._level; + if (this.parent) + return this.parent.level; + dump("log4moz warning: root logger configuration error: no level defined\n"); + return Log4Moz.Level.All; + }, + set level(level) { + this._level = level; + }, + + _appenders: null, + get appenders() { + if (!this.parent) + return this._appenders; + return this._appenders.concat(this.parent.appenders); + }, + + addAppender: function Logger_addAppender(appender) { + for (let i = 0; i < this._appenders.length; i++) { + if (this._appenders[i] == appender) + return; + } + this._appenders.push(appender); + }, + + log: function Logger_log(message) { + if (this.level > message.level) + return; + let appenders = this.appenders; + for (let i = 0; i < appenders.length; i++){ + appenders[i].append(message); + } + }, + + fatal: function Logger_fatal(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Fatal, string)); + }, + error: function Logger_error(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Error, string)); + }, + warn: function Logger_warn(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Warn, string)); + }, + info: function Logger_info(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Info, string)); + }, + config: function Logger_config(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Config, string)); + }, + debug: function Logger_debug(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Debug, string)); + }, + trace: function Logger_trace(string) { + this.log(new LogMessage(this._name, Log4Moz.Level.Trace, string)); + } +}; + +/* + * LoggerRepository + * Implements a hierarchy of Loggers + */ + +function LoggerRepository() {} +LoggerRepository.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + + _loggers: {}, + + _rootLogger: null, + get rootLogger() { + if (!this._rootLogger) { + this._rootLogger = new Logger("root", this); + this._rootLogger.level = Log4Moz.Level.All; + } + return this._rootLogger; + }, + // FIXME: need to update all parent values if we do this + //set rootLogger(logger) { + // this._rootLogger = logger; + //}, + + _updateParents: function LogRep__updateParents(name) { + let pieces = name.split('.'); + let cur, parent; + + // find the closest parent + for (let i = 0; i < pieces.length; i++) { + if (cur) + cur += '.' + pieces[i]; + else + cur = pieces[i]; + if (cur in this._loggers) + parent = cur; + } + + // if they are the same it has no parent + if (parent == name) + this._loggers[name].parent = this.rootLogger; + else + this._loggers[name].parent = this._loggers[parent]; + + // trigger updates for any possible descendants of this logger + for (let logger in this._loggers) { + if (logger != name && name.indexOf(logger) == 0) + this._updateParents(logger); + } + }, + + getLogger: function LogRep_getLogger(name) { + if (!(name in this._loggers)) { + this._loggers[name] = new Logger(name, this); + this._updateParents(name); + } + return this._loggers[name]; + } +}; + +/* + * Formatters + * These massage a LogMessage into whatever output is desired + * Only the BasicFormatter is currently implemented + */ + +// Abstract formatter +function Formatter() {} +Formatter.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + format: function Formatter_format(message) {} +}; + +// FIXME: should allow for formatting the whole string, not just the date +function BasicFormatter(dateFormat) { + if (dateFormat) + this.dateFormat = dateFormat; +} +BasicFormatter.prototype = { + _dateFormat: null, + + get dateFormat() { + if (!this._dateFormat) + this._dateFormat = "%Y-%m-%d %H:%M:%S"; + return this._dateFormat; + }, + + set dateFormat(format) { + this._dateFormat = format; + }, + + format: function BF_format(message) { + let date = new Date(message.time); + return date.toLocaleFormat(this.dateFormat) + "\t" + + message.loggerName + "\t" + message.levelDesc + "\t" + + message.message + "\n"; + } +}; +BasicFormatter.prototype.__proto__ = new Formatter(); + +/* + * Appenders + * These can be attached to Loggers to log to different places + * Simply subclass and override doAppend to implement a new one + */ + +function Appender(formatter) { + this._name = "Appender"; + this._formatter = formatter? formatter : new BasicFormatter(); +} +Appender.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + + _level: Log4Moz.Level.All, + get level() { return this._level; }, + set level(level) { this._level = level; }, + + append: function App_append(message) { + if(this._level <= message.level) + this.doAppend(this._formatter.format(message)); + }, + toString: function App_toString() { + return this._name + " [level=" + this._level + + ", formatter=" + this._formatter + "]"; + }, + doAppend: function App_doAppend(message) {} +}; + +/* + * DumpAppender + * Logs to standard out + */ + +function DumpAppender(formatter) { + this._name = "DumpAppender"; + this._formatter = formatter; +} +DumpAppender.prototype = { + doAppend: function DApp_doAppend(message) { + dump(message); + } +}; +DumpAppender.prototype.__proto__ = new Appender(); + +/* + * ConsoleAppender + * Logs to the javascript console + */ + +function ConsoleAppender(formatter) { + this._name = "ConsoleAppender"; + this._formatter = formatter; +} +ConsoleAppender.prototype = { + doAppend: function CApp_doAppend(message) { + if (message.level > Log4Moz.Level.Warn) { + Cu.reportError(message); + return; + } + Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService).logStringMessage(message); + } +}; +ConsoleAppender.prototype.__proto__ = new Appender(); + +/* + * FileAppender + * Logs to a file + */ + +function FileAppender(file, formatter) { + this._name = "FileAppender"; + this._file = file; // nsIFile + this._formatter = formatter; +} +FileAppender.prototype = { + __fos: null, + get _fos() { + if (!this.__fos) + this.openStream(); + return this.__fos; + }, + + openStream: function FApp_openStream() { + this.__fos = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + let flags = MODE_WRONLY | MODE_CREATE | MODE_APPEND; + this.__fos.init(this._file, flags, PERMS_FILE, 0); + }, + + closeStream: function FApp_closeStream() { + if (!this.__fos) + return; + try { + this.__fos.close(); + this.__fos = null; + } catch(e) { + dump("Failed to close file output stream\n" + e); + } + }, + + doAppend: function FApp_doAppend(message) { + if (message === null || message.length <= 0) + return; + try { + this._fos().write(message, message.length); + } catch(e) { + dump("Error writing file:\n" + e); + } + } +}; +FileAppender.prototype.__proto__ = new Appender(); + +/* + * RotatingFileAppender + * Similar to FileAppender, but rotates logs when they become too large + */ + +function RotatingFileAppender(file, formatter, maxSize, maxBackups) { + this._name = "RotatingFileAppender"; + this._file = file; // nsIFile + this._formatter = formatter; + this._maxSize = maxSize; + this._maxBackups = maxBackups; +} +RotatingFileAppender.prototype = { + doAppend: function RFApp_doAppend(message) { + if (message === null || message.length <= 0) + return; + try { + this.rotateLogs(); + this._fos.write(message, message.length); + } catch(e) { + dump("Error writing file:\n" + e); + } + }, + rotateLogs: function RFApp_rotateLogs() { + if(this._file.exists() && + this._file.fileSize < this._maxSize) + return; + + this.closeStream(); + + for (let i = this.maxBackups - 1; i > 0; i--){ + let backup = this._file.parent.clone(); + backup.append(this._file.leafName + "." + i); + if (backup.exists()) + backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1)); + } + + let cur = this._file.clone(); + if (cur.exists()) + cur.moveTo(cur.parent, cur.leafName + ".1"); + + // Note: this._file still points to the same file + } +}; +RotatingFileAppender.prototype.__proto__ = new FileAppender(); + +/* + * LoggingService + */ + +function Log4MozService() { + this._repository = new LoggerRepository(); +} +Log4MozService.prototype = { + //classDescription: "Log4moz Logging Service", + //contractID: "@mozilla.org/log4moz/service;1", + //classID: Components.ID("{a60e50d7-90b8-4a12-ad0c-79e6a1896978}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), + + get rootLogger() { + return this._repository.rootLogger; + }, + + getLogger: function LogSvc_getLogger(name) { + return this._repository.getLogger(name); + }, + + newAppender: function LogSvc_newAppender(kind, formatter) { + switch (kind) { + case "dump": + return new DumpAppender(formatter); + case "console": + return new ConsoleAppender(formatter); + default: + dump("log4moz: unknown appender kind: " + kind); + return; + } + }, + + newFileAppender: function LogSvc_newAppender(kind, file, formatter) { + switch (kind) { + case "file": + return new FileAppender(file, formatter); + case "rotating": + // FIXME: hardcoded constants + return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 5, 0); + default: + dump("log4moz: unknown appender kind: " + kind); + return; + } + }, + + newFormatter: function LogSvc_newFormatter(kind) { + switch (kind) { + case "basic": + return new BasicFormatter(); + default: + dump("log4moz: unknown formatter kind: " + kind); + return; + } + } +}; + +Log4Moz.Service = new Log4MozService();