UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

867 lines (744 loc) 25.9 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * John Spackman (john.spackman@zenesis.com) ************************************************************************ */ /** * Main qooxdoo logging class. * * Used as central logging feature by qx.core.Object. * * Extremely modular and lightweight to support logging at bootstrap and * at shutdown as well. * * * Supports dynamic appenders to push the output to the user * * Supports buffering of the last 50 messages (configurable) * * Supports different debug levels ("debug", "info", "warn" or "error") * * Simple data serialization for incoming messages * * Typical use of this class is via qx.core.MLogging which is included into most * classes, so you would use "this.debug(...)" etc, but qx.log.Logger.debug(), * .warn(), .error(), .info(), and .trace() can be used directly for static code. * * The first parameter is expected to be the context object, ie the object which * is sending the log; this can be null but that will prevent the filtering from * filtering on class name so ideally it will be a real qx.core.Object derived * object. Other parameters are any Javascript object which will be serialized * into the log message * * <pre class="javascript"> * qx.log.Logger.warn(myObject, "This is a message to log", myParam, otherData); * </pre> * * * The output of logging is controlled by "appenders", which are classes that * accept a log message and output it somehow (see examples in qx.log.appender.*); * typical examples are qx.log.appender.Console which outputs to the browser * console, or qx.log.appender.Native which outputs messages into a popup * window as part of your Qooxdoo UI. * * While it's quick and easy to add logging calls to code as and when you need it, * it is often convenient to control which logging calls output messages at runtime * rather than having to edit code. @see qx.log.Logger#addFilter * * @require(qx.dev.StackTrace) */ qx.Bootstrap.define("qx.log.Logger", { statics : { /* --------------------------------------------------------------------------- CONFIGURATION --------------------------------------------------------------------------- */ __level : "debug", /** * Configures the minimum log level required for new messages. * * @param value {String} One of "debug", "info", "warn" or "error". */ setLevel : function(value) { this.__level = value; }, /** * Returns the currently configured minimum log level required for new * messages. * * @return {Integer} Debug level */ getLevel : function() { return this.__level; }, /** * Configures the number of messages to be kept in the buffer. * * @param value {Integer} Any positive integer */ setTreshold : function(value) { this.__buffer.setMaxMessages(value); }, /** * Returns the currently configured number of messages to be kept in the * buffer. * * @return {Integer} Treshold value */ getTreshold : function() { return this.__buffer.getMaxMessages(); }, /* --------------------------------------------------------------------------- APPENDER MANAGEMENT --------------------------------------------------------------------------- */ /** @type {Map} Map of all known appenders by ID */ __appenders : [], /** @type {Map} Map of all known appenders by name */ __appendersByName: {}, /** @type {Array} Array of filters to apply when selecting appenders to append to */ __filters: [], /** @type {Integer} Last free appender ID */ __id : 0, /** * Registers the given appender and inserts the last cached messages. * * @param appender {Class} A static appender class supporting at * least a <code>process()</code> method to handle incoming messages. */ register : function(appender) { if (appender.$$id) { return; } // Register appender var id = this.__id++; this.__appenders[id] = appender; this.__appendersByName[appender.classname] = appender; appender.$$id = id; // Insert previous messages var entries = this.__buffer.getAllLogEvents(); for (var i=0, l=entries.length; i<l; i++) { var entry = entries[i]; var appenders = this.__getAppenders(entry.loggerName, entry.level); if (appenders[appender.classname]) { appender.process(entry); } } }, /** * Unregisters the given appender * * @param appender {Class} A static appender class */ unregister : function(appender) { var id = appender.$$id; if (id == null) { return; } delete this.__appendersByName[appender.classname]; delete this.__appenders[id]; delete appender.$$id; }, /** * Adds a filter that specifies the appenders to use for a given logger name (classname). * * By default, every log entry is output to all appenders but you can change this * behaviour by calling qx.log.Logger.addFilter; every log message is associated * with a class and a logging level (ie debug, warn, info, error, etc) and you can * apply a filter on either one. * * For example, to restrict the output to only allow qx.ui.* classes to output debug * logging information you would use this: * * <pre class="javascript"> * qx.log.Logger.addFilter(/^qx\.ui/, null, "debug"); * </pre> * * Note that while the default is to log everything, as soon as you apply one filter * you are specifying an exhaustive list of classes; so if you use the above example, * the ONLY classes that will be able to log is qx.ui.*. If you want to use multiple * classes to the output, just add more addFilter calls. * * The logging level (eg "debug", "error", etc) is greater than or equal to - so in * the above example, debug, error, warn, and info will be output but trace will not. * * The second parameter to addFilter is the classname of the appender to use; this * allows you to specify that log messages only go to one destination; for example: * * <pre class="javascript"> * qx.log.Logger.addFilter(/^qx\.ui/, "qx.log.appender.Console", "warn"); * qx.log.Logger.addFilter(/^qx\.io/, "qx.log.appender.Native", "debug"); * qx.log.Logger.addFilter(/^qx\.io/, "qx.log.appender.Console", "error"); * </pre> * * In this example, qx.ui.* will only go to the Console appender and only if a warning * is issued; qx.io.* will go to Native for debug, error, warn, and info and to * Console for error, warn, and info * * @param logger {String|RegExp} the pattern to match in the logger name * @param appenderName {String?} the name of the appender class, if undefined then all appenders * @param level {String?} the minimum logging level to use the appender, if undefined the default level is used */ addFilter: function(logger, appenderName, level) { if (typeof logger == "string") { logger = new RegExp(logger); } this.__filters.push({ loggerMatch: logger, level: level||this.__level, appenderName: appenderName }); }, /** * Reset all filters */ resetFilters: function() { this.__filters = []; }, /* --------------------------------------------------------------------------- USER METHODS --------------------------------------------------------------------------- */ /** * Sending a message at level "debug" to the logger. * * @param object {Object} Contextual object (either instance or static class) * @param message {var} Any number of arguments supported. An argument may * have any JavaScript data type. All data is serialized immediately and * does not keep references to other objects. */ debug : function(object, message) { qx.log.Logger.__log("debug", arguments); }, /** * Sending a message at level "info" to the logger. * * @param object {Object} Contextual object (either instance or static class) * @param message {var} Any number of arguments supported. An argument may * have any JavaScript data type. All data is serialized immediately and * does not keep references to other objects. */ info : function(object, message) { qx.log.Logger.__log("info", arguments); }, /** * Sending a message at level "warn" to the logger. * * @param object {Object} Contextual object (either instance or static class) * @param message {var} Any number of arguments supported. An argument may * have any JavaScript data type. All data is serialized immediately and * does not keep references to other objects. */ warn : function(object, message) { qx.log.Logger.__log("warn", arguments); }, /** * Sending a message at level "error" to the logger. * * @param object {Object} Contextual object (either instance or static class) * @param message {var} Any number of arguments supported. An argument may * have any JavaScript data type. All data is serialized immediately and * does not keep references to other objects. */ error : function(object, message) { qx.log.Logger.__log("error", arguments); }, /** * Prints the current stack trace at level "info" * * @param object {Object?} Contextual object (either instance or static class) * @param message {var} Any number of arguments supported. An argument may * have any JavaScript data type. All data is serialized immediately and * does not keep references to other objects. */ trace : function(object, message) { if (qx.log.Logger.isLoggerEnabled("trace", object)) { var trace = qx.dev.StackTrace.getStackTrace(); var args = qx.lang.Array.fromArguments(arguments); args.push(trace.join("\n")); qx.log.Logger.__log("trace", args); } }, /** * Prints a method deprecation warning and a stack trace if the setting * <code>qx.debug</code> is set to <code>true</code>. * * @param fcn {Function} reference to the deprecated function. This is * arguments.callee if the calling method is to be deprecated. * @param msg {String?} Optional message to be printed. */ deprecatedMethodWarning : function(fcn, msg) { if (qx.core.Environment.get("qx.debug")) { var functionName = qx.lang.Function.getName(fcn); this.warn( "The method '"+ functionName + "' is deprecated: " + (msg || "Please consult the API documentation of this method for alternatives.") ); this.trace(); } }, /** * Prints a class deprecation warning and a stack trace if the setting * <code>qx.debug</code> is set to <code>true</code>. * * @param clazz {Class} reference to the deprecated class. * @param msg {String?} Optional message to be printed. */ deprecatedClassWarning : function(clazz, msg) { if (qx.core.Environment.get("qx.debug")) { var className = clazz.classname || "unknown"; this.warn( "The class '"+className+"' is deprecated: " + (msg || "Please consult the API documentation of this class for alternatives.") ); this.trace(); } }, /** * Prints an event deprecation warning and a stack trace if the setting * <code>qx.debug</code> is set to <code>true</code>. * * @param clazz {Class} reference to the deprecated class. * @param event {String} deprecated event name. * @param msg {String?} Optional message to be printed. */ deprecatedEventWarning : function(clazz, event, msg) { if (qx.core.Environment.get("qx.debug")) { var className = clazz.self ? clazz.self.classname : "unknown"; this.warn( "The event '"+(event || "unknown")+"' from class '"+className+"' is deprecated: " + (msg || "Please consult the API documentation of this class for alternatives.") ); this.trace(); } }, /** * Prints a mixin deprecation warning and a stack trace if the setting * <code>qx.debug</code> is set to <code>true</code>. * * @param clazz {Class} reference to the deprecated mixin. * @param msg {String?} Optional message to be printed. */ deprecatedMixinWarning : function(clazz, msg) { if (qx.core.Environment.get("qx.debug")) { var mixinName = clazz ? clazz.name : "unknown"; this.warn( "The mixin '"+mixinName+"' is deprecated: " + (msg || "Please consult the API documentation of this class for alternatives.") ); this.trace(); } }, /** * Prints a constant deprecation warning and a stacktrace if the setting * <code>qx.debug</code> is set to <code>true</code> AND the browser supports * __defineGetter__! * * @param clazz {Class} The class the constant is attached to. * @param constant {String} The name of the constant as string. * @param msg {String} Optional message to be printed. */ deprecatedConstantWarning : function(clazz, constant, msg) { if (qx.core.Environment.get("qx.debug")) { // check if __defineGetter__ is available if (clazz.__defineGetter__) { var self = this; var constantValue = clazz[constant]; clazz.__defineGetter__(constant, function() { self.warn( "The constant '"+ constant + "' is deprecated: " + (msg || "Please consult the API documentation for alternatives.") ); self.trace(); return constantValue; }); } } }, /** * Prints a deprecation warning and a stacktrace when a subclass overrides * the passed method name. The deprecation is only printed if the setting * <code>qx.debug</code> is set to <code>true</code>. * * * @param object {qx.core.Object} Instance to check for overriding. * @param baseclass {Class} The baseclass as starting point. * @param methodName {String} The method name which is deprecated for overriding. * @param msg {String?} Optional message to be printed. */ deprecateMethodOverriding : function(object, baseclass, methodName, msg) { if (qx.core.Environment.get("qx.debug")) { var clazz = object.constructor; while(clazz.classname !== baseclass.classname) { if (clazz.prototype.hasOwnProperty(methodName)) { this.warn( "The method '" + qx.lang.Function.getName(object[methodName]) + "' overrides a deprecated method: " + (msg || "Please consult the API documentation for alternatives.") ); this.trace(); break; } clazz = clazz.superclass; } } }, /** * Deletes the current buffer. Does not influence message handling of the * connected appenders. * */ clear : function() { this.__buffer.clearHistory(); }, /* --------------------------------------------------------------------------- INTERNAL LOGGING IMPLEMENTATION --------------------------------------------------------------------------- */ /** @type {qx.log.appender.RingBuffer} Message buffer of previously fired messages. */ __buffer : new qx.log.appender.RingBuffer(50), /** @type {Map} Numeric translation of log levels */ __levels : { trace: 0, debug : 1, info : 2, warn : 3, error : 4 }, /** @type {Map} cache of appenders for a given logger and level */ __appendersCache: {}, /** * Detects the name of the logger to use for an object * * @param object {Object} Contextual object (either instance or static class) * @return {String} Logger name */ __getLoggerName: function(object) { if (object) { if (object.classname) { return object.classname; } if (typeof object == "string") { return object; } } return "[default]"; }, /** * Detects whether a logger level is enabled for an object * * @param level {String} One of "trace", "debug", "info", "warn" or "error" * @param object {Object} Contextual object (either instance or static class) * @return {Boolean} True if the logger is enabled */ isLoggerEnabled: function(level, object) { var loggerName = this.__getLoggerName(object); var appenders = this.__getAppenders(loggerName, level); return !!Object.keys(appenders).length; }, /** * Internal logging main routine. * * @param level {String} One of "trace", "debug", "info", "warn" or "error" * @param args {Array} List of other arguments, where the first is * taken as the context object. */ __log : function(level, args) { // Get object and determine appenders var object = args.length < 2 ? null : args[0]; var loggerName = this.__getLoggerName(object); var appenders = this.__getAppenders(loggerName, level); if (!Object.keys(appenders).length) { return; } // Serialize and cache var start = object ? 1 : 0; var items = []; for (var i=start, l=args.length; i<l; i++) { items.push(this.__serialize(args[i], true)); } // Build entry var time = new Date; var entry = { time : time, offset : time-qx.Bootstrap.LOADSTART, level: level, loggerName: loggerName, items: items, // store window to allow cross frame logging win: window }; // Add relation fields if (object) { // Do not explicitly check for instanceof qx.core.Object, in order not // to introduce an unwanted load-time dependency if (object.$$hash !== undefined) { entry.object = object.$$hash; } if (object.$$type) { entry.clazz = object; } else if (object.constructor) { entry.clazz = object.constructor; } } this.__buffer.process(entry); // Send to appenders for (var classname in appenders) { appenders[classname].process(entry); } }, /** * Finds the appenders for a given classname * * @param className {String} Name of the class * @param level {String} the minimum logging level to use the appender * @return {Array} list of appenders */ __getAppenders: function(className, level) { var levels = this.__levels; // If no filters, then all appenders apply if (!this.__filters.length) { // Check the default level if (levels[level] < levels[this.__level]) { return []; } return this.__appendersByName; } // Check the cache var cacheId = className + "|" + level; var appenders = this.__appendersCache[cacheId]; if (appenders !== undefined) { return appenders; } var appenders = {}; for (var i = 0; i < this.__filters.length; i++) { var filter = this.__filters[i]; // Filters only apply to certain levels if (levels[level] < levels[filter.level]) { continue; } // No duplicates if (filter.appenderName && appenders[filter.appenderName]) { continue; } // Test if (!filter.loggerMatch || filter.loggerMatch.test(className)) { if (filter.appenderName) { appenders[filter.appenderName] = this.__appendersByName[filter.appenderName]; } else { return this.__appendersCache[cacheId] = this.__appendersByName; } } } return this.__appendersCache[cacheId] = appenders; }, /** * Detects the type of the variable given. * * @param value {var} Incoming value * @return {String} Type of the incoming value. Possible values: * "undefined", "null", "boolean", "number", "string", * "function", "array", "error", "map", * "class", "instance", "node", "stringify", "unknown" */ __detect : function(value) { if (value === undefined) { return "undefined"; } else if (value === null) { return "null"; } if (value.$$type) { return "class"; } var type = typeof value; if (type === "function" || type == "string" || type === "number" || type === "boolean") { return type; } else if (type === "object") { if (value.nodeType) { return "node"; // In Gecko, DOMException doesn't inherit from Error } else if (value instanceof Error || (value.name && value.message)) { return "error"; } else if (value.classname) { return "instance"; } else if (value instanceof Array) { return "array"; } else if (value instanceof Date) { return "date"; } else { return "map"; } } if (value.toString) { return "stringify"; } return "unknown"; }, /** * Serializes the incoming value. If it is a singular value, the result is * a simple string. For an array or a map the result can also be a * serialized string of a limited number of individual items. * * @param value {var} Incoming value * @param deep {Boolean?false} Whether arrays and maps should be * serialized for a limited number of items * @return {Map} Contains the keys <code>type</code>, <code>text</code> and * <code>trace</code>. */ __serialize : function(value, deep) { var type = this.__detect(value); var text = "unknown"; var trace = []; switch(type) { case "null": case "undefined": text = type; break; case "string": case "number": case "boolean": case "date": text = value; break; case "node": if (value.nodeType === 9) { text = "document"; } else if (value.nodeType === 3) { text = "text[" + value.nodeValue + "]"; } else if (value.nodeType === 1) { text = value.nodeName.toLowerCase(); if (value.id) { text += "#" + value.id; } } else { text = "node"; } break; case "function": text = qx.lang.Function.getName(value) || type; break; case "instance": text = value.basename + "[" + value.$$hash + "]"; break; case "class": case "stringify": text = value.toString(); break; case "error": trace = qx.dev.StackTrace.getStackTraceFromError(value); text = (value.basename ? value.basename + ": " : "") + value.toString(); break; case "array": if (deep) { text = []; for (var i=0, l=value.length; i<l; i++) { if (text.length > 20) { text.push("...(+" + (l-i) + ")"); break; } text.push(this.__serialize(value[i], false)); } } else { text = "[...(" + value.length + ")]"; } break; case "map": if (deep) { var temp; // Produce sorted key list var sorted = []; for (var key in value) { sorted.push(key); } sorted.sort(); // Temporary text list text = []; for (var i=0, l=sorted.length; i<l; i++) { if (text.length > 20) { text.push("...(+" + (l-i) + ")"); break; } // Additional storage of hash-key key = sorted[i]; temp = this.__serialize(value[key], false); temp.key = key; text.push(temp); } } else { var number=0; for (var key in value) { number++; } text = "{...(" + number + ")}"; } break; } return { type : type, text : text, trace : trace }; } }, defer : function(statics) { var logs = qx.Bootstrap.$$logs; for (var i=0; i<logs.length; i++) { statics.__log(logs[i][0], logs[i][1]); } qx.Bootstrap.debug = statics.debug; qx.Bootstrap.info = statics.info; qx.Bootstrap.warn = statics.warn; qx.Bootstrap.error = statics.error; qx.Bootstrap.trace = statics.trace; } });