changeset 3:fba2b2c6ef19

log4moz from the weave project for better logging
author Myk Melez <myk@mozilla.org>
date Thu, 24 Jan 2008 17:46:58 -0800
parents fb8c8fbdcfe5
children ff671aec51c5
files extension/modules/log4moz.js
diffstat 1 files changed, 499 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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 <special.michael@gmail.com>
+ * Dan Mills <thunder@mozilla.com>
+ *
+ * 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();