Greasy Fork is available in English.
log4javascript is a logging framework for JavaScript based on log4j
此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.org/scripts/8159/37575/log4javascript.js
// ==UserScript== // @name log4javascript // @namespace Tim Down // @description log4javascript is a logging framework for JavaScript based on log4j // @copyright 2014 Tim Down // @version 1.4.11 // @source http://log4javascript.org // @license Apache // ==/UserScript== /** * Copyright 2014 Tim Down. * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * log4javascript * * log4javascript is a logging framework for JavaScript based on log4j * for Java. This file contains all core log4javascript code and is the only * file required to use log4javascript, unless you require support for * document.domain, in which case you will also need console.html, which must be * stored in the same directory as the main log4javascript.js file. * * Author: Tim Down <[email protected]> * Version: 1.4.11 * Edition: log4javascript * Build date: 19 February 2015 * Website: http://log4javascript.org */ (function(factory, root) { if (typeof define == "function" && define.amd) { // AMD. Register as an anonymous module. define(factory); } else if (typeof module != "undefined" && typeof exports == "object") { // Node/CommonJS style module.exports = factory(); } else { // No AMD or CommonJS support so we place log4javascript in (probably) the global variable root.log4javascript = factory(); } })(function() { // Array-related stuff. Next three methods are solely for IE5, which is missing them if (!Array.prototype.push) { Array.prototype.push = function() { for (var i = 0, len = arguments.length; i < len; i++){ this[this.length] = arguments[i]; } return this.length; }; } if (!Array.prototype.shift) { Array.prototype.shift = function() { if (this.length > 0) { var firstItem = this[0]; for (var i = 0, len = this.length - 1; i < len; i++) { this[i] = this[i + 1]; } this.length = this.length - 1; return firstItem; } }; } if (!Array.prototype.splice) { Array.prototype.splice = function(startIndex, deleteCount) { var itemsAfterDeleted = this.slice(startIndex + deleteCount); var itemsDeleted = this.slice(startIndex, startIndex + deleteCount); this.length = startIndex; // Copy the arguments into a proper Array object var argumentsArray = []; for (var i = 0, len = arguments.length; i < len; i++) { argumentsArray[i] = arguments[i]; } var itemsToAppend = (argumentsArray.length > 2) ? itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted; for (i = 0, len = itemsToAppend.length; i < len; i++) { this.push(itemsToAppend[i]); } return itemsDeleted; }; } /* ---------------------------------------------------------------------- */ function isUndefined(obj) { return typeof obj == "undefined"; } /* ---------------------------------------------------------------------- */ // Custom event support function EventSupport() {} EventSupport.prototype = { eventTypes: [], eventListeners: {}, setEventTypes: function(eventTypesParam) { if (eventTypesParam instanceof Array) { this.eventTypes = eventTypesParam; this.eventListeners = {}; for (var i = 0, len = this.eventTypes.length; i < len; i++) { this.eventListeners[this.eventTypes[i]] = []; } } else { handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array"); } }, addEventListener: function(eventType, listener) { if (typeof listener == "function") { if (!array_contains(this.eventTypes, eventType)) { handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'"); } this.eventListeners[eventType].push(listener); } else { handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function"); } }, removeEventListener: function(eventType, listener) { if (typeof listener == "function") { if (!array_contains(this.eventTypes, eventType)) { handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'"); } array_remove(this.eventListeners[eventType], listener); } else { handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function"); } }, dispatchEvent: function(eventType, eventArgs) { if (array_contains(this.eventTypes, eventType)) { var listeners = this.eventListeners[eventType]; for (var i = 0, len = listeners.length; i < len; i++) { listeners[i](this, eventType, eventArgs); } } else { handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'"); } } }; /* -------------------------------------------------------------------------- */ var applicationStartDate = new Date(); var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" + Math.floor(Math.random() * 100000000); var emptyFunction = function() {}; var newLine = "\r\n"; var pageLoaded = false; // Create main log4javascript object; this will be assigned public properties function Log4JavaScript() {} Log4JavaScript.prototype = new EventSupport(); var log4javascript = new Log4JavaScript(); log4javascript.version = "1.4.11"; log4javascript.edition = "log4javascript"; /* -------------------------------------------------------------------------- */ // Utility functions function toStr(obj) { if (obj && obj.toString) { return obj.toString(); } else { return String(obj); } } function getExceptionMessage(ex) { if (ex.message) { return ex.message; } else if (ex.description) { return ex.description; } else { return toStr(ex); } } // Gets the portion of the URL after the last slash function getUrlFileName(url) { var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\")); return url.substr(lastSlashIndex + 1); } // Returns a nicely formatted representation of an error function getExceptionStringRep(ex) { if (ex) { var exStr = "Exception: " + getExceptionMessage(ex); try { if (ex.lineNumber) { exStr += " on line number " + ex.lineNumber; } if (ex.fileName) { exStr += " in file " + getUrlFileName(ex.fileName); } } catch (localEx) { logLog.warn("Unable to obtain file and line information for error"); } if (showStackTraces && ex.stack) { exStr += newLine + "Stack trace:" + newLine + ex.stack; } return exStr; } return null; } function bool(obj) { return Boolean(obj); } function trim(str) { return str.replace(/^\s+/, "").replace(/\s+$/, ""); } function splitIntoLines(text) { // Ensure all line breaks are \n only var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); return text2.split("\n"); } var urlEncode = (typeof window.encodeURIComponent != "undefined") ? function(str) { return encodeURIComponent(str); }: function(str) { return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D"); }; function array_remove(arr, val) { var index = -1; for (var i = 0, len = arr.length; i < len; i++) { if (arr[i] === val) { index = i; break; } } if (index >= 0) { arr.splice(index, 1); return true; } else { return false; } } function array_contains(arr, val) { for(var i = 0, len = arr.length; i < len; i++) { if (arr[i] == val) { return true; } } return false; } function extractBooleanFromParam(param, defaultValue) { if (isUndefined(param)) { return defaultValue; } else { return bool(param); } } function extractStringFromParam(param, defaultValue) { if (isUndefined(param)) { return defaultValue; } else { return String(param); } } function extractIntFromParam(param, defaultValue) { if (isUndefined(param)) { return defaultValue; } else { try { var value = parseInt(param, 10); return isNaN(value) ? defaultValue : value; } catch (ex) { logLog.warn("Invalid int param " + param, ex); return defaultValue; } } } function extractFunctionFromParam(param, defaultValue) { if (typeof param == "function") { return param; } else { return defaultValue; } } function isError(err) { return (err instanceof Error); } if (!Function.prototype.apply){ Function.prototype.apply = function(obj, args) { var methodName = "__apply__"; if (typeof obj[methodName] != "undefined") { methodName += String(Math.random()).substr(2); } obj[methodName] = this; var argsStrings = []; for (var i = 0, len = args.length; i < len; i++) { argsStrings[i] = "args[" + i + "]"; } var script = "obj." + methodName + "(" + argsStrings.join(",") + ")"; var returnValue = eval(script); delete obj[methodName]; return returnValue; }; } if (!Function.prototype.call){ Function.prototype.call = function(obj) { var args = []; for (var i = 1, len = arguments.length; i < len; i++) { args[i - 1] = arguments[i]; } return this.apply(obj, args); }; } /* ---------------------------------------------------------------------- */ // Simple logging for log4javascript itself var logLog = { quietMode: false, debugMessages: [], setQuietMode: function(quietMode) { this.quietMode = bool(quietMode); }, numberOfErrors: 0, alertAllErrors: false, setAlertAllErrors: function(alertAllErrors) { this.alertAllErrors = alertAllErrors; }, debug: function(message) { this.debugMessages.push(message); }, displayDebug: function() { alert(this.debugMessages.join(newLine)); }, warn: function(message, exception) { }, error: function(message, exception) { if (++this.numberOfErrors == 1 || this.alertAllErrors) { if (!this.quietMode) { var alertMessage = "log4javascript error: " + message; if (exception) { alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception); } alert(alertMessage); } } } }; log4javascript.logLog = logLog; log4javascript.setEventTypes(["load", "error"]); function handleError(message, exception) { logLog.error(message, exception); log4javascript.dispatchEvent("error", { "message": message, "exception": exception }); } log4javascript.handleError = handleError; /* ---------------------------------------------------------------------- */ var enabled = !((typeof log4javascript_disabled != "undefined") && log4javascript_disabled); log4javascript.setEnabled = function(enable) { enabled = bool(enable); }; log4javascript.isEnabled = function() { return enabled; }; var useTimeStampsInMilliseconds = true; log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) { useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds); }; log4javascript.isTimeStampsInMilliseconds = function() { return useTimeStampsInMilliseconds; }; // This evaluates the given expression in the current scope, thus allowing // scripts to access private variables. Particularly useful for testing log4javascript.evalInScope = function(expr) { return eval(expr); }; var showStackTraces = false; log4javascript.setShowStackTraces = function(show) { showStackTraces = bool(show); }; /* ---------------------------------------------------------------------- */ // Levels var Level = function(level, name) { this.level = level; this.name = name; }; Level.prototype = { toString: function() { return this.name; }, equals: function(level) { return this.level == level.level; }, isGreaterOrEqual: function(level) { return this.level >= level.level; } }; Level.ALL = new Level(Number.MIN_VALUE, "ALL"); Level.TRACE = new Level(10000, "TRACE"); Level.DEBUG = new Level(20000, "DEBUG"); Level.INFO = new Level(30000, "INFO"); Level.WARN = new Level(40000, "WARN"); Level.ERROR = new Level(50000, "ERROR"); Level.FATAL = new Level(60000, "FATAL"); Level.OFF = new Level(Number.MAX_VALUE, "OFF"); log4javascript.Level = Level; /* ---------------------------------------------------------------------- */ // Timers function Timer(name, level) { this.name = name; this.level = isUndefined(level) ? Level.INFO : level; this.start = new Date(); } Timer.prototype.getElapsedTime = function() { return new Date().getTime() - this.start.getTime(); }; /* ---------------------------------------------------------------------- */ // Loggers var anonymousLoggerName = "[anonymous]"; var defaultLoggerName = "[default]"; var nullLoggerName = "[null]"; var rootLoggerName = "root"; function Logger(name) { this.name = name; this.parent = null; this.children = []; var appenders = []; var loggerLevel = null; var isRoot = (this.name === rootLoggerName); var isNull = (this.name === nullLoggerName); var appenderCache = null; var appenderCacheInvalidated = false; this.addChild = function(childLogger) { this.children.push(childLogger); childLogger.parent = this; childLogger.invalidateAppenderCache(); }; // Additivity var additive = true; this.getAdditivity = function() { return additive; }; this.setAdditivity = function(additivity) { var valueChanged = (additive != additivity); additive = additivity; if (valueChanged) { this.invalidateAppenderCache(); } }; // Create methods that use the appenders variable in this scope this.addAppender = function(appender) { if (isNull) { handleError("Logger.addAppender: you may not add an appender to the null logger"); } else { if (appender instanceof log4javascript.Appender) { if (!array_contains(appenders, appender)) { appenders.push(appender); appender.setAddedToLogger(this); this.invalidateAppenderCache(); } } else { handleError("Logger.addAppender: appender supplied ('" + toStr(appender) + "') is not a subclass of Appender"); } } }; this.removeAppender = function(appender) { array_remove(appenders, appender); appender.setRemovedFromLogger(this); this.invalidateAppenderCache(); }; this.removeAllAppenders = function() { var appenderCount = appenders.length; if (appenderCount > 0) { for (var i = 0; i < appenderCount; i++) { appenders[i].setRemovedFromLogger(this); } appenders.length = 0; this.invalidateAppenderCache(); } }; this.getEffectiveAppenders = function() { if (appenderCache === null || appenderCacheInvalidated) { // Build appender cache var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ? [] : this.parent.getEffectiveAppenders(); appenderCache = parentEffectiveAppenders.concat(appenders); appenderCacheInvalidated = false; } return appenderCache; }; this.invalidateAppenderCache = function() { appenderCacheInvalidated = true; for (var i = 0, len = this.children.length; i < len; i++) { this.children[i].invalidateAppenderCache(); } }; this.log = function(level, params) { if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) { // Check whether last param is an exception var exception; var finalParamIndex = params.length - 1; var lastParam = params[finalParamIndex]; if (params.length > 1 && isError(lastParam)) { exception = lastParam; finalParamIndex--; } // Construct genuine array for the params var messages = []; for (var i = 0; i <= finalParamIndex; i++) { messages[i] = params[i]; } var loggingEvent = new LoggingEvent( this, new Date(), level, messages, exception); this.callAppenders(loggingEvent); } }; this.callAppenders = function(loggingEvent) { var effectiveAppenders = this.getEffectiveAppenders(); for (var i = 0, len = effectiveAppenders.length; i < len; i++) { effectiveAppenders[i].doAppend(loggingEvent); } }; this.setLevel = function(level) { // Having a level of null on the root logger would be very bad. if (isRoot && level === null) { handleError("Logger.setLevel: you cannot set the level of the root logger to null"); } else if (level instanceof Level) { loggerLevel = level; } else { handleError("Logger.setLevel: level supplied to logger " + this.name + " is not an instance of log4javascript.Level"); } }; this.getLevel = function() { return loggerLevel; }; this.getEffectiveLevel = function() { for (var logger = this; logger !== null; logger = logger.parent) { var level = logger.getLevel(); if (level !== null) { return level; } } }; this.group = function(name, initiallyExpanded) { if (enabled) { var effectiveAppenders = this.getEffectiveAppenders(); for (var i = 0, len = effectiveAppenders.length; i < len; i++) { effectiveAppenders[i].group(name, initiallyExpanded); } } }; this.groupEnd = function() { if (enabled) { var effectiveAppenders = this.getEffectiveAppenders(); for (var i = 0, len = effectiveAppenders.length; i < len; i++) { effectiveAppenders[i].groupEnd(); } } }; var timers = {}; this.time = function(name, level) { if (enabled) { if (isUndefined(name)) { handleError("Logger.time: a name for the timer must be supplied"); } else if (level && !(level instanceof Level)) { handleError("Logger.time: level supplied to timer " + name + " is not an instance of log4javascript.Level"); } else { timers[name] = new Timer(name, level); } } }; this.timeEnd = function(name) { if (enabled) { if (isUndefined(name)) { handleError("Logger.timeEnd: a name for the timer must be supplied"); } else if (timers[name]) { var timer = timers[name]; var milliseconds = timer.getElapsedTime(); this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]); delete timers[name]; } else { logLog.warn("Logger.timeEnd: no timer found with name " + name); } } }; this.assert = function(expr) { if (enabled && !expr) { var args = []; for (var i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } args = (args.length > 0) ? args : ["Assertion Failure"]; args.push(newLine); args.push(expr); this.log(Level.ERROR, args); } }; this.toString = function() { return "Logger[" + this.name + "]"; }; } Logger.prototype = { trace: function() { this.log(Level.TRACE, arguments); }, debug: function() { this.log(Level.DEBUG, arguments); }, info: function() { this.log(Level.INFO, arguments); }, warn: function() { this.log(Level.WARN, arguments); }, error: function() { this.log(Level.ERROR, arguments); }, fatal: function() { this.log(Level.FATAL, arguments); }, isEnabledFor: function(level) { return level.isGreaterOrEqual(this.getEffectiveLevel()); }, isTraceEnabled: function() { return this.isEnabledFor(Level.TRACE); }, isDebugEnabled: function() { return this.isEnabledFor(Level.DEBUG); }, isInfoEnabled: function() { return this.isEnabledFor(Level.INFO); }, isWarnEnabled: function() { return this.isEnabledFor(Level.WARN); }, isErrorEnabled: function() { return this.isEnabledFor(Level.ERROR); }, isFatalEnabled: function() { return this.isEnabledFor(Level.FATAL); } }; Logger.prototype.trace.isEntryPoint = true; Logger.prototype.debug.isEntryPoint = true; Logger.prototype.info.isEntryPoint = true; Logger.prototype.warn.isEntryPoint = true; Logger.prototype.error.isEntryPoint = true; Logger.prototype.fatal.isEntryPoint = true; /* ---------------------------------------------------------------------- */ // Logger access methods // Hashtable of loggers keyed by logger name var loggers = {}; var loggerNames = []; var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG; var rootLogger = new Logger(rootLoggerName); rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL); log4javascript.getRootLogger = function() { return rootLogger; }; log4javascript.getLogger = function(loggerName) { // Use default logger if loggerName is not specified or invalid if (typeof loggerName != "string") { loggerName = anonymousLoggerName; logLog.warn("log4javascript.getLogger: non-string logger name " + toStr(loggerName) + " supplied, returning anonymous logger"); } // Do not allow retrieval of the root logger by name if (loggerName == rootLoggerName) { handleError("log4javascript.getLogger: root logger may not be obtained by name"); } // Create the logger for this name if it doesn't already exist if (!loggers[loggerName]) { var logger = new Logger(loggerName); loggers[loggerName] = logger; loggerNames.push(loggerName); // Set up parent logger, if it doesn't exist var lastDotIndex = loggerName.lastIndexOf("."); var parentLogger; if (lastDotIndex > -1) { var parentLoggerName = loggerName.substring(0, lastDotIndex); parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc. } else { parentLogger = rootLogger; } parentLogger.addChild(logger); } return loggers[loggerName]; }; var defaultLogger = null; log4javascript.getDefaultLogger = function() { if (!defaultLogger) { defaultLogger = createDefaultLogger(); } return defaultLogger; }; var nullLogger = null; log4javascript.getNullLogger = function() { if (!nullLogger) { nullLogger = new Logger(nullLoggerName); nullLogger.setLevel(Level.OFF); } return nullLogger; }; // Destroys all loggers log4javascript.resetConfiguration = function() { rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL); loggers = {}; }; /* ---------------------------------------------------------------------- */ // Logging events var LoggingEvent = function(logger, timeStamp, level, messages, exception) { this.logger = logger; this.timeStamp = timeStamp; this.timeStampInMilliseconds = timeStamp.getTime(); this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000); this.milliseconds = this.timeStamp.getMilliseconds(); this.level = level; this.messages = messages; this.exception = exception; }; LoggingEvent.prototype = { getThrowableStrRep: function() { return this.exception ? getExceptionStringRep(this.exception) : ""; }, getCombinedMessages: function() { return (this.messages.length == 1) ? this.messages[0] : this.messages.join(newLine); }, toString: function() { return "LoggingEvent[" + this.level + "]"; } }; log4javascript.LoggingEvent = LoggingEvent; /* ---------------------------------------------------------------------- */ // Layout prototype var Layout = function() { }; Layout.prototype = { defaults: { loggerKey: "logger", timeStampKey: "timestamp", millisecondsKey: "milliseconds", levelKey: "level", messageKey: "message", exceptionKey: "exception", urlKey: "url" }, loggerKey: "logger", timeStampKey: "timestamp", millisecondsKey: "milliseconds", levelKey: "level", messageKey: "message", exceptionKey: "exception", urlKey: "url", batchHeader: "", batchFooter: "", batchSeparator: "", returnsPostData: false, overrideTimeStampsSetting: false, useTimeStampsInMilliseconds: null, format: function() { handleError("Layout.format: layout supplied has no format() method"); }, ignoresThrowable: function() { handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method"); }, getContentType: function() { return "text/plain"; }, allowBatching: function() { return true; }, setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) { this.overrideTimeStampsSetting = true; this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds); }, isTimeStampsInMilliseconds: function() { return this.overrideTimeStampsSetting ? this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds; }, getTimeStampValue: function(loggingEvent) { return this.isTimeStampsInMilliseconds() ? loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds; }, getDataValues: function(loggingEvent, combineMessages) { var dataValues = [ [this.loggerKey, loggingEvent.logger.name], [this.timeStampKey, this.getTimeStampValue(loggingEvent)], [this.levelKey, loggingEvent.level.name], [this.urlKey, window.location.href], [this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages] ]; if (!this.isTimeStampsInMilliseconds()) { dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]); } if (loggingEvent.exception) { dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]); } if (this.hasCustomFields()) { for (var i = 0, len = this.customFields.length; i < len; i++) { var val = this.customFields[i].value; // Check if the value is a function. If so, execute it, passing it the // current layout and the logging event if (typeof val === "function") { val = val(this, loggingEvent); } dataValues.push([this.customFields[i].name, val]); } } return dataValues; }, setKeys: function(loggerKey, timeStampKey, levelKey, messageKey, exceptionKey, urlKey, millisecondsKey) { this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey); this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey); this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey); this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey); this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey); this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey); this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey); }, setCustomField: function(name, value) { var fieldUpdated = false; for (var i = 0, len = this.customFields.length; i < len; i++) { if (this.customFields[i].name === name) { this.customFields[i].value = value; fieldUpdated = true; } } if (!fieldUpdated) { this.customFields.push({"name": name, "value": value}); } }, hasCustomFields: function() { return (this.customFields.length > 0); }, formatWithException: function(loggingEvent) { var formatted = this.format(loggingEvent); if (loggingEvent.exception && this.ignoresThrowable()) { formatted += loggingEvent.getThrowableStrRep(); } return formatted; }, toString: function() { handleError("Layout.toString: all layouts must override this method"); } }; log4javascript.Layout = Layout; /* ---------------------------------------------------------------------- */ // Appender prototype var Appender = function() {}; Appender.prototype = new EventSupport(); Appender.prototype.layout = new PatternLayout(); Appender.prototype.threshold = Level.ALL; Appender.prototype.loggers = []; // Performs threshold checks before delegating actual logging to the // subclass's specific append method. Appender.prototype.doAppend = function(loggingEvent) { if (enabled && loggingEvent.level.level >= this.threshold.level) { this.append(loggingEvent); } }; Appender.prototype.append = function(loggingEvent) {}; Appender.prototype.setLayout = function(layout) { if (layout instanceof Layout) { this.layout = layout; } else { handleError("Appender.setLayout: layout supplied to " + this.toString() + " is not a subclass of Layout"); } }; Appender.prototype.getLayout = function() { return this.layout; }; Appender.prototype.setThreshold = function(threshold) { if (threshold instanceof Level) { this.threshold = threshold; } else { handleError("Appender.setThreshold: threshold supplied to " + this.toString() + " is not a subclass of Level"); } }; Appender.prototype.getThreshold = function() { return this.threshold; }; Appender.prototype.setAddedToLogger = function(logger) { this.loggers.push(logger); }; Appender.prototype.setRemovedFromLogger = function(logger) { array_remove(this.loggers, logger); }; Appender.prototype.group = emptyFunction; Appender.prototype.groupEnd = emptyFunction; Appender.prototype.toString = function() { handleError("Appender.toString: all appenders must override this method"); }; log4javascript.Appender = Appender; /* ---------------------------------------------------------------------- */ // SimpleLayout function SimpleLayout() { this.customFields = []; } SimpleLayout.prototype = new Layout(); SimpleLayout.prototype.format = function(loggingEvent) { return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages(); }; SimpleLayout.prototype.ignoresThrowable = function() { return true; }; SimpleLayout.prototype.toString = function() { return "SimpleLayout"; }; log4javascript.SimpleLayout = SimpleLayout; /* ----------------------------------------------------------------------- */ // NullLayout function NullLayout() { this.customFields = []; } NullLayout.prototype = new Layout(); NullLayout.prototype.format = function(loggingEvent) { return loggingEvent.messages; }; NullLayout.prototype.ignoresThrowable = function() { return true; }; NullLayout.prototype.formatWithException = function(loggingEvent) { var messages = loggingEvent.messages, ex = loggingEvent.exception; return ex ? messages.concat([ex]) : messages; }; NullLayout.prototype.toString = function() { return "NullLayout"; }; log4javascript.NullLayout = NullLayout; /* ---------------------------------------------------------------------- */ // XmlLayout function XmlLayout(combineMessages) { this.combineMessages = extractBooleanFromParam(combineMessages, true); this.customFields = []; } XmlLayout.prototype = new Layout(); XmlLayout.prototype.isCombinedMessages = function() { return this.combineMessages; }; XmlLayout.prototype.getContentType = function() { return "text/xml"; }; XmlLayout.prototype.escapeCdata = function(str) { return str.replace(/\]\]>/, "]]>]]><![CDATA["); }; XmlLayout.prototype.format = function(loggingEvent) { var layout = this; var i, len; function formatMessage(message) { message = (typeof message === "string") ? message : toStr(message); return "<log4javascript:message><![CDATA[" + layout.escapeCdata(message) + "]]></log4javascript:message>"; } var str = "<log4javascript:event logger=\"" + loggingEvent.logger.name + "\" timestamp=\"" + this.getTimeStampValue(loggingEvent) + "\""; if (!this.isTimeStampsInMilliseconds()) { str += " milliseconds=\"" + loggingEvent.milliseconds + "\""; } str += " level=\"" + loggingEvent.level.name + "\">" + newLine; if (this.combineMessages) { str += formatMessage(loggingEvent.getCombinedMessages()); } else { str += "<log4javascript:messages>" + newLine; for (i = 0, len = loggingEvent.messages.length; i < len; i++) { str += formatMessage(loggingEvent.messages[i]) + newLine; } str += "</log4javascript:messages>" + newLine; } if (this.hasCustomFields()) { for (i = 0, len = this.customFields.length; i < len; i++) { str += "<log4javascript:customfield name=\"" + this.customFields[i].name + "\"><![CDATA[" + this.customFields[i].value.toString() + "]]></log4javascript:customfield>" + newLine; } } if (loggingEvent.exception) { str += "<log4javascript:exception><![CDATA[" + getExceptionStringRep(loggingEvent.exception) + "]]></log4javascript:exception>" + newLine; } str += "</log4javascript:event>" + newLine + newLine; return str; }; XmlLayout.prototype.ignoresThrowable = function() { return false; }; XmlLayout.prototype.toString = function() { return "XmlLayout"; }; log4javascript.XmlLayout = XmlLayout; /* ---------------------------------------------------------------------- */ // JsonLayout related function escapeNewLines(str) { return str.replace(/\r\n|\r|\n/g, "\\r\\n"); } function JsonLayout(readable, combineMessages) { this.readable = extractBooleanFromParam(readable, false); this.combineMessages = extractBooleanFromParam(combineMessages, true); this.batchHeader = this.readable ? "[" + newLine : "["; this.batchFooter = this.readable ? "]" + newLine : "]"; this.batchSeparator = this.readable ? "," + newLine : ","; this.setKeys(); this.colon = this.readable ? ": " : ":"; this.tab = this.readable ? "\t" : ""; this.lineBreak = this.readable ? newLine : ""; this.customFields = []; } /* ---------------------------------------------------------------------- */ // JsonLayout JsonLayout.prototype = new Layout(); JsonLayout.prototype.isReadable = function() { return this.readable; }; JsonLayout.prototype.isCombinedMessages = function() { return this.combineMessages; }; JsonLayout.prototype.format = function(loggingEvent) { var layout = this; var dataValues = this.getDataValues(loggingEvent, this.combineMessages); var str = "{" + this.lineBreak; var i, len; function formatValue(val, prefix, expand) { // Check the type of the data value to decide whether quotation marks // or expansion are required var formattedValue; var valType = typeof val; if (val instanceof Date) { formattedValue = String(val.getTime()); } else if (expand && (val instanceof Array)) { formattedValue = "[" + layout.lineBreak; for (var i = 0, len = val.length; i < len; i++) { var childPrefix = prefix + layout.tab; formattedValue += childPrefix + formatValue(val[i], childPrefix, false); if (i < val.length - 1) { formattedValue += ","; } formattedValue += layout.lineBreak; } formattedValue += prefix + "]"; } else if (valType !== "number" && valType !== "boolean") { formattedValue = "\"" + escapeNewLines(toStr(val).replace(/\"/g, "\\\"")) + "\""; } else { formattedValue = val; } return formattedValue; } for (i = 0, len = dataValues.length - 1; i <= len; i++) { str += this.tab + "\"" + dataValues[i][0] + "\"" + this.colon + formatValue(dataValues[i][1], this.tab, true); if (i < len) { str += ","; } str += this.lineBreak; } str += "}" + this.lineBreak; return str; }; JsonLayout.prototype.ignoresThrowable = function() { return false; }; JsonLayout.prototype.toString = function() { return "JsonLayout"; }; JsonLayout.prototype.getContentType = function() { return "application/json"; }; log4javascript.JsonLayout = JsonLayout; /* ---------------------------------------------------------------------- */ // HttpPostDataLayout function HttpPostDataLayout() { this.setKeys(); this.customFields = []; this.returnsPostData = true; } HttpPostDataLayout.prototype = new Layout(); // Disable batching HttpPostDataLayout.prototype.allowBatching = function() { return false; }; HttpPostDataLayout.prototype.format = function(loggingEvent) { var dataValues = this.getDataValues(loggingEvent); var queryBits = []; for (var i = 0, len = dataValues.length; i < len; i++) { var val = (dataValues[i][1] instanceof Date) ? String(dataValues[i][1].getTime()) : dataValues[i][1]; queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(val)); } return queryBits.join("&"); }; HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) { return false; }; HttpPostDataLayout.prototype.toString = function() { return "HttpPostDataLayout"; }; log4javascript.HttpPostDataLayout = HttpPostDataLayout; /* ---------------------------------------------------------------------- */ // formatObjectExpansion function formatObjectExpansion(obj, depth, indentation) { var objectsExpanded = []; function doFormat(obj, depth, indentation) { var i, len, childDepth, childIndentation, childLines, expansion, childExpansion; if (!indentation) { indentation = ""; } function formatString(text) { var lines = splitIntoLines(text); for (var j = 1, jLen = lines.length; j < jLen; j++) { lines[j] = indentation + lines[j]; } return lines.join(newLine); } if (obj === null) { return "null"; } else if (typeof obj == "undefined") { return "undefined"; } else if (typeof obj == "string") { return formatString(obj); } else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) { try { expansion = toStr(obj); } catch (ex) { expansion = "Error formatting property. Details: " + getExceptionStringRep(ex); } return expansion + " [already expanded]"; } else if ((obj instanceof Array) && depth > 0) { objectsExpanded.push(obj); expansion = "[" + newLine; childDepth = depth - 1; childIndentation = indentation + " "; childLines = []; for (i = 0, len = obj.length; i < len; i++) { try { childExpansion = doFormat(obj[i], childDepth, childIndentation); childLines.push(childIndentation + childExpansion); } catch (ex) { childLines.push(childIndentation + "Error formatting array member. Details: " + getExceptionStringRep(ex) + ""); } } expansion += childLines.join("," + newLine) + newLine + indentation + "]"; return expansion; } else if (Object.prototype.toString.call(obj) == "[object Date]") { return obj.toString(); } else if (typeof obj == "object" && depth > 0) { objectsExpanded.push(obj); expansion = "{" + newLine; childDepth = depth - 1; childIndentation = indentation + " "; childLines = []; for (i in obj) { try { childExpansion = doFormat(obj[i], childDepth, childIndentation); childLines.push(childIndentation + i + ": " + childExpansion); } catch (ex) { childLines.push(childIndentation + i + ": Error formatting property. Details: " + getExceptionStringRep(ex)); } } expansion += childLines.join("," + newLine) + newLine + indentation + "}"; return expansion; } else { return formatString(toStr(obj)); } } return doFormat(obj, depth, indentation); } /* ---------------------------------------------------------------------- */ // Date-related stuff var SimpleDateFormat; (function() { var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/; var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5; var types = { G : TEXT2, y : YEAR, M : MONTH, w : NUMBER, W : NUMBER, D : NUMBER, d : NUMBER, F : NUMBER, E : TEXT3, a : TEXT2, H : NUMBER, k : NUMBER, K : NUMBER, h : NUMBER, m : NUMBER, s : NUMBER, S : NUMBER, Z : TIMEZONE }; var ONE_DAY = 24 * 60 * 60 * 1000; var ONE_WEEK = 7 * ONE_DAY; var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1; var newDateAtMidnight = function(year, month, day) { var d = new Date(year, month, day, 0, 0, 0); d.setMilliseconds(0); return d; }; Date.prototype.getDifference = function(date) { return this.getTime() - date.getTime(); }; Date.prototype.isBefore = function(d) { return this.getTime() < d.getTime(); }; Date.prototype.getUTCTime = function() { return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds()); }; Date.prototype.getTimeSince = function(d) { return this.getUTCTime() - d.getUTCTime(); }; Date.prototype.getPreviousSunday = function() { // Using midday avoids any possibility of DST messing things up var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0); var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY); return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(), previousSunday.getDate()); }; Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) { if (isUndefined(this.minimalDaysInFirstWeek)) { minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK; } var previousSunday = this.getPreviousSunday(); var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1); var numberOfSundays = previousSunday.isBefore(startOfYear) ? 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK); var numberOfDaysInFirstWeek = 7 - startOfYear.getDay(); var weekInYear = numberOfSundays; if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) { weekInYear--; } return weekInYear; }; Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) { if (isUndefined(this.minimalDaysInFirstWeek)) { minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK; } var previousSunday = this.getPreviousSunday(); var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1); var numberOfSundays = previousSunday.isBefore(startOfMonth) ? 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK); var numberOfDaysInFirstWeek = 7 - startOfMonth.getDay(); var weekInMonth = numberOfSundays; if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) { weekInMonth++; } return weekInMonth; }; Date.prototype.getDayInYear = function() { var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1); return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY); }; /* ------------------------------------------------------------------ */ SimpleDateFormat = function(formatString) { this.formatString = formatString; }; /** * Sets the minimum number of days in a week in order for that week to * be considered as belonging to a particular month or year */ SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) { this.minimalDaysInFirstWeek = days; }; SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() { return isUndefined(this.minimalDaysInFirstWeek) ? DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek; }; var padWithZeroes = function(str, len) { while (str.length < len) { str = "0" + str; } return str; }; var formatText = function(data, numberOfLetters, minLength) { return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters)); }; var formatNumber = function(data, numberOfLetters) { var dataString = "" + data; // Pad with 0s as necessary return padWithZeroes(dataString, numberOfLetters); }; SimpleDateFormat.prototype.format = function(date) { var formattedString = ""; var result; var searchString = this.formatString; while ((result = regex.exec(searchString))) { var quotedString = result[1]; var patternLetters = result[2]; var otherLetters = result[3]; var otherCharacters = result[4]; // If the pattern matched is quoted string, output the text between the quotes if (quotedString) { if (quotedString == "''") { formattedString += "'"; } else { formattedString += quotedString.substring(1, quotedString.length - 1); } } else if (otherLetters) { // Swallow non-pattern letters by doing nothing here } else if (otherCharacters) { // Simply output other characters formattedString += otherCharacters; } else if (patternLetters) { // Replace pattern letters var patternLetter = patternLetters.charAt(0); var numberOfLetters = patternLetters.length; var rawData = ""; switch(patternLetter) { case "G": rawData = "AD"; break; case "y": rawData = date.getFullYear(); break; case "M": rawData = date.getMonth(); break; case "w": rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek()); break; case "W": rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek()); break; case "D": rawData = date.getDayInYear(); break; case "d": rawData = date.getDate(); break; case "F": rawData = 1 + Math.floor((date.getDate() - 1) / 7); break; case "E": rawData = dayNames[date.getDay()]; break; case "a": rawData = (date.getHours() >= 12) ? "PM" : "AM"; break; case "H": rawData = date.getHours(); break; case "k": rawData = date.getHours() || 24; break; case "K": rawData = date.getHours() % 12; break; case "h": rawData = (date.getHours() % 12) || 12; break; case "m": rawData = date.getMinutes(); break; case "s": rawData = date.getSeconds(); break; case "S": rawData = date.getMilliseconds(); break; case "Z": rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time. break; } // Format the raw data depending on the type switch(types[patternLetter]) { case TEXT2: formattedString += formatText(rawData, numberOfLetters, 2); break; case TEXT3: formattedString += formatText(rawData, numberOfLetters, 3); break; case NUMBER: formattedString += formatNumber(rawData, numberOfLetters); break; case YEAR: if (numberOfLetters <= 3) { // Output a 2-digit year var dataString = "" + rawData; formattedString += dataString.substr(2, 2); } else { formattedString += formatNumber(rawData, numberOfLetters); } break; case MONTH: if (numberOfLetters >= 3) { formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters); } else { // NB. Months returned by getMonth are zero-based formattedString += formatNumber(rawData + 1, numberOfLetters); } break; case TIMEZONE: var isPositive = (rawData > 0); // The following line looks like a mistake but isn't // because of the way getTimezoneOffset measures. var prefix = isPositive ? "-" : "+"; var absData = Math.abs(rawData); // Hours var hours = "" + Math.floor(absData / 60); hours = padWithZeroes(hours, 2); // Minutes var minutes = "" + (absData % 60); minutes = padWithZeroes(minutes, 2); formattedString += prefix + hours + minutes; break; } } searchString = searchString.substr(result.index + result[0].length); } return formattedString; }; })(); log4javascript.SimpleDateFormat = SimpleDateFormat; /* ---------------------------------------------------------------------- */ // PatternLayout function PatternLayout(pattern) { if (pattern) { this.pattern = pattern; } else { this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; } this.customFields = []; } PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n"; PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS"; PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS"; PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS"; PatternLayout.prototype = new Layout(); PatternLayout.prototype.format = function(loggingEvent) { var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/; var formattedString = ""; var result; var searchString = this.pattern; // Cannot use regex global flag since it doesn't work with exec in IE5 while ((result = regex.exec(searchString))) { var matchedString = result[0]; var padding = result[1]; var truncation = result[2]; var conversionCharacter = result[3]; var specifier = result[5]; var text = result[6]; // Check if the pattern matched was just normal text if (text) { formattedString += "" + text; } else { // Create a raw replacement string based on the conversion // character and specifier var replacement = ""; switch(conversionCharacter) { case "a": // Array of messages case "m": // Message var depth = 0; if (specifier) { depth = parseInt(specifier, 10); if (isNaN(depth)) { handleError("PatternLayout.format: invalid specifier '" + specifier + "' for conversion character '" + conversionCharacter + "' - should be a number"); depth = 0; } } var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages; for (var i = 0, len = messages.length; i < len; i++) { if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) { replacement += " "; } if (depth === 0) { replacement += messages[i]; } else { replacement += formatObjectExpansion(messages[i], depth); } } break; case "c": // Logger name var loggerName = loggingEvent.logger.name; if (specifier) { var precision = parseInt(specifier, 10); var loggerNameBits = loggingEvent.logger.name.split("."); if (precision >= loggerNameBits.length) { replacement = loggerName; } else { replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); } } else { replacement = loggerName; } break; case "d": // Date var dateFormat = PatternLayout.ISO8601_DATEFORMAT; if (specifier) { dateFormat = specifier; // Pick up special cases if (dateFormat == "ISO8601") { dateFormat = PatternLayout.ISO8601_DATEFORMAT; } else if (dateFormat == "ABSOLUTE") { dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT; } else if (dateFormat == "DATE") { dateFormat = PatternLayout.DATETIME_DATEFORMAT; } } // Format the date replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp); break; case "f": // Custom field if (this.hasCustomFields()) { var fieldIndex = 0; if (specifier) { fieldIndex = parseInt(specifier, 10); if (isNaN(fieldIndex)) { handleError("PatternLayout.format: invalid specifier '" + specifier + "' for conversion character 'f' - should be a number"); } else if (fieldIndex === 0) { handleError("PatternLayout.format: invalid specifier '" + specifier + "' for conversion character 'f' - must be greater than zero"); } else if (fieldIndex > this.customFields.length) { handleError("PatternLayout.format: invalid specifier '" + specifier + "' for conversion character 'f' - there aren't that many custom fields"); } else { fieldIndex = fieldIndex - 1; } } var val = this.customFields[fieldIndex].value; if (typeof val == "function") { val = val(this, loggingEvent); } replacement = val; } break; case "n": // New line replacement = newLine; break; case "p": // Level replacement = loggingEvent.level.name; break; case "r": // Milliseconds since log4javascript startup replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate); break; case "%": // Literal % sign replacement = "%"; break; default: replacement = matchedString; break; } // Format the replacement according to any padding or // truncation specified var l; // First, truncation if (truncation) { l = parseInt(truncation.substr(1), 10); var strLen = replacement.length; if (l < strLen) { replacement = replacement.substring(strLen - l, strLen); } } // Next, padding if (padding) { if (padding.charAt(0) == "-") { l = parseInt(padding.substr(1), 10); // Right pad with spaces while (replacement.length < l) { replacement += " "; } } else { l = parseInt(padding, 10); // Left pad with spaces while (replacement.length < l) { replacement = " " + replacement; } } } formattedString += replacement; } searchString = searchString.substr(result.index + result[0].length); } return formattedString; }; PatternLayout.prototype.ignoresThrowable = function() { return true; }; PatternLayout.prototype.toString = function() { return "PatternLayout"; }; log4javascript.PatternLayout = PatternLayout; /* ---------------------------------------------------------------------- */ // AlertAppender function AlertAppender() {} AlertAppender.prototype = new Appender(); AlertAppender.prototype.layout = new SimpleLayout(); AlertAppender.prototype.append = function(loggingEvent) { alert( this.getLayout().formatWithException(loggingEvent) ); }; AlertAppender.prototype.toString = function() { return "AlertAppender"; }; log4javascript.AlertAppender = AlertAppender; /* ---------------------------------------------------------------------- */ // BrowserConsoleAppender (only works in Opera and Safari and Firefox with // Firebug extension) function BrowserConsoleAppender() {} BrowserConsoleAppender.prototype = new log4javascript.Appender(); BrowserConsoleAppender.prototype.layout = new NullLayout(); BrowserConsoleAppender.prototype.threshold = Level.DEBUG; BrowserConsoleAppender.prototype.append = function(loggingEvent) { var appender = this; var getFormattedMessage = function() { var formattedMessage = appender.getLayout().formatWithException(loggingEvent); return (typeof formattedMessage == "string") ? [formattedMessage] : formattedMessage; }; var console; if ( (console = window.console) && console.log) { // Safari and Firebug var formattedMessage = getFormattedMessage(); // Log to Firebug or the browser console using specific logging // methods or revert to console.log otherwise var consoleMethodName; if (console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) { consoleMethodName = "debug"; } else if (console.info && Level.INFO.equals(loggingEvent.level)) { consoleMethodName = "info"; } else if (console.warn && Level.WARN.equals(loggingEvent.level)) { consoleMethodName = "warn"; } else if (console.error && loggingEvent.level.isGreaterOrEqual(Level.ERROR)) { consoleMethodName = "error"; } else { consoleMethodName = "log"; } if (console[consoleMethodName].apply) { console[consoleMethodName].apply(console, formattedMessage); } else { console[consoleMethodName](formattedMessage); } } else if ((typeof opera != "undefined") && opera.postError) { // Opera opera.postError(getFormattedMessage()); } }; BrowserConsoleAppender.prototype.group = function(name) { if (window.console && window.console.group) { window.console.group(name); } }; BrowserConsoleAppender.prototype.groupEnd = function() { if (window.console && window.console.groupEnd) { window.console.groupEnd(); } }; BrowserConsoleAppender.prototype.toString = function() { return "BrowserConsoleAppender"; }; log4javascript.BrowserConsoleAppender = BrowserConsoleAppender; /* ---------------------------------------------------------------------- */ // AjaxAppender related var xhrFactory = function() { return new XMLHttpRequest(); }; var xmlHttpFactories = [ xhrFactory, function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, function() { return new ActiveXObject("Microsoft.XMLHTTP"); } ]; var withCredentialsSupported = false; var getXmlHttp = function(errorHandler) { // This is only run the first time; the value of getXmlHttp gets // replaced with the factory that succeeds on the first run var xmlHttp = null, factory; for (var i = 0, len = xmlHttpFactories.length; i < len; i++) { factory = xmlHttpFactories[i]; try { xmlHttp = factory(); withCredentialsSupported = (factory == xhrFactory && ("withCredentials" in xmlHttp)); getXmlHttp = factory; return xmlHttp; } catch (e) { } } // If we're here, all factories have failed, so throw an error if (errorHandler) { errorHandler(); } else { handleError("getXmlHttp: unable to obtain XMLHttpRequest object"); } }; function isHttpRequestSuccessful(xmlHttp) { return isUndefined(xmlHttp.status) || xmlHttp.status === 0 || (xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 1223 /* Fix for IE */; } /* ---------------------------------------------------------------------- */ // AjaxAppender function AjaxAppender(url, withCredentials) { var appender = this; var isSupported = true; if (!url) { handleError("AjaxAppender: URL must be specified in constructor"); isSupported = false; } var timed = this.defaults.timed; var waitForResponse = this.defaults.waitForResponse; var batchSize = this.defaults.batchSize; var timerInterval = this.defaults.timerInterval; var requestSuccessCallback = this.defaults.requestSuccessCallback; var failCallback = this.defaults.failCallback; var postVarName = this.defaults.postVarName; var sendAllOnUnload = this.defaults.sendAllOnUnload; var contentType = this.defaults.contentType; var sessionId = null; var queuedLoggingEvents = []; var queuedRequests = []; var headers = []; var sending = false; var initialized = false; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. function checkCanConfigure(configOptionName) { if (initialized) { handleError("AjaxAppender: configuration option '" + configOptionName + "' may not be set after the appender has been initialized"); return false; } return true; } this.getSessionId = function() { return sessionId; }; this.setSessionId = function(sessionIdParam) { sessionId = extractStringFromParam(sessionIdParam, null); this.layout.setCustomField("sessionid", sessionId); }; this.setLayout = function(layoutParam) { if (checkCanConfigure("layout")) { this.layout = layoutParam; // Set the session id as a custom field on the layout, if not already present if (sessionId !== null) { this.setSessionId(sessionId); } } }; this.isTimed = function() { return timed; }; this.setTimed = function(timedParam) { if (checkCanConfigure("timed")) { timed = bool(timedParam); } }; this.getTimerInterval = function() { return timerInterval; }; this.setTimerInterval = function(timerIntervalParam) { if (checkCanConfigure("timerInterval")) { timerInterval = extractIntFromParam(timerIntervalParam, timerInterval); } }; this.isWaitForResponse = function() { return waitForResponse; }; this.setWaitForResponse = function(waitForResponseParam) { if (checkCanConfigure("waitForResponse")) { waitForResponse = bool(waitForResponseParam); } }; this.getBatchSize = function() { return batchSize; }; this.setBatchSize = function(batchSizeParam) { if (checkCanConfigure("batchSize")) { batchSize = extractIntFromParam(batchSizeParam, batchSize); } }; this.isSendAllOnUnload = function() { return sendAllOnUnload; }; this.setSendAllOnUnload = function(sendAllOnUnloadParam) { if (checkCanConfigure("sendAllOnUnload")) { sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload); } }; this.setRequestSuccessCallback = function(requestSuccessCallbackParam) { requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback); }; this.setFailCallback = function(failCallbackParam) { failCallback = extractFunctionFromParam(failCallbackParam, failCallback); }; this.getPostVarName = function() { return postVarName; }; this.setPostVarName = function(postVarNameParam) { if (checkCanConfigure("postVarName")) { postVarName = extractStringFromParam(postVarNameParam, postVarName); } }; this.getHeaders = function() { return headers; }; this.addHeader = function(name, value) { if (name.toLowerCase() == "content-type") { contentType = value; } else { headers.push( { name: name, value: value } ); } }; // Internal functions function sendAll() { if (isSupported && enabled) { sending = true; var currentRequestBatch; if (waitForResponse) { // Send the first request then use this function as the callback once // the response comes back if (queuedRequests.length > 0) { currentRequestBatch = queuedRequests.shift(); sendRequest(preparePostData(currentRequestBatch), sendAll); } else { sending = false; if (timed) { scheduleSending(); } } } else { // Rattle off all the requests without waiting to see the response while ((currentRequestBatch = queuedRequests.shift())) { sendRequest(preparePostData(currentRequestBatch)); } sending = false; if (timed) { scheduleSending(); } } } } this.sendAll = sendAll; // Called when the window unloads. At this point we're past caring about // waiting for responses or timers or incomplete batches - everything // must go, now function sendAllRemaining() { var sendingAnything = false; if (isSupported && enabled) { // Create requests for everything left over, batched as normal var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1; var currentLoggingEvent; var batchedLoggingEvents = []; while ((currentLoggingEvent = queuedLoggingEvents.shift())) { batchedLoggingEvents.push(currentLoggingEvent); if (queuedLoggingEvents.length >= actualBatchSize) { // Queue this batch of log entries queuedRequests.push(batchedLoggingEvents); batchedLoggingEvents = []; } } // If there's a partially completed batch, add it if (batchedLoggingEvents.length > 0) { queuedRequests.push(batchedLoggingEvents); } sendingAnything = (queuedRequests.length > 0); waitForResponse = false; timed = false; sendAll(); } return sendingAnything; } this.sendAllRemaining = sendAllRemaining; function preparePostData(batchedLoggingEvents) { // Format the logging events var formattedMessages = []; var currentLoggingEvent; var postData = ""; while ((currentLoggingEvent = batchedLoggingEvents.shift())) { formattedMessages.push( appender.getLayout().formatWithException(currentLoggingEvent) ); } // Create the post data string if (batchedLoggingEvents.length == 1) { postData = formattedMessages.join(""); } else { postData = appender.getLayout().batchHeader + formattedMessages.join(appender.getLayout().batchSeparator) + appender.getLayout().batchFooter; } if (contentType == appender.defaults.contentType) { postData = appender.getLayout().returnsPostData ? postData : urlEncode(postVarName) + "=" + urlEncode(postData); // Add the layout name to the post data if (postData.length > 0) { postData += "&"; } postData += "layout=" + urlEncode(appender.getLayout().toString()); } return postData; } function scheduleSending() { window.setTimeout(sendAll, timerInterval); } function xmlHttpErrorHandler() { var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled"; handleError(msg); isSupported = false; if (failCallback) { failCallback(msg); } } function sendRequest(postData, successCallback) { try { var xmlHttp = getXmlHttp(xmlHttpErrorHandler); if (isSupported) { xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { if (isHttpRequestSuccessful(xmlHttp)) { if (requestSuccessCallback) { requestSuccessCallback(xmlHttp); } if (successCallback) { successCallback(xmlHttp); } } else { var msg = "AjaxAppender.append: XMLHttpRequest request to URL " + url + " returned status code " + xmlHttp.status; handleError(msg); if (failCallback) { failCallback(msg); } } xmlHttp.onreadystatechange = emptyFunction; xmlHttp = null; } }; xmlHttp.open("POST", url, true); // Add withCredentials to facilitate CORS requests with cookies if (withCredentials && withCredentialsSupported) { xmlHttp.withCredentials = true; } try { for (var i = 0, header; header = headers[i++]; ) { xmlHttp.setRequestHeader(header.name, header.value); } xmlHttp.setRequestHeader("Content-Type", contentType); } catch (headerEx) { var msg = "AjaxAppender.append: your browser's XMLHttpRequest implementation" + " does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled"; handleError(msg); isSupported = false; if (failCallback) { failCallback(msg); } return; } xmlHttp.send(postData); } } catch (ex) { var errMsg = "AjaxAppender.append: error sending log message to " + url; handleError(errMsg, ex); isSupported = false; if (failCallback) { failCallback(errMsg + ". Details: " + getExceptionStringRep(ex)); } } } this.append = function(loggingEvent) { if (isSupported) { if (!initialized) { init(); } queuedLoggingEvents.push(loggingEvent); var actualBatchSize = this.getLayout().allowBatching() ? batchSize : 1; if (queuedLoggingEvents.length >= actualBatchSize) { var currentLoggingEvent; var batchedLoggingEvents = []; while ((currentLoggingEvent = queuedLoggingEvents.shift())) { batchedLoggingEvents.push(currentLoggingEvent); } // Queue this batch of log entries queuedRequests.push(batchedLoggingEvents); // If using a timer, the queue of requests will be processed by the // timer function, so nothing needs to be done here. if (!timed && (!waitForResponse || (waitForResponse && !sending))) { sendAll(); } } } }; function init() { initialized = true; // Add unload event to send outstanding messages if (sendAllOnUnload) { var oldBeforeUnload = window.onbeforeunload; window.onbeforeunload = function() { if (oldBeforeUnload) { oldBeforeUnload(); } sendAllRemaining(); }; } // Start timer if (timed) { scheduleSending(); } } } AjaxAppender.prototype = new Appender(); AjaxAppender.prototype.defaults = { waitForResponse: false, timed: false, timerInterval: 1000, batchSize: 1, sendAllOnUnload: false, requestSuccessCallback: null, failCallback: null, postVarName: "data", contentType: "application/x-www-form-urlencoded" }; AjaxAppender.prototype.layout = new HttpPostDataLayout(); AjaxAppender.prototype.toString = function() { return "AjaxAppender"; }; log4javascript.AjaxAppender = AjaxAppender; /* ---------------------------------------------------------------------- */ // PopUpAppender and InPageAppender related function setCookie(name, value, days, path) { var expires; path = path ? "; path=" + path : ""; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toGMTString(); } else { expires = ""; } document.cookie = escape(name) + "=" + escape(value) + expires + path; } function getCookie(name) { var nameEquals = escape(name) + "="; var ca = document.cookie.split(";"); for (var i = 0, len = ca.length; i < len; i++) { var c = ca[i]; while (c.charAt(0) === " ") { c = c.substring(1, c.length); } if (c.indexOf(nameEquals) === 0) { return unescape(c.substring(nameEquals.length, c.length)); } } return null; } // Gets the base URL of the location of the log4javascript script. // This is far from infallible. function getBaseUrl() { var scripts = document.getElementsByTagName("script"); for (var i = 0, len = scripts.length; i < len; ++i) { if (scripts[i].src.indexOf("log4javascript") != -1) { var lastSlash = scripts[i].src.lastIndexOf("/"); return (lastSlash == -1) ? "" : scripts[i].src.substr(0, lastSlash + 1); } } return null; } function isLoaded(win) { try { return bool(win.loaded); } catch (ex) { return false; } } /* ---------------------------------------------------------------------- */ // ConsoleAppender (prototype for PopUpAppender and InPageAppender) var ConsoleAppender; // Create an anonymous function to protect base console methods (function() { var getConsoleHtmlLines = function() { return [ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">', '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">', ' <head>', ' <title>log4javascript</title>', ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', ' <!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->', ' <meta http-equiv="X-UA-Compatible" content="IE=7" />', ' <script type="text/javascript">var isIe = false, isIePre7 = false;</script>', ' <!--[if IE]><script type="text/javascript">isIe = true</script><![endif]-->', ' <!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]-->', ' <script type="text/javascript">', ' //<![CDATA[', ' var loggingEnabled = true;', ' var logQueuedEventsTimer = null;', ' var logEntries = [];', ' var logEntriesAndSeparators = [];', ' var logItems = [];', ' var renderDelay = 100;', ' var unrenderedLogItemsExist = false;', ' var rootGroup, currentGroup = null;', ' var loaded = false;', ' var currentLogItem = null;', ' var logMainContainer;', '', ' function copyProperties(obj, props) {', ' for (var i in props) {', ' obj[i] = props[i];', ' }', ' }', '', ' /*----------------------------------------------------------------*/', '', ' function LogItem() {', ' }', '', ' LogItem.prototype = {', ' mainContainer: null,', ' wrappedContainer: null,', ' unwrappedContainer: null,', ' group: null,', '', ' appendToLog: function() {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].appendToLog();', ' }', ' this.group.update();', ' },', '', ' doRemove: function(doUpdate, removeFromGroup) {', ' if (this.rendered) {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].remove();', ' }', ' this.unwrappedElementContainer = null;', ' this.wrappedElementContainer = null;', ' this.mainElementContainer = null;', ' }', ' if (this.group && removeFromGroup) {', ' this.group.removeChild(this, doUpdate);', ' }', ' if (this === currentLogItem) {', ' currentLogItem = null;', ' }', ' },', '', ' remove: function(doUpdate, removeFromGroup) {', ' this.doRemove(doUpdate, removeFromGroup);', ' },', '', ' render: function() {},', '', ' accept: function(visitor) {', ' visitor.visit(this);', ' },', '', ' getUnwrappedDomContainer: function() {', ' return this.group.unwrappedElementContainer.contentDiv;', ' },', '', ' getWrappedDomContainer: function() {', ' return this.group.wrappedElementContainer.contentDiv;', ' },', '', ' getMainDomContainer: function() {', ' return this.group.mainElementContainer.contentDiv;', ' }', ' };', '', ' LogItem.serializedItemKeys = {LOG_ENTRY: 0, GROUP_START: 1, GROUP_END: 2};', '', ' /*----------------------------------------------------------------*/', '', ' function LogItemContainerElement() {', ' }', '', ' LogItemContainerElement.prototype = {', ' appendToLog: function() {', ' var insertBeforeFirst = (newestAtTop && this.containerDomNode.hasChildNodes());', ' if (insertBeforeFirst) {', ' this.containerDomNode.insertBefore(this.mainDiv, this.containerDomNode.firstChild);', ' } else {', ' this.containerDomNode.appendChild(this.mainDiv);', ' }', ' }', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function SeparatorElementContainer(containerDomNode) {', ' this.containerDomNode = containerDomNode;', ' this.mainDiv = document.createElement("div");', ' this.mainDiv.className = "separator";', ' this.mainDiv.innerHTML = " ";', ' }', '', ' SeparatorElementContainer.prototype = new LogItemContainerElement();', '', ' SeparatorElementContainer.prototype.remove = function() {', ' this.mainDiv.parentNode.removeChild(this.mainDiv);', ' this.mainDiv = null;', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function Separator() {', ' this.rendered = false;', ' }', '', ' Separator.prototype = new LogItem();', '', ' copyProperties(Separator.prototype, {', ' render: function() {', ' var containerDomNode = this.group.contentDiv;', ' if (isIe) {', ' this.unwrappedElementContainer = new SeparatorElementContainer(this.getUnwrappedDomContainer());', ' this.wrappedElementContainer = new SeparatorElementContainer(this.getWrappedDomContainer());', ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];', ' } else {', ' this.mainElementContainer = new SeparatorElementContainer(this.getMainDomContainer());', ' this.elementContainers = [this.mainElementContainer];', ' }', ' this.content = this.formattedMessage;', ' this.rendered = true;', ' }', ' });', '', ' /*----------------------------------------------------------------*/', '', ' function GroupElementContainer(group, containerDomNode, isRoot, isWrapped) {', ' this.group = group;', ' this.containerDomNode = containerDomNode;', ' this.isRoot = isRoot;', ' this.isWrapped = isWrapped;', ' this.expandable = false;', '', ' if (this.isRoot) {', ' if (isIe) {', ' this.contentDiv = logMainContainer.appendChild(document.createElement("div"));', ' this.contentDiv.id = this.isWrapped ? "log_wrapped" : "log_unwrapped";', ' } else {', ' this.contentDiv = logMainContainer;', ' }', ' } else {', ' var groupElementContainer = this;', ' ', ' this.mainDiv = document.createElement("div");', ' this.mainDiv.className = "group";', '', ' this.headingDiv = this.mainDiv.appendChild(document.createElement("div"));', ' this.headingDiv.className = "groupheading";', '', ' this.expander = this.headingDiv.appendChild(document.createElement("span"));', ' this.expander.className = "expander unselectable greyedout";', ' this.expander.unselectable = true;', ' var expanderText = this.group.expanded ? "-" : "+";', ' this.expanderTextNode = this.expander.appendChild(document.createTextNode(expanderText));', ' ', ' this.headingDiv.appendChild(document.createTextNode(" " + this.group.name));', '', ' this.contentDiv = this.mainDiv.appendChild(document.createElement("div"));', ' var contentCssClass = this.group.expanded ? "expanded" : "collapsed";', ' this.contentDiv.className = "groupcontent " + contentCssClass;', '', ' this.expander.onclick = function() {', ' if (groupElementContainer.group.expandable) {', ' groupElementContainer.group.toggleExpanded();', ' }', ' };', ' }', ' }', '', ' GroupElementContainer.prototype = new LogItemContainerElement();', '', ' copyProperties(GroupElementContainer.prototype, {', ' toggleExpanded: function() {', ' if (!this.isRoot) {', ' var oldCssClass, newCssClass, expanderText;', ' if (this.group.expanded) {', ' newCssClass = "expanded";', ' oldCssClass = "collapsed";', ' expanderText = "-";', ' } else {', ' newCssClass = "collapsed";', ' oldCssClass = "expanded";', ' expanderText = "+";', ' }', ' replaceClass(this.contentDiv, newCssClass, oldCssClass);', ' this.expanderTextNode.nodeValue = expanderText;', ' }', ' },', '', ' remove: function() {', ' if (!this.isRoot) {', ' this.headingDiv = null;', ' this.expander.onclick = null;', ' this.expander = null;', ' this.expanderTextNode = null;', ' this.contentDiv = null;', ' this.containerDomNode = null;', ' this.mainDiv.parentNode.removeChild(this.mainDiv);', ' this.mainDiv = null;', ' }', ' },', '', ' reverseChildren: function() {', ' // Invert the order of the log entries', ' var node = null;', '', ' // Remove all the log container nodes', ' var childDomNodes = [];', ' while ((node = this.contentDiv.firstChild)) {', ' this.contentDiv.removeChild(node);', ' childDomNodes.push(node);', ' }', '', ' // Put them all back in reverse order', ' while ((node = childDomNodes.pop())) {', ' this.contentDiv.appendChild(node);', ' }', ' },', '', ' update: function() {', ' if (!this.isRoot) {', ' if (this.group.expandable) {', ' removeClass(this.expander, "greyedout");', ' } else {', ' addClass(this.expander, "greyedout");', ' }', ' }', ' },', '', ' clear: function() {', ' if (this.isRoot) {', ' this.contentDiv.innerHTML = "";', ' }', ' }', ' });', '', ' /*----------------------------------------------------------------*/', '', ' function Group(name, isRoot, initiallyExpanded) {', ' this.name = name;', ' this.group = null;', ' this.isRoot = isRoot;', ' this.initiallyExpanded = initiallyExpanded;', ' this.elementContainers = [];', ' this.children = [];', ' this.expanded = initiallyExpanded;', ' this.rendered = false;', ' this.expandable = false;', ' }', '', ' Group.prototype = new LogItem();', '', ' copyProperties(Group.prototype, {', ' addChild: function(logItem) {', ' this.children.push(logItem);', ' logItem.group = this;', ' },', '', ' render: function() {', ' if (isIe) {', ' var unwrappedDomContainer, wrappedDomContainer;', ' if (this.isRoot) {', ' unwrappedDomContainer = logMainContainer;', ' wrappedDomContainer = logMainContainer;', ' } else {', ' unwrappedDomContainer = this.getUnwrappedDomContainer();', ' wrappedDomContainer = this.getWrappedDomContainer();', ' }', ' this.unwrappedElementContainer = new GroupElementContainer(this, unwrappedDomContainer, this.isRoot, false);', ' this.wrappedElementContainer = new GroupElementContainer(this, wrappedDomContainer, this.isRoot, true);', ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];', ' } else {', ' var mainDomContainer = this.isRoot ? logMainContainer : this.getMainDomContainer();', ' this.mainElementContainer = new GroupElementContainer(this, mainDomContainer, this.isRoot, false);', ' this.elementContainers = [this.mainElementContainer];', ' }', ' this.rendered = true;', ' },', '', ' toggleExpanded: function() {', ' this.expanded = !this.expanded;', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].toggleExpanded();', ' }', ' },', '', ' expand: function() {', ' if (!this.expanded) {', ' this.toggleExpanded();', ' }', ' },', '', ' accept: function(visitor) {', ' visitor.visitGroup(this);', ' },', '', ' reverseChildren: function() {', ' if (this.rendered) {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].reverseChildren();', ' }', ' }', ' },', '', ' update: function() {', ' var previouslyExpandable = this.expandable;', ' this.expandable = (this.children.length !== 0);', ' if (this.expandable !== previouslyExpandable) {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].update();', ' }', ' }', ' },', '', ' flatten: function() {', ' var visitor = new GroupFlattener();', ' this.accept(visitor);', ' return visitor.logEntriesAndSeparators;', ' },', '', ' removeChild: function(child, doUpdate) {', ' array_remove(this.children, child);', ' child.group = null;', ' if (doUpdate) {', ' this.update();', ' }', ' },', '', ' remove: function(doUpdate, removeFromGroup) {', ' for (var i = 0, len = this.children.length; i < len; i++) {', ' this.children[i].remove(false, false);', ' }', ' this.children = [];', ' this.update();', ' if (this === currentGroup) {', ' currentGroup = this.group;', ' }', ' this.doRemove(doUpdate, removeFromGroup);', ' },', '', ' serialize: function(items) {', ' items.push([LogItem.serializedItemKeys.GROUP_START, this.name]);', ' for (var i = 0, len = this.children.length; i < len; i++) {', ' this.children[i].serialize(items);', ' }', ' if (this !== currentGroup) {', ' items.push([LogItem.serializedItemKeys.GROUP_END]);', ' }', ' },', '', ' clear: function() {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].clear();', ' }', ' }', ' });', '', ' /*----------------------------------------------------------------*/', '', ' function LogEntryElementContainer() {', ' }', '', ' LogEntryElementContainer.prototype = new LogItemContainerElement();', '', ' copyProperties(LogEntryElementContainer.prototype, {', ' remove: function() {', ' this.doRemove();', ' },', '', ' doRemove: function() {', ' this.mainDiv.parentNode.removeChild(this.mainDiv);', ' this.mainDiv = null;', ' this.contentElement = null;', ' this.containerDomNode = null;', ' },', '', ' setContent: function(content, wrappedContent) {', ' if (content === this.formattedMessage) {', ' this.contentElement.innerHTML = "";', ' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));', ' } else {', ' this.contentElement.innerHTML = content;', ' }', ' },', '', ' setSearchMatch: function(isMatch) {', ' var oldCssClass = isMatch ? "searchnonmatch" : "searchmatch";', ' var newCssClass = isMatch ? "searchmatch" : "searchnonmatch";', ' replaceClass(this.mainDiv, newCssClass, oldCssClass);', ' },', '', ' clearSearch: function() {', ' removeClass(this.mainDiv, "searchmatch");', ' removeClass(this.mainDiv, "searchnonmatch");', ' }', ' });', '', ' /*----------------------------------------------------------------*/', '', ' function LogEntryWrappedElementContainer(logEntry, containerDomNode) {', ' this.logEntry = logEntry;', ' this.containerDomNode = containerDomNode;', ' this.mainDiv = document.createElement("div");', ' this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage));', ' this.mainDiv.className = "logentry wrapped " + this.logEntry.level;', ' this.contentElement = this.mainDiv;', ' }', '', ' LogEntryWrappedElementContainer.prototype = new LogEntryElementContainer();', '', ' LogEntryWrappedElementContainer.prototype.setContent = function(content, wrappedContent) {', ' if (content === this.formattedMessage) {', ' this.contentElement.innerHTML = "";', ' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));', ' } else {', ' this.contentElement.innerHTML = wrappedContent;', ' }', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function LogEntryUnwrappedElementContainer(logEntry, containerDomNode) {', ' this.logEntry = logEntry;', ' this.containerDomNode = containerDomNode;', ' this.mainDiv = document.createElement("div");', ' this.mainDiv.className = "logentry unwrapped " + this.logEntry.level;', ' this.pre = this.mainDiv.appendChild(document.createElement("pre"));', ' this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage));', ' this.pre.className = "unwrapped";', ' this.contentElement = this.pre;', ' }', '', ' LogEntryUnwrappedElementContainer.prototype = new LogEntryElementContainer();', '', ' LogEntryUnwrappedElementContainer.prototype.remove = function() {', ' this.doRemove();', ' this.pre = null;', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function LogEntryMainElementContainer(logEntry, containerDomNode) {', ' this.logEntry = logEntry;', ' this.containerDomNode = containerDomNode;', ' this.mainDiv = document.createElement("div");', ' this.mainDiv.className = "logentry nonielogentry " + this.logEntry.level;', ' this.contentElement = this.mainDiv.appendChild(document.createElement("span"));', ' this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage));', ' }', '', ' LogEntryMainElementContainer.prototype = new LogEntryElementContainer();', '', ' /*----------------------------------------------------------------*/', '', ' function LogEntry(level, formattedMessage) {', ' this.level = level;', ' this.formattedMessage = formattedMessage;', ' this.rendered = false;', ' }', '', ' LogEntry.prototype = new LogItem();', '', ' copyProperties(LogEntry.prototype, {', ' render: function() {', ' var logEntry = this;', ' var containerDomNode = this.group.contentDiv;', '', ' // Support for the CSS attribute white-space in IE for Windows is', ' // non-existent pre version 6 and slightly odd in 6, so instead', ' // use two different HTML elements', ' if (isIe) {', ' this.formattedMessage = this.formattedMessage.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space', ' this.unwrappedElementContainer = new LogEntryUnwrappedElementContainer(this, this.getUnwrappedDomContainer());', ' this.wrappedElementContainer = new LogEntryWrappedElementContainer(this, this.getWrappedDomContainer());', ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];', ' } else {', ' this.mainElementContainer = new LogEntryMainElementContainer(this, this.getMainDomContainer());', ' this.elementContainers = [this.mainElementContainer];', ' }', ' this.content = this.formattedMessage;', ' this.rendered = true;', ' },', '', ' setContent: function(content, wrappedContent) {', ' if (content != this.content) {', ' if (isIe && (content !== this.formattedMessage)) {', ' content = content.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space', ' }', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].setContent(content, wrappedContent);', ' }', ' this.content = content;', ' }', ' },', '', ' getSearchMatches: function() {', ' var matches = [];', ' var i, len;', ' if (isIe) {', ' var unwrappedEls = getElementsByClass(this.unwrappedElementContainer.mainDiv, "searchterm", "span");', ' var wrappedEls = getElementsByClass(this.wrappedElementContainer.mainDiv, "searchterm", "span");', ' for (i = 0, len = unwrappedEls.length; i < len; i++) {', ' matches[i] = new Match(this.level, null, unwrappedEls[i], wrappedEls[i]);', ' }', ' } else {', ' var els = getElementsByClass(this.mainElementContainer.mainDiv, "searchterm", "span");', ' for (i = 0, len = els.length; i < len; i++) {', ' matches[i] = new Match(this.level, els[i]);', ' }', ' }', ' return matches;', ' },', '', ' setSearchMatch: function(isMatch) {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].setSearchMatch(isMatch);', ' }', ' },', '', ' clearSearch: function() {', ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {', ' this.elementContainers[i].clearSearch();', ' }', ' },', '', ' accept: function(visitor) {', ' visitor.visitLogEntry(this);', ' },', '', ' serialize: function(items) {', ' items.push([LogItem.serializedItemKeys.LOG_ENTRY, this.level, this.formattedMessage]);', ' }', ' });', '', ' /*----------------------------------------------------------------*/', '', ' function LogItemVisitor() {', ' }', '', ' LogItemVisitor.prototype = {', ' visit: function(logItem) {', ' },', '', ' visitParent: function(logItem) {', ' if (logItem.group) {', ' logItem.group.accept(this);', ' }', ' },', '', ' visitChildren: function(logItem) {', ' for (var i = 0, len = logItem.children.length; i < len; i++) {', ' logItem.children[i].accept(this);', ' }', ' },', '', ' visitLogEntry: function(logEntry) {', ' this.visit(logEntry);', ' },', '', ' visitSeparator: function(separator) {', ' this.visit(separator);', ' },', '', ' visitGroup: function(group) {', ' this.visit(group);', ' }', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function GroupFlattener() {', ' this.logEntriesAndSeparators = [];', ' }', '', ' GroupFlattener.prototype = new LogItemVisitor();', '', ' GroupFlattener.prototype.visitGroup = function(group) {', ' this.visitChildren(group);', ' };', '', ' GroupFlattener.prototype.visitLogEntry = function(logEntry) {', ' this.logEntriesAndSeparators.push(logEntry);', ' };', '', ' GroupFlattener.prototype.visitSeparator = function(separator) {', ' this.logEntriesAndSeparators.push(separator);', ' };', '', ' /*----------------------------------------------------------------*/', '', ' window.onload = function() {', ' // Sort out document.domain', ' if (location.search) {', ' var queryBits = unescape(location.search).substr(1).split("&"), nameValueBits;', ' for (var i = 0, len = queryBits.length; i < len; i++) {', ' nameValueBits = queryBits[i].split("=");', ' if (nameValueBits[0] == "log4javascript_domain") {', ' document.domain = nameValueBits[1];', ' break;', ' }', ' }', ' }', '', ' // Create DOM objects', ' logMainContainer = $("log");', ' if (isIePre7) {', ' addClass(logMainContainer, "oldIe");', ' }', '', ' rootGroup = new Group("root", true);', ' rootGroup.render();', ' currentGroup = rootGroup;', ' ', ' setCommandInputWidth();', ' setLogContainerHeight();', ' toggleLoggingEnabled();', ' toggleSearchEnabled();', ' toggleSearchFilter();', ' toggleSearchHighlight();', ' applyFilters();', ' checkAllLevels();', ' toggleWrap();', ' toggleNewestAtTop();', ' toggleScrollToLatest();', ' renderQueuedLogItems();', ' loaded = true;', ' $("command").value = "";', ' $("command").autocomplete = "off";', ' $("command").onkeydown = function(evt) {', ' evt = getEvent(evt);', ' if (evt.keyCode == 10 || evt.keyCode == 13) { // Return/Enter', ' evalCommandLine();', ' stopPropagation(evt);', ' } else if (evt.keyCode == 27) { // Escape', ' this.value = "";', ' this.focus();', ' } else if (evt.keyCode == 38 && commandHistory.length > 0) { // Up', ' currentCommandIndex = Math.max(0, currentCommandIndex - 1);', ' this.value = commandHistory[currentCommandIndex];', ' moveCaretToEnd(this);', ' } else if (evt.keyCode == 40 && commandHistory.length > 0) { // Down', ' currentCommandIndex = Math.min(commandHistory.length - 1, currentCommandIndex + 1);', ' this.value = commandHistory[currentCommandIndex];', ' moveCaretToEnd(this);', ' }', ' };', '', ' // Prevent the keypress moving the caret in Firefox', ' $("command").onkeypress = function(evt) {', ' evt = getEvent(evt);', ' if (evt.keyCode == 38 && commandHistory.length > 0 && evt.preventDefault) { // Up', ' evt.preventDefault();', ' }', ' };', '', ' // Prevent the keyup event blurring the input in Opera', ' $("command").onkeyup = function(evt) {', ' evt = getEvent(evt);', ' if (evt.keyCode == 27 && evt.preventDefault) { // Up', ' evt.preventDefault();', ' this.focus();', ' }', ' };', '', ' // Add document keyboard shortcuts', ' document.onkeydown = function keyEventHandler(evt) {', ' evt = getEvent(evt);', ' switch (evt.keyCode) {', ' case 69: // Ctrl + shift + E: re-execute last command', ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {', ' evalLastCommand();', ' cancelKeyEvent(evt);', ' return false;', ' }', ' break;', ' case 75: // Ctrl + shift + K: focus search', ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {', ' focusSearch();', ' cancelKeyEvent(evt);', ' return false;', ' }', ' break;', ' case 40: // Ctrl + shift + down arrow: focus command line', ' case 76: // Ctrl + shift + L: focus command line', ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {', ' focusCommandLine();', ' cancelKeyEvent(evt);', ' return false;', ' }', ' break;', ' }', ' };', '', ' // Workaround to make sure log div starts at the correct size', ' setTimeout(setLogContainerHeight, 20);', '', ' setShowCommandLine(showCommandLine);', ' doSearch();', ' };', '', ' window.onunload = function() {', ' if (mainWindowExists()) {', ' appender.unload();', ' }', ' appender = null;', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function toggleLoggingEnabled() {', ' setLoggingEnabled($("enableLogging").checked);', ' }', '', ' function setLoggingEnabled(enable) {', ' loggingEnabled = enable;', ' }', '', ' var appender = null;', '', ' function setAppender(appenderParam) {', ' appender = appenderParam;', ' }', '', ' function setShowCloseButton(showCloseButton) {', ' $("closeButton").style.display = showCloseButton ? "inline" : "none";', ' }', '', ' function setShowHideButton(showHideButton) {', ' $("hideButton").style.display = showHideButton ? "inline" : "none";', ' }', '', ' var newestAtTop = false;', '', ' /*----------------------------------------------------------------*/', '', ' function LogItemContentReverser() {', ' }', ' ', ' LogItemContentReverser.prototype = new LogItemVisitor();', ' ', ' LogItemContentReverser.prototype.visitGroup = function(group) {', ' group.reverseChildren();', ' this.visitChildren(group);', ' };', '', ' /*----------------------------------------------------------------*/', '', ' function setNewestAtTop(isNewestAtTop) {', ' var oldNewestAtTop = newestAtTop;', ' var i, iLen, j, jLen;', ' newestAtTop = Boolean(isNewestAtTop);', ' if (oldNewestAtTop != newestAtTop) {', ' var visitor = new LogItemContentReverser();', ' rootGroup.accept(visitor);', '', ' // Reassemble the matches array', ' if (currentSearch) {', ' var currentMatch = currentSearch.matches[currentMatchIndex];', ' var matchIndex = 0;', ' var matches = [];', ' var actOnLogEntry = function(logEntry) {', ' var logEntryMatches = logEntry.getSearchMatches();', ' for (j = 0, jLen = logEntryMatches.length; j < jLen; j++) {', ' matches[matchIndex] = logEntryMatches[j];', ' if (currentMatch && logEntryMatches[j].equals(currentMatch)) {', ' currentMatchIndex = matchIndex;', ' }', ' matchIndex++;', ' }', ' };', ' if (newestAtTop) {', ' for (i = logEntries.length - 1; i >= 0; i--) {', ' actOnLogEntry(logEntries[i]);', ' }', ' } else {', ' for (i = 0, iLen = logEntries.length; i < iLen; i++) {', ' actOnLogEntry(logEntries[i]);', ' }', ' }', ' currentSearch.matches = matches;', ' if (currentMatch) {', ' currentMatch.setCurrent();', ' }', ' } else if (scrollToLatest) {', ' doScrollToLatest();', ' }', ' }', ' $("newestAtTop").checked = isNewestAtTop;', ' }', '', ' function toggleNewestAtTop() {', ' var isNewestAtTop = $("newestAtTop").checked;', ' setNewestAtTop(isNewestAtTop);', ' }', '', ' var scrollToLatest = true;', '', ' function setScrollToLatest(isScrollToLatest) {', ' scrollToLatest = isScrollToLatest;', ' if (scrollToLatest) {', ' doScrollToLatest();', ' }', ' $("scrollToLatest").checked = isScrollToLatest;', ' }', '', ' function toggleScrollToLatest() {', ' var isScrollToLatest = $("scrollToLatest").checked;', ' setScrollToLatest(isScrollToLatest);', ' }', '', ' function doScrollToLatest() {', ' var l = logMainContainer;', ' if (typeof l.scrollTop != "undefined") {', ' if (newestAtTop) {', ' l.scrollTop = 0;', ' } else {', ' var latestLogEntry = l.lastChild;', ' if (latestLogEntry) {', ' l.scrollTop = l.scrollHeight;', ' }', ' }', ' }', ' }', '', ' var closeIfOpenerCloses = true;', '', ' function setCloseIfOpenerCloses(isCloseIfOpenerCloses) {', ' closeIfOpenerCloses = isCloseIfOpenerCloses;', ' }', '', ' var maxMessages = null;', '', ' function setMaxMessages(max) {', ' maxMessages = max;', ' pruneLogEntries();', ' }', '', ' var showCommandLine = false;', '', ' function setShowCommandLine(isShowCommandLine) {', ' showCommandLine = isShowCommandLine;', ' if (loaded) {', ' $("commandLine").style.display = showCommandLine ? "block" : "none";', ' setCommandInputWidth();', ' setLogContainerHeight();', ' }', ' }', '', ' function focusCommandLine() {', ' if (loaded) {', ' $("command").focus();', ' }', ' }', '', ' function focusSearch() {', ' if (loaded) {', ' $("searchBox").focus();', ' }', ' }', '', ' function getLogItems() {', ' var items = [];', ' for (var i = 0, len = logItems.length; i < len; i++) {', ' logItems[i].serialize(items);', ' }', ' return items;', ' }', '', ' function setLogItems(items) {', ' var loggingReallyEnabled = loggingEnabled;', ' // Temporarily turn logging on', ' loggingEnabled = true;', ' for (var i = 0, len = items.length; i < len; i++) {', ' switch (items[i][0]) {', ' case LogItem.serializedItemKeys.LOG_ENTRY:', ' log(items[i][1], items[i][2]);', ' break;', ' case LogItem.serializedItemKeys.GROUP_START:', ' group(items[i][1]);', ' break;', ' case LogItem.serializedItemKeys.GROUP_END:', ' groupEnd();', ' break;', ' }', ' }', ' loggingEnabled = loggingReallyEnabled;', ' }', '', ' function log(logLevel, formattedMessage) {', ' if (loggingEnabled) {', ' var logEntry = new LogEntry(logLevel, formattedMessage);', ' logEntries.push(logEntry);', ' logEntriesAndSeparators.push(logEntry);', ' logItems.push(logEntry);', ' currentGroup.addChild(logEntry);', ' if (loaded) {', ' if (logQueuedEventsTimer !== null) {', ' clearTimeout(logQueuedEventsTimer);', ' }', ' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);', ' unrenderedLogItemsExist = true;', ' }', ' }', ' }', '', ' function renderQueuedLogItems() {', ' logQueuedEventsTimer = null;', ' var pruned = pruneLogEntries();', '', ' // Render any unrendered log entries and apply the current search to them', ' var initiallyHasMatches = currentSearch ? currentSearch.hasMatches() : false;', ' for (var i = 0, len = logItems.length; i < len; i++) {', ' if (!logItems[i].rendered) {', ' logItems[i].render();', ' logItems[i].appendToLog();', ' if (currentSearch && (logItems[i] instanceof LogEntry)) {', ' currentSearch.applyTo(logItems[i]);', ' }', ' }', ' }', ' if (currentSearch) {', ' if (pruned) {', ' if (currentSearch.hasVisibleMatches()) {', ' if (currentMatchIndex === null) {', ' setCurrentMatchIndex(0);', ' }', ' displayMatches();', ' } else {', ' displayNoMatches();', ' }', ' } else if (!initiallyHasMatches && currentSearch.hasVisibleMatches()) {', ' setCurrentMatchIndex(0);', ' displayMatches();', ' }', ' }', ' if (scrollToLatest) {', ' doScrollToLatest();', ' }', ' unrenderedLogItemsExist = false;', ' }', '', ' function pruneLogEntries() {', ' if ((maxMessages !== null) && (logEntriesAndSeparators.length > maxMessages)) {', ' var numberToDelete = logEntriesAndSeparators.length - maxMessages;', ' var prunedLogEntries = logEntriesAndSeparators.slice(0, numberToDelete);', ' if (currentSearch) {', ' currentSearch.removeMatches(prunedLogEntries);', ' }', ' var group;', ' for (var i = 0; i < numberToDelete; i++) {', ' group = logEntriesAndSeparators[i].group;', ' array_remove(logItems, logEntriesAndSeparators[i]);', ' array_remove(logEntries, logEntriesAndSeparators[i]);', ' logEntriesAndSeparators[i].remove(true, true);', ' if (group.children.length === 0 && group !== currentGroup && group !== rootGroup) {', ' array_remove(logItems, group);', ' group.remove(true, true);', ' }', ' }', ' logEntriesAndSeparators = array_removeFromStart(logEntriesAndSeparators, numberToDelete);', ' return true;', ' }', ' return false;', ' }', '', ' function group(name, startExpanded) {', ' if (loggingEnabled) {', ' initiallyExpanded = (typeof startExpanded === "undefined") ? true : Boolean(startExpanded);', ' var newGroup = new Group(name, false, initiallyExpanded);', ' currentGroup.addChild(newGroup);', ' currentGroup = newGroup;', ' logItems.push(newGroup);', ' if (loaded) {', ' if (logQueuedEventsTimer !== null) {', ' clearTimeout(logQueuedEventsTimer);', ' }', ' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);', ' unrenderedLogItemsExist = true;', ' }', ' }', ' }', '', ' function groupEnd() {', ' currentGroup = (currentGroup === rootGroup) ? rootGroup : currentGroup.group;', ' }', '', ' function mainPageReloaded() {', ' currentGroup = rootGroup;', ' var separator = new Separator();', ' logEntriesAndSeparators.push(separator);', ' logItems.push(separator);', ' currentGroup.addChild(separator);', ' }', '', ' function closeWindow() {', ' if (appender && mainWindowExists()) {', ' appender.close(true);', ' } else {', ' window.close();', ' }', ' }', '', ' function hide() {', ' if (appender && mainWindowExists()) {', ' appender.hide();', ' }', ' }', '', ' var mainWindow = window;', ' var windowId = "log4javascriptConsoleWindow_" + new Date().getTime() + "_" + ("" + Math.random()).substr(2);', '', ' function setMainWindow(win) {', ' mainWindow = win;', ' mainWindow[windowId] = window;', ' // If this is a pop-up, poll the opener to see if it\'s closed', ' if (opener && closeIfOpenerCloses) {', ' pollOpener();', ' }', ' }', '', ' function pollOpener() {', ' if (closeIfOpenerCloses) {', ' if (mainWindowExists()) {', ' setTimeout(pollOpener, 500);', ' } else {', ' closeWindow();', ' }', ' }', ' }', '', ' function mainWindowExists() {', ' try {', ' return (mainWindow && !mainWindow.closed &&', ' mainWindow[windowId] == window);', ' } catch (ex) {}', ' return false;', ' }', '', ' var logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];', '', ' function getCheckBox(logLevel) {', ' return $("switch_" + logLevel);', ' }', '', ' function getIeWrappedLogContainer() {', ' return $("log_wrapped");', ' }', '', ' function getIeUnwrappedLogContainer() {', ' return $("log_unwrapped");', ' }', '', ' function applyFilters() {', ' for (var i = 0; i < logLevels.length; i++) {', ' if (getCheckBox(logLevels[i]).checked) {', ' addClass(logMainContainer, logLevels[i]);', ' } else {', ' removeClass(logMainContainer, logLevels[i]);', ' }', ' }', ' updateSearchFromFilters();', ' }', '', ' function toggleAllLevels() {', ' var turnOn = $("switch_ALL").checked;', ' for (var i = 0; i < logLevels.length; i++) {', ' getCheckBox(logLevels[i]).checked = turnOn;', ' if (turnOn) {', ' addClass(logMainContainer, logLevels[i]);', ' } else {', ' removeClass(logMainContainer, logLevels[i]);', ' }', ' }', ' }', '', ' function checkAllLevels() {', ' for (var i = 0; i < logLevels.length; i++) {', ' if (!getCheckBox(logLevels[i]).checked) {', ' getCheckBox("ALL").checked = false;', ' return;', ' }', ' }', ' getCheckBox("ALL").checked = true;', ' }', '', ' function clearLog() {', ' rootGroup.clear();', ' currentGroup = rootGroup;', ' logEntries = [];', ' logItems = [];', ' logEntriesAndSeparators = [];', ' doSearch();', ' }', '', ' function toggleWrap() {', ' var enable = $("wrap").checked;', ' if (enable) {', ' addClass(logMainContainer, "wrap");', ' } else {', ' removeClass(logMainContainer, "wrap");', ' }', ' refreshCurrentMatch();', ' }', '', ' /* ------------------------------------------------------------------- */', '', ' // Search', '', ' var searchTimer = null;', '', ' function scheduleSearch() {', ' try {', ' clearTimeout(searchTimer);', ' } catch (ex) {', ' // Do nothing', ' }', ' searchTimer = setTimeout(doSearch, 500);', ' }', '', ' function Search(searchTerm, isRegex, searchRegex, isCaseSensitive) {', ' this.searchTerm = searchTerm;', ' this.isRegex = isRegex;', ' this.searchRegex = searchRegex;', ' this.isCaseSensitive = isCaseSensitive;', ' this.matches = [];', ' }', '', ' Search.prototype = {', ' hasMatches: function() {', ' return this.matches.length > 0;', ' },', '', ' hasVisibleMatches: function() {', ' if (this.hasMatches()) {', ' for (var i = 0; i < this.matches.length; i++) {', ' if (this.matches[i].isVisible()) {', ' return true;', ' }', ' }', ' }', ' return false;', ' },', '', ' match: function(logEntry) {', ' var entryText = String(logEntry.formattedMessage);', ' var matchesSearch = false;', ' if (this.isRegex) {', ' matchesSearch = this.searchRegex.test(entryText);', ' } else if (this.isCaseSensitive) {', ' matchesSearch = (entryText.indexOf(this.searchTerm) > -1);', ' } else {', ' matchesSearch = (entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1);', ' }', ' return matchesSearch;', ' },', '', ' getNextVisibleMatchIndex: function() {', ' for (var i = currentMatchIndex + 1; i < this.matches.length; i++) {', ' if (this.matches[i].isVisible()) {', ' return i;', ' }', ' }', ' // Start again from the first match', ' for (i = 0; i <= currentMatchIndex; i++) {', ' if (this.matches[i].isVisible()) {', ' return i;', ' }', ' }', ' return -1;', ' },', '', ' getPreviousVisibleMatchIndex: function() {', ' for (var i = currentMatchIndex - 1; i >= 0; i--) {', ' if (this.matches[i].isVisible()) {', ' return i;', ' }', ' }', ' // Start again from the last match', ' for (var i = this.matches.length - 1; i >= currentMatchIndex; i--) {', ' if (this.matches[i].isVisible()) {', ' return i;', ' }', ' }', ' return -1;', ' },', '', ' applyTo: function(logEntry) {', ' var doesMatch = this.match(logEntry);', ' if (doesMatch) {', ' logEntry.group.expand();', ' logEntry.setSearchMatch(true);', ' var logEntryContent;', ' var wrappedLogEntryContent;', ' var searchTermReplacementStartTag = "<span class=\\\"searchterm\\\">";', ' var searchTermReplacementEndTag = "<" + "/span>";', ' var preTagName = isIe ? "pre" : "span";', ' var preStartTag = "<" + preTagName + " class=\\\"pre\\\">";', ' var preEndTag = "<" + "/" + preTagName + ">";', ' var startIndex = 0;', ' var searchIndex, matchedText, textBeforeMatch;', ' if (this.isRegex) {', ' var flags = this.isCaseSensitive ? "g" : "gi";', ' var capturingRegex = new RegExp("(" + this.searchRegex.source + ")", flags);', '', ' // Replace the search term with temporary tokens for the start and end tags', ' var rnd = ("" + Math.random()).substr(2);', ' var startToken = "%%s" + rnd + "%%";', ' var endToken = "%%e" + rnd + "%%";', ' logEntryContent = logEntry.formattedMessage.replace(capturingRegex, startToken + "$1" + endToken);', '', ' // Escape the HTML to get rid of angle brackets', ' logEntryContent = escapeHtml(logEntryContent);', '', ' // Substitute the proper HTML back in for the search match', ' var result;', ' var searchString = logEntryContent;', ' logEntryContent = "";', ' wrappedLogEntryContent = "";', ' while ((searchIndex = searchString.indexOf(startToken, startIndex)) > -1) {', ' var endTokenIndex = searchString.indexOf(endToken, searchIndex);', ' matchedText = searchString.substring(searchIndex + startToken.length, endTokenIndex);', ' textBeforeMatch = searchString.substring(startIndex, searchIndex);', ' logEntryContent += preStartTag + textBeforeMatch + preEndTag;', ' logEntryContent += searchTermReplacementStartTag + preStartTag + matchedText +', ' preEndTag + searchTermReplacementEndTag;', ' if (isIe) {', ' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +', ' matchedText + searchTermReplacementEndTag;', ' }', ' startIndex = endTokenIndex + endToken.length;', ' }', ' logEntryContent += preStartTag + searchString.substr(startIndex) + preEndTag;', ' if (isIe) {', ' wrappedLogEntryContent += searchString.substr(startIndex);', ' }', ' } else {', ' logEntryContent = "";', ' wrappedLogEntryContent = "";', ' var searchTermReplacementLength = searchTermReplacementStartTag.length +', ' this.searchTerm.length + searchTermReplacementEndTag.length;', ' var searchTermLength = this.searchTerm.length;', ' var searchTermLowerCase = this.searchTerm.toLowerCase();', ' var logTextLowerCase = logEntry.formattedMessage.toLowerCase();', ' while ((searchIndex = logTextLowerCase.indexOf(searchTermLowerCase, startIndex)) > -1) {', ' matchedText = escapeHtml(logEntry.formattedMessage.substr(searchIndex, this.searchTerm.length));', ' textBeforeMatch = escapeHtml(logEntry.formattedMessage.substring(startIndex, searchIndex));', ' var searchTermReplacement = searchTermReplacementStartTag +', ' preStartTag + matchedText + preEndTag + searchTermReplacementEndTag;', ' logEntryContent += preStartTag + textBeforeMatch + preEndTag + searchTermReplacement;', ' if (isIe) {', ' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +', ' matchedText + searchTermReplacementEndTag;', ' }', ' startIndex = searchIndex + searchTermLength;', ' }', ' var textAfterLastMatch = escapeHtml(logEntry.formattedMessage.substr(startIndex));', ' logEntryContent += preStartTag + textAfterLastMatch + preEndTag;', ' if (isIe) {', ' wrappedLogEntryContent += textAfterLastMatch;', ' }', ' }', ' logEntry.setContent(logEntryContent, wrappedLogEntryContent);', ' var logEntryMatches = logEntry.getSearchMatches();', ' this.matches = this.matches.concat(logEntryMatches);', ' } else {', ' logEntry.setSearchMatch(false);', ' logEntry.setContent(logEntry.formattedMessage, logEntry.formattedMessage);', ' }', ' return doesMatch;', ' },', '', ' removeMatches: function(logEntries) {', ' var matchesToRemoveCount = 0;', ' var currentMatchRemoved = false;', ' var matchesToRemove = [];', ' var i, iLen, j, jLen;', '', ' // Establish the list of matches to be removed', ' for (i = 0, iLen = this.matches.length; i < iLen; i++) {', ' for (j = 0, jLen = logEntries.length; j < jLen; j++) {', ' if (this.matches[i].belongsTo(logEntries[j])) {', ' matchesToRemove.push(this.matches[i]);', ' if (i === currentMatchIndex) {', ' currentMatchRemoved = true;', ' }', ' }', ' }', ' }', '', ' // Set the new current match index if the current match has been deleted', ' // This will be the first match that appears after the first log entry being', ' // deleted, if one exists; otherwise, it\'s the first match overall', ' var newMatch = currentMatchRemoved ? null : this.matches[currentMatchIndex];', ' if (currentMatchRemoved) {', ' for (i = currentMatchIndex, iLen = this.matches.length; i < iLen; i++) {', ' if (this.matches[i].isVisible() && !array_contains(matchesToRemove, this.matches[i])) {', ' newMatch = this.matches[i];', ' break;', ' }', ' }', ' }', '', ' // Remove the matches', ' for (i = 0, iLen = matchesToRemove.length; i < iLen; i++) {', ' array_remove(this.matches, matchesToRemove[i]);', ' matchesToRemove[i].remove();', ' }', '', ' // Set the new match, if one exists', ' if (this.hasVisibleMatches()) {', ' if (newMatch === null) {', ' setCurrentMatchIndex(0);', ' } else {', ' // Get the index of the new match', ' var newMatchIndex = 0;', ' for (i = 0, iLen = this.matches.length; i < iLen; i++) {', ' if (newMatch === this.matches[i]) {', ' newMatchIndex = i;', ' break;', ' }', ' }', ' setCurrentMatchIndex(newMatchIndex);', ' }', ' } else {', ' currentMatchIndex = null;', ' displayNoMatches();', ' }', ' }', ' };', '', ' function getPageOffsetTop(el, container) {', ' var currentEl = el;', ' var y = 0;', ' while (currentEl && currentEl != container) {', ' y += currentEl.offsetTop;', ' currentEl = currentEl.offsetParent;', ' }', ' return y;', ' }', '', ' function scrollIntoView(el) {', ' var logContainer = logMainContainer;', ' // Check if the whole width of the element is visible and centre if not', ' if (!$("wrap").checked) {', ' var logContainerLeft = logContainer.scrollLeft;', ' var logContainerRight = logContainerLeft + logContainer.offsetWidth;', ' var elLeft = el.offsetLeft;', ' var elRight = elLeft + el.offsetWidth;', ' if (elLeft < logContainerLeft || elRight > logContainerRight) {', ' logContainer.scrollLeft = elLeft - (logContainer.offsetWidth - el.offsetWidth) / 2;', ' }', ' }', ' // Check if the whole height of the element is visible and centre if not', ' var logContainerTop = logContainer.scrollTop;', ' var logContainerBottom = logContainerTop + logContainer.offsetHeight;', ' var elTop = getPageOffsetTop(el) - getToolBarsHeight();', ' var elBottom = elTop + el.offsetHeight;', ' if (elTop < logContainerTop || elBottom > logContainerBottom) {', ' logContainer.scrollTop = elTop - (logContainer.offsetHeight - el.offsetHeight) / 2;', ' }', ' }', '', ' function Match(logEntryLevel, spanInMainDiv, spanInUnwrappedPre, spanInWrappedDiv) {', ' this.logEntryLevel = logEntryLevel;', ' this.spanInMainDiv = spanInMainDiv;', ' if (isIe) {', ' this.spanInUnwrappedPre = spanInUnwrappedPre;', ' this.spanInWrappedDiv = spanInWrappedDiv;', ' }', ' this.mainSpan = isIe ? spanInUnwrappedPre : spanInMainDiv;', ' }', '', ' Match.prototype = {', ' equals: function(match) {', ' return this.mainSpan === match.mainSpan;', ' },', '', ' setCurrent: function() {', ' if (isIe) {', ' addClass(this.spanInUnwrappedPre, "currentmatch");', ' addClass(this.spanInWrappedDiv, "currentmatch");', ' // Scroll the visible one into view', ' var elementToScroll = $("wrap").checked ? this.spanInWrappedDiv : this.spanInUnwrappedPre;', ' scrollIntoView(elementToScroll);', ' } else {', ' addClass(this.spanInMainDiv, "currentmatch");', ' scrollIntoView(this.spanInMainDiv);', ' }', ' },', '', ' belongsTo: function(logEntry) {', ' if (isIe) {', ' return isDescendant(this.spanInUnwrappedPre, logEntry.unwrappedPre);', ' } else {', ' return isDescendant(this.spanInMainDiv, logEntry.mainDiv);', ' }', ' },', '', ' setNotCurrent: function() {', ' if (isIe) {', ' removeClass(this.spanInUnwrappedPre, "currentmatch");', ' removeClass(this.spanInWrappedDiv, "currentmatch");', ' } else {', ' removeClass(this.spanInMainDiv, "currentmatch");', ' }', ' },', '', ' isOrphan: function() {', ' return isOrphan(this.mainSpan);', ' },', '', ' isVisible: function() {', ' return getCheckBox(this.logEntryLevel).checked;', ' },', '', ' remove: function() {', ' if (isIe) {', ' this.spanInUnwrappedPre = null;', ' this.spanInWrappedDiv = null;', ' } else {', ' this.spanInMainDiv = null;', ' }', ' }', ' };', '', ' var currentSearch = null;', ' var currentMatchIndex = null;', '', ' function doSearch() {', ' var searchBox = $("searchBox");', ' var searchTerm = searchBox.value;', ' var isRegex = $("searchRegex").checked;', ' var isCaseSensitive = $("searchCaseSensitive").checked;', ' var i;', '', ' if (searchTerm === "") {', ' $("searchReset").disabled = true;', ' $("searchNav").style.display = "none";', ' removeClass(document.body, "searching");', ' removeClass(searchBox, "hasmatches");', ' removeClass(searchBox, "nomatches");', ' for (i = 0; i < logEntries.length; i++) {', ' logEntries[i].clearSearch();', ' logEntries[i].setContent(logEntries[i].formattedMessage, logEntries[i].formattedMessage);', ' }', ' currentSearch = null;', ' setLogContainerHeight();', ' } else {', ' $("searchReset").disabled = false;', ' $("searchNav").style.display = "block";', ' var searchRegex;', ' var regexValid;', ' if (isRegex) {', ' try {', ' searchRegex = isCaseSensitive ? new RegExp(searchTerm, "g") : new RegExp(searchTerm, "gi");', ' regexValid = true;', ' replaceClass(searchBox, "validregex", "invalidregex");', ' searchBox.title = "Valid regex";', ' } catch (ex) {', ' regexValid = false;', ' replaceClass(searchBox, "invalidregex", "validregex");', ' searchBox.title = "Invalid regex: " + (ex.message ? ex.message : (ex.description ? ex.description : "unknown error"));', ' return;', ' }', ' } else {', ' searchBox.title = "";', ' removeClass(searchBox, "validregex");', ' removeClass(searchBox, "invalidregex");', ' }', ' addClass(document.body, "searching");', ' currentSearch = new Search(searchTerm, isRegex, searchRegex, isCaseSensitive);', ' for (i = 0; i < logEntries.length; i++) {', ' currentSearch.applyTo(logEntries[i]);', ' }', ' setLogContainerHeight();', '', ' // Highlight the first search match', ' if (currentSearch.hasVisibleMatches()) {', ' setCurrentMatchIndex(0);', ' displayMatches();', ' } else {', ' displayNoMatches();', ' }', ' }', ' }', '', ' function updateSearchFromFilters() {', ' if (currentSearch) {', ' if (currentSearch.hasMatches()) {', ' if (currentMatchIndex === null) {', ' currentMatchIndex = 0;', ' }', ' var currentMatch = currentSearch.matches[currentMatchIndex];', ' if (currentMatch.isVisible()) {', ' displayMatches();', ' setCurrentMatchIndex(currentMatchIndex);', ' } else {', ' currentMatch.setNotCurrent();', ' // Find the next visible match, if one exists', ' var nextVisibleMatchIndex = currentSearch.getNextVisibleMatchIndex();', ' if (nextVisibleMatchIndex > -1) {', ' setCurrentMatchIndex(nextVisibleMatchIndex);', ' displayMatches();', ' } else {', ' displayNoMatches();', ' }', ' }', ' } else {', ' displayNoMatches();', ' }', ' }', ' }', '', ' function refreshCurrentMatch() {', ' if (currentSearch && currentSearch.hasVisibleMatches()) {', ' setCurrentMatchIndex(currentMatchIndex);', ' }', ' }', '', ' function displayMatches() {', ' replaceClass($("searchBox"), "hasmatches", "nomatches");', ' $("searchBox").title = "" + currentSearch.matches.length + " matches found";', ' $("searchNav").style.display = "block";', ' setLogContainerHeight();', ' }', '', ' function displayNoMatches() {', ' replaceClass($("searchBox"), "nomatches", "hasmatches");', ' $("searchBox").title = "No matches found";', ' $("searchNav").style.display = "none";', ' setLogContainerHeight();', ' }', '', ' function toggleSearchEnabled(enable) {', ' enable = (typeof enable == "undefined") ? !$("searchDisable").checked : enable;', ' $("searchBox").disabled = !enable;', ' $("searchReset").disabled = !enable;', ' $("searchRegex").disabled = !enable;', ' $("searchNext").disabled = !enable;', ' $("searchPrevious").disabled = !enable;', ' $("searchCaseSensitive").disabled = !enable;', ' $("searchNav").style.display = (enable && ($("searchBox").value !== "") &&', ' currentSearch && currentSearch.hasVisibleMatches()) ?', ' "block" : "none";', ' if (enable) {', ' removeClass($("search"), "greyedout");', ' addClass(document.body, "searching");', ' if ($("searchHighlight").checked) {', ' addClass(logMainContainer, "searchhighlight");', ' } else {', ' removeClass(logMainContainer, "searchhighlight");', ' }', ' if ($("searchFilter").checked) {', ' addClass(logMainContainer, "searchfilter");', ' } else {', ' removeClass(logMainContainer, "searchfilter");', ' }', ' $("searchDisable").checked = !enable;', ' } else {', ' addClass($("search"), "greyedout");', ' removeClass(document.body, "searching");', ' removeClass(logMainContainer, "searchhighlight");', ' removeClass(logMainContainer, "searchfilter");', ' }', ' setLogContainerHeight();', ' }', '', ' function toggleSearchFilter() {', ' var enable = $("searchFilter").checked;', ' if (enable) {', ' addClass(logMainContainer, "searchfilter");', ' } else {', ' removeClass(logMainContainer, "searchfilter");', ' }', ' refreshCurrentMatch();', ' }', '', ' function toggleSearchHighlight() {', ' var enable = $("searchHighlight").checked;', ' if (enable) {', ' addClass(logMainContainer, "searchhighlight");', ' } else {', ' removeClass(logMainContainer, "searchhighlight");', ' }', ' }', '', ' function clearSearch() {', ' $("searchBox").value = "";', ' doSearch();', ' }', '', ' function searchNext() {', ' if (currentSearch !== null && currentMatchIndex !== null) {', ' currentSearch.matches[currentMatchIndex].setNotCurrent();', ' var nextMatchIndex = currentSearch.getNextVisibleMatchIndex();', ' if (nextMatchIndex > currentMatchIndex || confirm("Reached the end of the page. Start from the top?")) {', ' setCurrentMatchIndex(nextMatchIndex);', ' }', ' }', ' }', '', ' function searchPrevious() {', ' if (currentSearch !== null && currentMatchIndex !== null) {', ' currentSearch.matches[currentMatchIndex].setNotCurrent();', ' var previousMatchIndex = currentSearch.getPreviousVisibleMatchIndex();', ' if (previousMatchIndex < currentMatchIndex || confirm("Reached the start of the page. Continue from the bottom?")) {', ' setCurrentMatchIndex(previousMatchIndex);', ' }', ' }', ' }', '', ' function setCurrentMatchIndex(index) {', ' currentMatchIndex = index;', ' currentSearch.matches[currentMatchIndex].setCurrent();', ' }', '', ' /* ------------------------------------------------------------------------- */', '', ' // CSS Utilities', '', ' function addClass(el, cssClass) {', ' if (!hasClass(el, cssClass)) {', ' if (el.className) {', ' el.className += " " + cssClass;', ' } else {', ' el.className = cssClass;', ' }', ' }', ' }', '', ' function hasClass(el, cssClass) {', ' if (el.className) {', ' var classNames = el.className.split(" ");', ' return array_contains(classNames, cssClass);', ' }', ' return false;', ' }', '', ' function removeClass(el, cssClass) {', ' if (hasClass(el, cssClass)) {', ' // Rebuild the className property', ' var existingClasses = el.className.split(" ");', ' var newClasses = [];', ' for (var i = 0, len = existingClasses.length; i < len; i++) {', ' if (existingClasses[i] != cssClass) {', ' newClasses[newClasses.length] = existingClasses[i];', ' }', ' }', ' el.className = newClasses.join(" ");', ' }', ' }', '', ' function replaceClass(el, newCssClass, oldCssClass) {', ' removeClass(el, oldCssClass);', ' addClass(el, newCssClass);', ' }', '', ' /* ------------------------------------------------------------------------- */', '', ' // Other utility functions', '', ' function getElementsByClass(el, cssClass, tagName) {', ' var elements = el.getElementsByTagName(tagName);', ' var matches = [];', ' for (var i = 0, len = elements.length; i < len; i++) {', ' if (hasClass(elements[i], cssClass)) {', ' matches.push(elements[i]);', ' }', ' }', ' return matches;', ' }', '', ' // Syntax borrowed from Prototype library', ' function $(id) {', ' return document.getElementById(id);', ' }', '', ' function isDescendant(node, ancestorNode) {', ' while (node != null) {', ' if (node === ancestorNode) {', ' return true;', ' }', ' node = node.parentNode;', ' }', ' return false;', ' }', '', ' function isOrphan(node) {', ' var currentNode = node;', ' while (currentNode) {', ' if (currentNode == document.body) {', ' return false;', ' }', ' currentNode = currentNode.parentNode;', ' }', ' return true;', ' }', '', ' function escapeHtml(str) {', ' return str.replace(/&/g, "&").replace(/[<]/g, "<").replace(/>/g, ">");', ' }', '', ' function getWindowWidth() {', ' if (window.innerWidth) {', ' return window.innerWidth;', ' } else if (document.documentElement && document.documentElement.clientWidth) {', ' return document.documentElement.clientWidth;', ' } else if (document.body) {', ' return document.body.clientWidth;', ' }', ' return 0;', ' }', '', ' function getWindowHeight() {', ' if (window.innerHeight) {', ' return window.innerHeight;', ' } else if (document.documentElement && document.documentElement.clientHeight) {', ' return document.documentElement.clientHeight;', ' } else if (document.body) {', ' return document.body.clientHeight;', ' }', ' return 0;', ' }', '', ' function getToolBarsHeight() {', ' return $("switches").offsetHeight;', ' }', '', ' function getChromeHeight() {', ' var height = getToolBarsHeight();', ' if (showCommandLine) {', ' height += $("commandLine").offsetHeight;', ' }', ' return height;', ' }', '', ' function setLogContainerHeight() {', ' if (logMainContainer) {', ' var windowHeight = getWindowHeight();', ' $("body").style.height = getWindowHeight() + "px";', ' logMainContainer.style.height = "" +', ' Math.max(0, windowHeight - getChromeHeight()) + "px";', ' }', ' }', '', ' function setCommandInputWidth() {', ' if (showCommandLine) {', ' $("command").style.width = "" + Math.max(0, $("commandLineContainer").offsetWidth -', ' ($("evaluateButton").offsetWidth + 13)) + "px";', ' }', ' }', '', ' window.onresize = function() {', ' setCommandInputWidth();', ' setLogContainerHeight();', ' };', '', ' if (!Array.prototype.push) {', ' Array.prototype.push = function() {', ' for (var i = 0, len = arguments.length; i < len; i++){', ' this[this.length] = arguments[i];', ' }', ' return this.length;', ' };', ' }', '', ' if (!Array.prototype.pop) {', ' Array.prototype.pop = function() {', ' if (this.length > 0) {', ' var val = this[this.length - 1];', ' this.length = this.length - 1;', ' return val;', ' }', ' };', ' }', '', ' if (!Array.prototype.shift) {', ' Array.prototype.shift = function() {', ' if (this.length > 0) {', ' var firstItem = this[0];', ' for (var i = 0, len = this.length - 1; i < len; i++) {', ' this[i] = this[i + 1];', ' }', ' this.length = this.length - 1;', ' return firstItem;', ' }', ' };', ' }', '', ' if (!Array.prototype.splice) {', ' Array.prototype.splice = function(startIndex, deleteCount) {', ' var itemsAfterDeleted = this.slice(startIndex + deleteCount);', ' var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);', ' this.length = startIndex;', ' // Copy the arguments into a proper Array object', ' var argumentsArray = [];', ' for (var i = 0, len = arguments.length; i < len; i++) {', ' argumentsArray[i] = arguments[i];', ' }', ' var itemsToAppend = (argumentsArray.length > 2) ?', ' itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;', ' for (i = 0, len = itemsToAppend.length; i < len; i++) {', ' this.push(itemsToAppend[i]);', ' }', ' return itemsDeleted;', ' };', ' }', '', ' function array_remove(arr, val) {', ' var index = -1;', ' for (var i = 0, len = arr.length; i < len; i++) {', ' if (arr[i] === val) {', ' index = i;', ' break;', ' }', ' }', ' if (index >= 0) {', ' arr.splice(index, 1);', ' return index;', ' } else {', ' return false;', ' }', ' }', '', ' function array_removeFromStart(array, numberToRemove) {', ' if (Array.prototype.splice) {', ' array.splice(0, numberToRemove);', ' } else {', ' for (var i = numberToRemove, len = array.length; i < len; i++) {', ' array[i - numberToRemove] = array[i];', ' }', ' array.length = array.length - numberToRemove;', ' }', ' return array;', ' }', '', ' function array_contains(arr, val) {', ' for (var i = 0, len = arr.length; i < len; i++) {', ' if (arr[i] == val) {', ' return true;', ' }', ' }', ' return false;', ' }', '', ' function getErrorMessage(ex) {', ' if (ex.message) {', ' return ex.message;', ' } else if (ex.description) {', ' return ex.description;', ' }', ' return "" + ex;', ' }', '', ' function moveCaretToEnd(input) {', ' if (input.setSelectionRange) {', ' input.focus();', ' var length = input.value.length;', ' input.setSelectionRange(length, length);', ' } else if (input.createTextRange) {', ' var range = input.createTextRange();', ' range.collapse(false);', ' range.select();', ' }', ' input.focus();', ' }', '', ' function stopPropagation(evt) {', ' if (evt.stopPropagation) {', ' evt.stopPropagation();', ' } else if (typeof evt.cancelBubble != "undefined") {', ' evt.cancelBubble = true;', ' }', ' }', '', ' function getEvent(evt) {', ' return evt ? evt : event;', ' }', '', ' function getTarget(evt) {', ' return evt.target ? evt.target : evt.srcElement;', ' }', '', ' function getRelatedTarget(evt) {', ' if (evt.relatedTarget) {', ' return evt.relatedTarget;', ' } else if (evt.srcElement) {', ' switch(evt.type) {', ' case "mouseover":', ' return evt.fromElement;', ' case "mouseout":', ' return evt.toElement;', ' default:', ' return evt.srcElement;', ' }', ' }', ' }', '', ' function cancelKeyEvent(evt) {', ' evt.returnValue = false;', ' stopPropagation(evt);', ' }', '', ' function evalCommandLine() {', ' var expr = $("command").value;', ' evalCommand(expr);', ' $("command").value = "";', ' }', '', ' function evalLastCommand() {', ' if (lastCommand != null) {', ' evalCommand(lastCommand);', ' }', ' }', '', ' var lastCommand = null;', ' var commandHistory = [];', ' var currentCommandIndex = 0;', '', ' function evalCommand(expr) {', ' if (appender) {', ' appender.evalCommandAndAppend(expr);', ' } else {', ' var prefix = ">>> " + expr + "\\r\\n";', ' try {', ' log("INFO", prefix + eval(expr));', ' } catch (ex) {', ' log("ERROR", prefix + "Error: " + getErrorMessage(ex));', ' }', ' }', ' // Update command history', ' if (expr != commandHistory[commandHistory.length - 1]) {', ' commandHistory.push(expr);', ' // Update the appender', ' if (appender) {', ' appender.storeCommandHistory(commandHistory);', ' }', ' }', ' currentCommandIndex = (expr == commandHistory[currentCommandIndex]) ? currentCommandIndex + 1 : commandHistory.length;', ' lastCommand = expr;', ' }', ' //]]>', ' </script>', ' <style type="text/css">', ' body {', ' background-color: white;', ' color: black;', ' padding: 0;', ' margin: 0;', ' font-family: tahoma, verdana, arial, helvetica, sans-serif;', ' overflow: hidden;', ' -webkit-touch-callout: none;', ' -webkit-user-select: none;', ' -khtml-user-select: none;', ' -moz-user-select: none;', ' -ms-user-select: none;', ' user-select: none;', ' }', '', ' div#switchesContainer input {', ' margin-bottom: 0;', ' }', '', ' div.toolbar {', ' border-top: solid #ffffff 1px;', ' border-bottom: solid #aca899 1px;', ' background-color: #f1efe7;', ' padding: 3px 5px;', ' font-size: 68.75%;', ' }', '', ' div.toolbar, div#search input {', ' font-family: tahoma, verdana, arial, helvetica, sans-serif;', ' }', '', ' div.toolbar input.button {', ' padding: 0 5px;', ' font-size: 100%;', ' }', '', ' div.toolbar input.hidden {', ' display: none;', ' }', '', ' div#switches input#clearButton {', ' margin-left: 20px;', ' }', '', ' div#levels label {', ' font-weight: bold;', ' }', '', ' div#levels label, div#options label {', ' margin-right: 5px;', ' }', '', ' div#levels label#wrapLabel {', ' font-weight: normal;', ' }', '', ' div#search label {', ' margin-right: 10px;', ' }', '', ' div#search label.searchboxlabel {', ' margin-right: 0;', ' }', '', ' div#search input {', ' font-size: 100%;', ' }', '', ' div#search input.validregex {', ' color: green;', ' }', '', ' div#search input.invalidregex {', ' color: red;', ' }', '', ' div#search input.nomatches {', ' color: white;', ' background-color: #ff6666;', ' }', '', ' div#search input.nomatches {', ' color: white;', ' background-color: #ff6666;', ' }', '', ' div#searchNav {', ' display: none;', ' }', '', ' div#commandLine {', ' display: none;', ' }', '', ' div#commandLine input#command {', ' font-size: 100%;', ' font-family: Courier New, Courier;', ' }', '', ' div#commandLine input#evaluateButton {', ' }', '', ' *.greyedout {', ' color: gray !important;', ' border-color: gray !important;', ' }', '', ' *.greyedout *.alwaysenabled { color: black; }', '', ' *.unselectable {', ' -khtml-user-select: none;', ' -moz-user-select: none;', ' user-select: none;', ' }', '', ' div#log {', ' font-family: Courier New, Courier;', ' font-size: 75%;', ' width: 100%;', ' overflow: auto;', ' clear: both;', ' position: relative;', ' -webkit-touch-callout: text;', ' -webkit-user-select: text;', ' -khtml-user-select: text;', ' -moz-user-select: text;', ' -ms-user-select: text;', ' user-select: text;', ' }', '', ' div.group {', ' border-color: #cccccc;', ' border-style: solid;', ' border-width: 1px 0 1px 1px;', ' overflow: visible;', ' }', '', ' div.oldIe div.group, div.oldIe div.group *, div.oldIe *.logentry {', ' height: 1%;', ' }', '', ' div.group div.groupheading span.expander {', ' border: solid black 1px;', ' font-family: Courier New, Courier;', ' font-size: 0.833em;', ' background-color: #eeeeee;', ' position: relative;', ' top: -1px;', ' color: black;', ' padding: 0 2px;', ' cursor: pointer;', ' cursor: hand;', ' height: 1%;', ' }', '', ' div.group div.groupcontent {', ' margin-left: 10px;', ' padding-bottom: 2px;', ' overflow: visible;', ' }', '', ' div.group div.expanded {', ' display: block;', ' }', '', ' div.group div.collapsed {', ' display: none;', ' }', '', ' *.logentry {', ' overflow: visible;', ' display: none;', ' white-space: pre;', ' }', '', ' span.pre {', ' white-space: pre;', ' }', ' ', ' pre.unwrapped {', ' display: inline !important;', ' }', '', ' pre.unwrapped pre.pre, div.wrapped pre.pre {', ' display: inline;', ' }', '', ' div.wrapped pre.pre {', ' white-space: normal;', ' }', '', ' div.wrapped {', ' display: none;', ' }', '', ' body.searching *.logentry span.currentmatch {', ' color: white !important;', ' background-color: green !important;', ' }', '', ' body.searching div.searchhighlight *.logentry span.searchterm {', ' color: black;', ' background-color: yellow;', ' }', '', ' div.wrap *.logentry {', ' white-space: normal !important;', ' border-width: 0 0 1px 0;', ' border-color: #dddddd;', ' border-style: dotted;', ' }', '', ' div.wrap #log_wrapped, #log_unwrapped {', ' display: block;', ' }', '', ' div.wrap #log_unwrapped, #log_wrapped {', ' display: none;', ' }', '', ' div.wrap *.logentry span.pre {', ' overflow: visible;', ' white-space: normal;', ' }', '', ' div.wrap *.logentry pre.unwrapped {', ' display: none;', ' }', '', ' div.wrap *.logentry span.wrapped {', ' display: inline;', ' }', '', ' div.searchfilter *.searchnonmatch {', ' display: none !important;', ' }', '', ' div#log *.TRACE, label#label_TRACE {', ' color: #666666;', ' }', '', ' div#log *.DEBUG, label#label_DEBUG {', ' color: green;', ' }', '', ' div#log *.INFO, label#label_INFO {', ' color: #000099;', ' }', '', ' div#log *.WARN, label#label_WARN {', ' color: #999900;', ' }', '', ' div#log *.ERROR, label#label_ERROR {', ' color: red;', ' }', '', ' div#log *.FATAL, label#label_FATAL {', ' color: #660066;', ' }', '', ' div.TRACE#log *.TRACE,', ' div.DEBUG#log *.DEBUG,', ' div.INFO#log *.INFO,', ' div.WARN#log *.WARN,', ' div.ERROR#log *.ERROR,', ' div.FATAL#log *.FATAL {', ' display: block;', ' }', '', ' div#log div.separator {', ' background-color: #cccccc;', ' margin: 5px 0;', ' line-height: 1px;', ' }', ' </style>', ' </head>', '', ' <body id="body">', ' <div id="switchesContainer">', ' <div id="switches">', ' <div id="levels" class="toolbar">', ' Filters:', ' <input type="checkbox" id="switch_TRACE" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide trace messages" /><label for="switch_TRACE" id="label_TRACE">trace</label>', ' <input type="checkbox" id="switch_DEBUG" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide debug messages" /><label for="switch_DEBUG" id="label_DEBUG">debug</label>', ' <input type="checkbox" id="switch_INFO" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide info messages" /><label for="switch_INFO" id="label_INFO">info</label>', ' <input type="checkbox" id="switch_WARN" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide warn messages" /><label for="switch_WARN" id="label_WARN">warn</label>', ' <input type="checkbox" id="switch_ERROR" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide error messages" /><label for="switch_ERROR" id="label_ERROR">error</label>', ' <input type="checkbox" id="switch_FATAL" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide fatal messages" /><label for="switch_FATAL" id="label_FATAL">fatal</label>', ' <input type="checkbox" id="switch_ALL" onclick="toggleAllLevels(); applyFilters()" checked="checked" title="Show/hide all messages" /><label for="switch_ALL" id="label_ALL">all</label>', ' </div>', ' <div id="search" class="toolbar">', ' <label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" />', ' <input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" />', ' <input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label>', ' <input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label>', ' <input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label>', ' <div id="searchNav">', ' <input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext()" class="button" title="Go to the next matching log entry" />', ' <input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" />', ' <input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label>', ' <input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label>', ' </div>', ' </div>', ' <div id="options" class="toolbar">', ' Options:', ' <input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label>', ' <input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label>', ' <input type="checkbox" id="newestAtTop" onclick="toggleNewestAtTop()" title="If checked, causes newest messages to appear at the top" /><label for="newestAtTop" id="newestAtTopLabel">Newest at the top</label>', ' <input type="checkbox" id="scrollToLatest" onclick="toggleScrollToLatest()" checked="checked" title="If checked, window automatically scrolls to a new message when it is added" /><label for="scrollToLatest" id="scrollToLatestLabel">Scroll to latest</label>', ' <input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages" />', ' <input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" />', ' <input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" />', ' </div>', ' </div>', ' </div>', ' <div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>', ' <div id="commandLine" class="toolbar">', ' <div id="commandLineContainer">', ' <input type="text" id="command" title="Enter a JavaScript command here and hit return or press \'Evaluate\'" />', ' <input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" />', ' </div>', ' </div>', ' </body>', '</html>', '' ]; }; var defaultCommandLineFunctions = []; ConsoleAppender = function() {}; var consoleAppenderIdCounter = 1; ConsoleAppender.prototype = new Appender(); ConsoleAppender.prototype.create = function(inPage, container, lazyInit, initiallyMinimized, useDocumentWrite, width, height, focusConsoleWindow) { var appender = this; // Common properties var initialized = false; var consoleWindowCreated = false; var consoleWindowLoaded = false; var consoleClosed = false; var queuedLoggingEvents = []; var isSupported = true; var consoleAppenderId = consoleAppenderIdCounter++; // Local variables initiallyMinimized = extractBooleanFromParam(initiallyMinimized, this.defaults.initiallyMinimized); lazyInit = extractBooleanFromParam(lazyInit, this.defaults.lazyInit); useDocumentWrite = extractBooleanFromParam(useDocumentWrite, this.defaults.useDocumentWrite); var newestMessageAtTop = this.defaults.newestMessageAtTop; var scrollToLatestMessage = this.defaults.scrollToLatestMessage; width = width ? width : this.defaults.width; height = height ? height : this.defaults.height; var maxMessages = this.defaults.maxMessages; var showCommandLine = this.defaults.showCommandLine; var commandLineObjectExpansionDepth = this.defaults.commandLineObjectExpansionDepth; var showHideButton = this.defaults.showHideButton; var showCloseButton = this.defaults.showCloseButton; this.setLayout(this.defaults.layout); // Functions whose implementations vary between subclasses var init, createWindow, safeToAppend, getConsoleWindow, open; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. var appenderName = inPage ? "InPageAppender" : "PopUpAppender"; var checkCanConfigure = function(configOptionName) { if (consoleWindowCreated) { handleError(appenderName + ": configuration option '" + configOptionName + "' may not be set after the appender has been initialized"); return false; } return true; }; var consoleWindowExists = function() { return (consoleWindowLoaded && isSupported && !consoleClosed); }; this.isNewestMessageAtTop = function() { return newestMessageAtTop; }; this.setNewestMessageAtTop = function(newestMessageAtTopParam) { newestMessageAtTop = bool(newestMessageAtTopParam); if (consoleWindowExists()) { getConsoleWindow().setNewestAtTop(newestMessageAtTop); } }; this.isScrollToLatestMessage = function() { return scrollToLatestMessage; }; this.setScrollToLatestMessage = function(scrollToLatestMessageParam) { scrollToLatestMessage = bool(scrollToLatestMessageParam); if (consoleWindowExists()) { getConsoleWindow().setScrollToLatest(scrollToLatestMessage); } }; this.getWidth = function() { return width; }; this.setWidth = function(widthParam) { if (checkCanConfigure("width")) { width = extractStringFromParam(widthParam, width); } }; this.getHeight = function() { return height; }; this.setHeight = function(heightParam) { if (checkCanConfigure("height")) { height = extractStringFromParam(heightParam, height); } }; this.getMaxMessages = function() { return maxMessages; }; this.setMaxMessages = function(maxMessagesParam) { maxMessages = extractIntFromParam(maxMessagesParam, maxMessages); if (consoleWindowExists()) { getConsoleWindow().setMaxMessages(maxMessages); } }; this.isShowCommandLine = function() { return showCommandLine; }; this.setShowCommandLine = function(showCommandLineParam) { showCommandLine = bool(showCommandLineParam); if (consoleWindowExists()) { getConsoleWindow().setShowCommandLine(showCommandLine); } }; this.isShowHideButton = function() { return showHideButton; }; this.setShowHideButton = function(showHideButtonParam) { showHideButton = bool(showHideButtonParam); if (consoleWindowExists()) { getConsoleWindow().setShowHideButton(showHideButton); } }; this.isShowCloseButton = function() { return showCloseButton; }; this.setShowCloseButton = function(showCloseButtonParam) { showCloseButton = bool(showCloseButtonParam); if (consoleWindowExists()) { getConsoleWindow().setShowCloseButton(showCloseButton); } }; this.getCommandLineObjectExpansionDepth = function() { return commandLineObjectExpansionDepth; }; this.setCommandLineObjectExpansionDepth = function(commandLineObjectExpansionDepthParam) { commandLineObjectExpansionDepth = extractIntFromParam(commandLineObjectExpansionDepthParam, commandLineObjectExpansionDepth); }; var minimized = initiallyMinimized; this.isInitiallyMinimized = function() { return initiallyMinimized; }; this.setInitiallyMinimized = function(initiallyMinimizedParam) { if (checkCanConfigure("initiallyMinimized")) { initiallyMinimized = bool(initiallyMinimizedParam); minimized = initiallyMinimized; } }; this.isUseDocumentWrite = function() { return useDocumentWrite; }; this.setUseDocumentWrite = function(useDocumentWriteParam) { if (checkCanConfigure("useDocumentWrite")) { useDocumentWrite = bool(useDocumentWriteParam); } }; // Common methods function QueuedLoggingEvent(loggingEvent, formattedMessage) { this.loggingEvent = loggingEvent; this.levelName = loggingEvent.level.name; this.formattedMessage = formattedMessage; } QueuedLoggingEvent.prototype.append = function() { getConsoleWindow().log(this.levelName, this.formattedMessage); }; function QueuedGroup(name, initiallyExpanded) { this.name = name; this.initiallyExpanded = initiallyExpanded; } QueuedGroup.prototype.append = function() { getConsoleWindow().group(this.name, this.initiallyExpanded); }; function QueuedGroupEnd() {} QueuedGroupEnd.prototype.append = function() { getConsoleWindow().groupEnd(); }; var checkAndAppend = function() { // Next line forces a check of whether the window has been closed safeToAppend(); if (!initialized) { init(); } else if (consoleClosed && reopenWhenClosed) { createWindow(); } if (safeToAppend()) { appendQueuedLoggingEvents(); } }; this.append = function(loggingEvent) { if (isSupported) { // Format the message var formattedMessage = appender.getLayout().formatWithException(loggingEvent); queuedLoggingEvents.push(new QueuedLoggingEvent(loggingEvent, formattedMessage)); checkAndAppend(); } }; this.group = function(name, initiallyExpanded) { if (isSupported) { queuedLoggingEvents.push(new QueuedGroup(name, initiallyExpanded)); checkAndAppend(); } }; this.groupEnd = function() { if (isSupported) { queuedLoggingEvents.push(new QueuedGroupEnd()); checkAndAppend(); } }; var appendQueuedLoggingEvents = function() { while (queuedLoggingEvents.length > 0) { queuedLoggingEvents.shift().append(); } if (focusConsoleWindow) { getConsoleWindow().focus(); } }; this.setAddedToLogger = function(logger) { this.loggers.push(logger); if (enabled && !lazyInit) { init(); } }; this.clear = function() { if (consoleWindowExists()) { getConsoleWindow().clearLog(); } queuedLoggingEvents.length = 0; }; this.focus = function() { if (consoleWindowExists()) { getConsoleWindow().focus(); } }; this.focusCommandLine = function() { if (consoleWindowExists()) { getConsoleWindow().focusCommandLine(); } }; this.focusSearch = function() { if (consoleWindowExists()) { getConsoleWindow().focusSearch(); } }; var commandWindow = window; this.getCommandWindow = function() { return commandWindow; }; this.setCommandWindow = function(commandWindowParam) { commandWindow = commandWindowParam; }; this.executeLastCommand = function() { if (consoleWindowExists()) { getConsoleWindow().evalLastCommand(); } }; var commandLayout = new PatternLayout("%m"); this.getCommandLayout = function() { return commandLayout; }; this.setCommandLayout = function(commandLayoutParam) { commandLayout = commandLayoutParam; }; this.evalCommandAndAppend = function(expr) { var commandReturnValue = { appendResult: true, isError: false }; var commandOutput = ""; // Evaluate the command try { var result, i; // The next three lines constitute a workaround for IE. Bizarrely, iframes seem to have no // eval method on the window object initially, but once execScript has been called on // it once then the eval method magically appears. See http://www.thismuchiknow.co.uk/?p=25 if (!commandWindow.eval && commandWindow.execScript) { commandWindow.execScript("null"); } var commandLineFunctionsHash = {}; for (i = 0, len = commandLineFunctions.length; i < len; i++) { commandLineFunctionsHash[commandLineFunctions[i][0]] = commandLineFunctions[i][1]; } // Keep an array of variables that are being changed in the command window so that they // can be restored to their original values afterwards var objectsToRestore = []; var addObjectToRestore = function(name) { objectsToRestore.push([name, commandWindow[name]]); }; addObjectToRestore("appender"); commandWindow.appender = appender; addObjectToRestore("commandReturnValue"); commandWindow.commandReturnValue = commandReturnValue; addObjectToRestore("commandLineFunctionsHash"); commandWindow.commandLineFunctionsHash = commandLineFunctionsHash; var addFunctionToWindow = function(name) { addObjectToRestore(name); commandWindow[name] = function() { return this.commandLineFunctionsHash[name](appender, arguments, commandReturnValue); }; }; for (i = 0, len = commandLineFunctions.length; i < len; i++) { addFunctionToWindow(commandLineFunctions[i][0]); } // Another bizarre workaround to get IE to eval in the global scope if (commandWindow === window && commandWindow.execScript) { addObjectToRestore("evalExpr"); addObjectToRestore("result"); window.evalExpr = expr; commandWindow.execScript("window.result=eval(window.evalExpr);"); result = window.result; } else { result = commandWindow.eval(expr); } commandOutput = isUndefined(result) ? result : formatObjectExpansion(result, commandLineObjectExpansionDepth); // Restore variables in the command window to their original state for (i = 0, len = objectsToRestore.length; i < len; i++) { commandWindow[objectsToRestore[i][0]] = objectsToRestore[i][1]; } } catch (ex) { commandOutput = "Error evaluating command: " + getExceptionStringRep(ex); commandReturnValue.isError = true; } // Append command output if (commandReturnValue.appendResult) { var message = ">>> " + expr; if (!isUndefined(commandOutput)) { message += newLine + commandOutput; } var level = commandReturnValue.isError ? Level.ERROR : Level.INFO; var loggingEvent = new LoggingEvent(null, new Date(), level, [message], null); var mainLayout = this.getLayout(); this.setLayout(commandLayout); this.append(loggingEvent); this.setLayout(mainLayout); } }; var commandLineFunctions = defaultCommandLineFunctions.concat([]); this.addCommandLineFunction = function(functionName, commandLineFunction) { commandLineFunctions.push([functionName, commandLineFunction]); }; var commandHistoryCookieName = "log4javascriptCommandHistory"; this.storeCommandHistory = function(commandHistory) { setCookie(commandHistoryCookieName, commandHistory.join(",")); }; var writeHtml = function(doc) { var lines = getConsoleHtmlLines(); doc.open(); for (var i = 0, len = lines.length; i < len; i++) { doc.writeln(lines[i]); } doc.close(); }; // Set up event listeners this.setEventTypes(["load", "unload"]); var consoleWindowLoadHandler = function() { var win = getConsoleWindow(); win.setAppender(appender); win.setNewestAtTop(newestMessageAtTop); win.setScrollToLatest(scrollToLatestMessage); win.setMaxMessages(maxMessages); win.setShowCommandLine(showCommandLine); win.setShowHideButton(showHideButton); win.setShowCloseButton(showCloseButton); win.setMainWindow(window); // Restore command history stored in cookie var storedValue = getCookie(commandHistoryCookieName); if (storedValue) { win.commandHistory = storedValue.split(","); win.currentCommandIndex = win.commandHistory.length; } appender.dispatchEvent("load", { "win" : win }); }; this.unload = function() { logLog.debug("unload " + this + ", caller: " + this.unload.caller); if (!consoleClosed) { logLog.debug("really doing unload " + this); consoleClosed = true; consoleWindowLoaded = false; consoleWindowCreated = false; appender.dispatchEvent("unload", {}); } }; var pollConsoleWindow = function(windowTest, interval, successCallback, errorMessage) { function doPoll() { try { // Test if the console has been closed while polling if (consoleClosed) { clearInterval(poll); } if (windowTest(getConsoleWindow())) { clearInterval(poll); successCallback(); } } catch (ex) { clearInterval(poll); isSupported = false; handleError(errorMessage, ex); } } // Poll the pop-up since the onload event is not reliable var poll = setInterval(doPoll, interval); }; var getConsoleUrl = function() { var documentDomainSet = (document.domain != location.hostname); return useDocumentWrite ? "" : getBaseUrl() + "console_uncompressed.html" + (documentDomainSet ? "?log4javascript_domain=" + escape(document.domain) : ""); }; // Define methods and properties that vary between subclasses if (inPage) { // InPageAppender var containerElement = null; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. var cssProperties = []; this.addCssProperty = function(name, value) { if (checkCanConfigure("cssProperties")) { cssProperties.push([name, value]); } }; // Define useful variables var windowCreationStarted = false; var iframeContainerDiv; var iframeId = uniqueId + "_InPageAppender_" + consoleAppenderId; this.hide = function() { if (initialized && consoleWindowCreated) { if (consoleWindowExists()) { getConsoleWindow().$("command").blur(); } iframeContainerDiv.style.display = "none"; minimized = true; } }; this.show = function() { if (initialized) { if (consoleWindowCreated) { iframeContainerDiv.style.display = "block"; this.setShowCommandLine(showCommandLine); // Force IE to update minimized = false; } else if (!windowCreationStarted) { createWindow(true); } } }; this.isVisible = function() { return !minimized && !consoleClosed; }; this.close = function(fromButton) { if (!consoleClosed && (!fromButton || confirm("This will permanently remove the console from the page. No more messages will be logged. Do you wish to continue?"))) { iframeContainerDiv.parentNode.removeChild(iframeContainerDiv); this.unload(); } }; // Create open, init, getConsoleWindow and safeToAppend functions open = function() { var initErrorMessage = "InPageAppender.open: unable to create console iframe"; function finalInit() { try { if (!initiallyMinimized) { appender.show(); } consoleWindowLoadHandler(); consoleWindowLoaded = true; appendQueuedLoggingEvents(); } catch (ex) { isSupported = false; handleError(initErrorMessage, ex); } } function writeToDocument() { try { var windowTest = function(win) { return isLoaded(win); }; if (useDocumentWrite) { writeHtml(getConsoleWindow().document); } if (windowTest(getConsoleWindow())) { finalInit(); } else { pollConsoleWindow(windowTest, 100, finalInit, initErrorMessage); } } catch (ex) { isSupported = false; handleError(initErrorMessage, ex); } } minimized = false; iframeContainerDiv = containerElement.appendChild(document.createElement("div")); iframeContainerDiv.style.width = width; iframeContainerDiv.style.height = height; iframeContainerDiv.style.border = "solid gray 1px"; for (var i = 0, len = cssProperties.length; i < len; i++) { iframeContainerDiv.style[cssProperties[i][0]] = cssProperties[i][1]; } var iframeSrc = useDocumentWrite ? "" : " src='" + getConsoleUrl() + "'"; // Adding an iframe using the DOM would be preferable, but it doesn't work // in IE5 on Windows, or in Konqueror prior to version 3.5 - in Konqueror // it creates the iframe fine but I haven't been able to find a way to obtain // the iframe's window object iframeContainerDiv.innerHTML = "<iframe id='" + iframeId + "' name='" + iframeId + "' width='100%' height='100%' frameborder='0'" + iframeSrc + " scrolling='no'></iframe>"; consoleClosed = false; // Write the console HTML to the iframe var iframeDocumentExistsTest = function(win) { try { return bool(win) && bool(win.document); } catch (ex) { return false; } }; if (iframeDocumentExistsTest(getConsoleWindow())) { writeToDocument(); } else { pollConsoleWindow(iframeDocumentExistsTest, 100, writeToDocument, initErrorMessage); } consoleWindowCreated = true; }; createWindow = function(show) { if (show || !initiallyMinimized) { var pageLoadHandler = function() { if (!container) { // Set up default container element containerElement = document.createElement("div"); containerElement.style.position = "fixed"; containerElement.style.left = "0"; containerElement.style.right = "0"; containerElement.style.bottom = "0"; document.body.appendChild(containerElement); appender.addCssProperty("borderWidth", "1px 0 0 0"); appender.addCssProperty("zIndex", 1000000); // Can't find anything authoritative that says how big z-index can be open(); } else { try { var el = document.getElementById(container); if (el.nodeType == 1) { containerElement = el; } open(); } catch (ex) { handleError("InPageAppender.init: invalid container element '" + container + "' supplied", ex); } } }; // Test the type of the container supplied. First, check if it's an element if (pageLoaded && container && container.appendChild) { containerElement = container; open(); } else if (pageLoaded) { pageLoadHandler(); } else { log4javascript.addEventListener("load", pageLoadHandler); } windowCreationStarted = true; } }; init = function() { createWindow(); initialized = true; }; getConsoleWindow = function() { var iframe = window.frames[iframeId]; if (iframe) { return iframe; } }; safeToAppend = function() { if (isSupported && !consoleClosed) { if (consoleWindowCreated && !consoleWindowLoaded && getConsoleWindow() && isLoaded(getConsoleWindow())) { consoleWindowLoaded = true; } return consoleWindowLoaded; } return false; }; } else { // PopUpAppender // Extract params var useOldPopUp = appender.defaults.useOldPopUp; var complainAboutPopUpBlocking = appender.defaults.complainAboutPopUpBlocking; var reopenWhenClosed = this.defaults.reopenWhenClosed; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. this.isUseOldPopUp = function() { return useOldPopUp; }; this.setUseOldPopUp = function(useOldPopUpParam) { if (checkCanConfigure("useOldPopUp")) { useOldPopUp = bool(useOldPopUpParam); } }; this.isComplainAboutPopUpBlocking = function() { return complainAboutPopUpBlocking; }; this.setComplainAboutPopUpBlocking = function(complainAboutPopUpBlockingParam) { if (checkCanConfigure("complainAboutPopUpBlocking")) { complainAboutPopUpBlocking = bool(complainAboutPopUpBlockingParam); } }; this.isFocusPopUp = function() { return focusConsoleWindow; }; this.setFocusPopUp = function(focusPopUpParam) { // This property can be safely altered after logging has started focusConsoleWindow = bool(focusPopUpParam); }; this.isReopenWhenClosed = function() { return reopenWhenClosed; }; this.setReopenWhenClosed = function(reopenWhenClosedParam) { // This property can be safely altered after logging has started reopenWhenClosed = bool(reopenWhenClosedParam); }; this.close = function() { logLog.debug("close " + this); try { popUp.close(); this.unload(); } catch (ex) { // Do nothing } }; this.hide = function() { logLog.debug("hide " + this); if (consoleWindowExists()) { this.close(); } }; this.show = function() { logLog.debug("show " + this); if (!consoleWindowCreated) { open(); } }; this.isVisible = function() { return safeToAppend(); }; // Define useful variables var popUp; // Create open, init, getConsoleWindow and safeToAppend functions open = function() { var windowProperties = "width=" + width + ",height=" + height + ",status,resizable"; var frameInfo = ""; try { var frameEl = window.frameElement; if (frameEl) { frameInfo = "_" + frameEl.tagName + "_" + (frameEl.name || frameEl.id || ""); } } catch (e) { frameInfo = "_inaccessibleParentFrame"; } var windowName = "PopUp_" + location.host.replace(/[^a-z0-9]/gi, "_") + "_" + consoleAppenderId + frameInfo; if (!useOldPopUp || !useDocumentWrite) { // Ensure a previous window isn't used by using a unique name windowName = windowName + "_" + uniqueId; } var checkPopUpClosed = function(win) { if (consoleClosed) { return true; } else { try { return bool(win) && win.closed; } catch(ex) {} } return false; }; var popUpClosedCallback = function() { if (!consoleClosed) { appender.unload(); } }; function finalInit() { getConsoleWindow().setCloseIfOpenerCloses(!useOldPopUp || !useDocumentWrite); consoleWindowLoadHandler(); consoleWindowLoaded = true; appendQueuedLoggingEvents(); pollConsoleWindow(checkPopUpClosed, 500, popUpClosedCallback, "PopUpAppender.checkPopUpClosed: error checking pop-up window"); } try { popUp = window.open(getConsoleUrl(), windowName, windowProperties); consoleClosed = false; consoleWindowCreated = true; if (popUp && popUp.document) { if (useDocumentWrite && useOldPopUp && isLoaded(popUp)) { popUp.mainPageReloaded(); finalInit(); } else { if (useDocumentWrite) { writeHtml(popUp.document); } // Check if the pop-up window object is available var popUpLoadedTest = function(win) { return bool(win) && isLoaded(win); }; if (isLoaded(popUp)) { finalInit(); } else { pollConsoleWindow(popUpLoadedTest, 100, finalInit, "PopUpAppender.init: unable to create console window"); } } } else { isSupported = false; logLog.warn("PopUpAppender.init: pop-ups blocked, please unblock to use PopUpAppender"); if (complainAboutPopUpBlocking) { handleError("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging."); } } } catch (ex) { handleError("PopUpAppender.init: error creating pop-up", ex); } }; createWindow = function() { if (!initiallyMinimized) { open(); } }; init = function() { createWindow(); initialized = true; }; getConsoleWindow = function() { return popUp; }; safeToAppend = function() { if (isSupported && !isUndefined(popUp) && !consoleClosed) { if (popUp.closed || (consoleWindowLoaded && isUndefined(popUp.closed))) { // Extra check for Opera appender.unload(); logLog.debug("PopUpAppender: pop-up closed"); return false; } if (!consoleWindowLoaded && isLoaded(popUp)) { consoleWindowLoaded = true; } } return isSupported && consoleWindowLoaded && !consoleClosed; }; } // Expose getConsoleWindow so that automated tests can check the DOM this.getConsoleWindow = getConsoleWindow; }; ConsoleAppender.addGlobalCommandLineFunction = function(functionName, commandLineFunction) { defaultCommandLineFunctions.push([functionName, commandLineFunction]); }; /* ------------------------------------------------------------------ */ function PopUpAppender(lazyInit, initiallyMinimized, useDocumentWrite, width, height) { this.create(false, null, lazyInit, initiallyMinimized, useDocumentWrite, width, height, this.defaults.focusPopUp); } PopUpAppender.prototype = new ConsoleAppender(); PopUpAppender.prototype.defaults = { layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"), initiallyMinimized: false, focusPopUp: false, lazyInit: true, useOldPopUp: true, complainAboutPopUpBlocking: true, newestMessageAtTop: false, scrollToLatestMessage: true, width: "600", height: "400", reopenWhenClosed: false, maxMessages: null, showCommandLine: true, commandLineObjectExpansionDepth: 1, showHideButton: false, showCloseButton: true, useDocumentWrite: true }; PopUpAppender.prototype.toString = function() { return "PopUpAppender"; }; log4javascript.PopUpAppender = PopUpAppender; /* ------------------------------------------------------------------ */ function InPageAppender(container, lazyInit, initiallyMinimized, useDocumentWrite, width, height) { this.create(true, container, lazyInit, initiallyMinimized, useDocumentWrite, width, height, false); } InPageAppender.prototype = new ConsoleAppender(); InPageAppender.prototype.defaults = { layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"), initiallyMinimized: false, lazyInit: true, newestMessageAtTop: false, scrollToLatestMessage: true, width: "100%", height: "220px", maxMessages: null, showCommandLine: true, commandLineObjectExpansionDepth: 1, showHideButton: false, showCloseButton: false, showLogEntryDeleteButtons: true, useDocumentWrite: true }; InPageAppender.prototype.toString = function() { return "InPageAppender"; }; log4javascript.InPageAppender = InPageAppender; // Next line for backwards compatibility log4javascript.InlineAppender = InPageAppender; })(); /* ---------------------------------------------------------------------- */ // Console extension functions function padWithSpaces(str, len) { if (str.length < len) { var spaces = []; var numberOfSpaces = Math.max(0, len - str.length); for (var i = 0; i < numberOfSpaces; i++) { spaces[i] = " "; } str += spaces.join(""); } return str; } (function() { function dir(obj) { var maxLen = 0; // Obtain the length of the longest property name for (var p in obj) { maxLen = Math.max(toStr(p).length, maxLen); } // Create the nicely formatted property list var propList = []; for (p in obj) { var propNameStr = " " + padWithSpaces(toStr(p), maxLen + 2); var propVal; try { propVal = splitIntoLines(toStr(obj[p])).join(padWithSpaces(newLine, maxLen + 6)); } catch (ex) { propVal = "[Error obtaining property. Details: " + getExceptionMessage(ex) + "]"; } propList.push(propNameStr + propVal); } return propList.join(newLine); } var nodeTypes = { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }; var preFormattedElements = ["script", "pre"]; // This should be the definitive list, as specified by the XHTML 1.0 Transitional DTD var emptyElements = ["br", "img", "hr", "param", "link", "area", "input", "col", "base", "meta"]; var indentationUnit = " "; // Create and return an XHTML string from the node specified function getXhtml(rootNode, includeRootNode, indentation, startNewLine, preformatted) { includeRootNode = (typeof includeRootNode == "undefined") ? true : !!includeRootNode; if (typeof indentation != "string") { indentation = ""; } startNewLine = !!startNewLine; preformatted = !!preformatted; var xhtml; function isWhitespace(node) { return ((node.nodeType == nodeTypes.TEXT_NODE) && /^[ \t\r\n]*$/.test(node.nodeValue)); } function fixAttributeValue(attrValue) { return attrValue.toString().replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """); } function getStyleAttributeValue(el) { var stylePairs = el.style.cssText.split(";"); var styleValue = ""; for (var j = 0, len = stylePairs.length; j < len; j++) { var nameValueBits = stylePairs[j].split(":"); var props = []; if (!/^\s*$/.test(nameValueBits[0])) { props.push(trim(nameValueBits[0]).toLowerCase() + ":" + trim(nameValueBits[1])); } styleValue = props.join(";"); } return styleValue; } function getNamespace(el) { if (el.prefix) { return el.prefix; } else if (el.outerHTML) { var regex = new RegExp("<([^:]+):" + el.tagName + "[^>]*>", "i"); if (regex.test(el.outerHTML)) { return RegExp.$1.toLowerCase(); } } return ""; } var lt = "<"; var gt = ">"; var i, len; if (includeRootNode && rootNode.nodeType != nodeTypes.DOCUMENT_FRAGMENT_NODE) { switch (rootNode.nodeType) { case nodeTypes.ELEMENT_NODE: var tagName = rootNode.tagName.toLowerCase(); xhtml = startNewLine ? newLine + indentation : ""; xhtml += lt; // Allow for namespaces, where present var prefix = getNamespace(rootNode); var hasPrefix = !!prefix; if (hasPrefix) { xhtml += prefix + ":"; } xhtml += tagName; for (i = 0, len = rootNode.attributes.length; i < len; i++) { var currentAttr = rootNode.attributes[i]; // Check the attribute is valid. if (! currentAttr.specified || currentAttr.nodeValue === null || currentAttr.nodeName.toLowerCase() === "style" || typeof currentAttr.nodeValue !== "string" || currentAttr.nodeName.indexOf("_moz") === 0) { continue; } xhtml += " " + currentAttr.nodeName.toLowerCase() + "=\""; xhtml += fixAttributeValue(currentAttr.nodeValue); xhtml += "\""; } // Style needs to be done separately as it is not reported as an // attribute in IE if (rootNode.style.cssText) { var styleValue = getStyleAttributeValue(rootNode); if (styleValue !== "") { xhtml += " style=\"" + getStyleAttributeValue(rootNode) + "\""; } } if (array_contains(emptyElements, tagName) || (hasPrefix && !rootNode.hasChildNodes())) { xhtml += "/" + gt; } else { xhtml += gt; // Add output for childNodes collection (which doesn't include attribute nodes) var childStartNewLine = !(rootNode.childNodes.length === 1 && rootNode.childNodes[0].nodeType === nodeTypes.TEXT_NODE); var childPreformatted = array_contains(preFormattedElements, tagName); for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit, childStartNewLine, childPreformatted); } // Add the end tag var endTag = lt + "/" + tagName + gt; xhtml += childStartNewLine ? newLine + indentation + endTag : endTag; } return xhtml; case nodeTypes.TEXT_NODE: if (isWhitespace(rootNode)) { xhtml = ""; } else { if (preformatted) { xhtml = rootNode.nodeValue; } else { // Trim whitespace from each line of the text node var lines = splitIntoLines(trim(rootNode.nodeValue)); var trimmedLines = []; for (i = 0, len = lines.length; i < len; i++) { trimmedLines[i] = trim(lines[i]); } xhtml = trimmedLines.join(newLine + indentation); } if (startNewLine) { xhtml = newLine + indentation + xhtml; } } return xhtml; case nodeTypes.CDATA_SECTION_NODE: return "<![CDA" + "TA[" + rootNode.nodeValue + "]" + "]>" + newLine; case nodeTypes.DOCUMENT_NODE: xhtml = ""; // Add output for childNodes collection (which doesn't include attribute nodes) for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation); } return xhtml; default: return ""; } } else { xhtml = ""; // Add output for childNodes collection (which doesn't include attribute nodes) for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit); } return xhtml; } } function createCommandLineFunctions() { ConsoleAppender.addGlobalCommandLineFunction("$", function(appender, args, returnValue) { return document.getElementById(args[0]); }); ConsoleAppender.addGlobalCommandLineFunction("dir", function(appender, args, returnValue) { var lines = []; for (var i = 0, len = args.length; i < len; i++) { lines[i] = dir(args[i]); } return lines.join(newLine + newLine); }); ConsoleAppender.addGlobalCommandLineFunction("dirxml", function(appender, args, returnValue) { var lines = []; for (var i = 0, len = args.length; i < len; i++) { lines[i] = getXhtml(args[i]); } return lines.join(newLine + newLine); }); ConsoleAppender.addGlobalCommandLineFunction("cd", function(appender, args, returnValue) { var win, message; if (args.length === 0 || args[0] === "") { win = window; message = "Command line set to run in main window"; } else { if (args[0].window == args[0]) { win = args[0]; message = "Command line set to run in frame '" + args[0].name + "'"; } else { win = window.frames[args[0]]; if (win) { message = "Command line set to run in frame '" + args[0] + "'"; } else { returnValue.isError = true; message = "Frame '" + args[0] + "' does not exist"; win = appender.getCommandWindow(); } } } appender.setCommandWindow(win); return message; }); ConsoleAppender.addGlobalCommandLineFunction("clear", function(appender, args, returnValue) { returnValue.appendResult = false; appender.clear(); }); ConsoleAppender.addGlobalCommandLineFunction("keys", function(appender, args, returnValue) { var keys = []; for (var k in args[0]) { keys.push(k); } return keys; }); ConsoleAppender.addGlobalCommandLineFunction("values", function(appender, args, returnValue) { var values = []; for (var k in args[0]) { try { values.push(args[0][k]); } catch (ex) { logLog.warn("values(): Unable to obtain value for key " + k + ". Details: " + getExceptionMessage(ex)); } } return values; }); ConsoleAppender.addGlobalCommandLineFunction("expansionDepth", function(appender, args, returnValue) { var expansionDepth = parseInt(args[0], 10); if (isNaN(expansionDepth) || expansionDepth < 0) { returnValue.isError = true; return "" + args[0] + " is not a valid expansion depth"; } else { appender.setCommandLineObjectExpansionDepth(expansionDepth); return "Object expansion depth set to " + expansionDepth; } }); } function init() { // Add command line functions createCommandLineFunctions(); } /* ------------------------------------------------------------------ */ init(); })(); /* ---------------------------------------------------------------------- */ function createDefaultLogger() { var logger = log4javascript.getLogger(defaultLoggerName); var a = new log4javascript.PopUpAppender(); logger.addAppender(a); return logger; } /* ---------------------------------------------------------------------- */ // Main load log4javascript.setDocumentReady = function() { pageLoaded = true; log4javascript.dispatchEvent("load", {}); }; if (window.addEventListener) { window.addEventListener("load", log4javascript.setDocumentReady, false); } else if (window.attachEvent) { window.attachEvent("onload", log4javascript.setDocumentReady); } else { var oldOnload = window.onload; if (typeof window.onload != "function") { window.onload = log4javascript.setDocumentReady; } else { window.onload = function(evt) { if (oldOnload) { oldOnload(evt); } log4javascript.setDocumentReady(); }; } } return log4javascript; }, this);