/** | |
* Copyright 2013 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 <tim@log4javascript.org> | |
* Version: 1.4.6 | |
* Edition: log4javascript | |
* Build date: 19 March 2013 | |
* Website: http://log4javascript.org | |
*/ | |
/* -------------------------------------------------------------------------- */ | |
// 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; | |
}; | |
} | |
/* -------------------------------------------------------------------------- */ | |
var log4javascript = (function() { | |
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(); | |
log4javascript = new Log4JavaScript(); | |
log4javascript.version = "1.4.6"; | |
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"); | |
}; | |
var urlDecode = (typeof window.decodeURIComponent != "undefined") ? | |
function(str) { | |
return decodeURIComponent(str); | |
}: | |
function(str) { | |
return unescape(str).replace(/%2B/g, "+").replace(/%22/g, "\"").replace(/%27/g, "'").replace(/%2F/g, "/").replace(/%3D/g, "="); | |
}; | |
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); | |
}; | |
} | |
function getListenersPropertyName(eventName) { | |
return "__log4javascript_listeners__" + eventName; | |
} | |
function addEvent(node, eventName, listener, useCapture, win) { | |
win = win ? win : window; | |
if (node.addEventListener) { | |
node.addEventListener(eventName, listener, useCapture); | |
} else if (node.attachEvent) { | |
node.attachEvent("on" + eventName, listener); | |
} else { | |
var propertyName = getListenersPropertyName(eventName); | |
if (!node[propertyName]) { | |
node[propertyName] = []; | |
// Set event handler | |
node["on" + eventName] = function(evt) { | |
evt = getEvent(evt, win); | |
var listenersPropertyName = getListenersPropertyName(eventName); | |
// Clone the array of listeners to leave the original untouched | |
var listeners = this[listenersPropertyName].concat([]); | |
var currentListener; | |
// Call each listener in turn | |
while ((currentListener = listeners.shift())) { | |
currentListener.call(this, evt); | |
} | |
}; | |
} | |
node[propertyName].push(listener); | |
} | |
} | |
function removeEvent(node, eventName, listener, useCapture) { | |
if (node.removeEventListener) { | |
node.removeEventListener(eventName, listener, useCapture); | |
} else if (node.detachEvent) { | |
node.detachEvent("on" + eventName, listener); | |
} else { | |
var propertyName = getListenersPropertyName(eventName); | |
if (node[propertyName]) { | |
array_remove(node[propertyName], listener); | |
} | |
} | |
} | |
function getEvent(evt, win) { | |
win = win ? win : window; | |
return evt ? evt : win.event; | |
} | |
function stopEventPropagation(evt) { | |
if (evt.stopPropagation) { | |
evt.stopPropagation(); | |
} else if (typeof evt.cancelBubble != "undefined") { | |
evt.cancelBubble = true; | |
} | |
evt.returnValue = false; | |
} | |
/* ---------------------------------------------------------------------- */ | |
// 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 = log4javascript.getLogger(defaultLoggerName); | |
var a = new log4javascript.PopUpAppender(); | |
defaultLogger.addAppender(a); | |
} | |
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); | |
}, | |
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.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, j, 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) { | |
var formattedMessage = this.getLayout().format(loggingEvent); | |
if (this.getLayout().ignoresThrowable()) { | |
formattedMessage += loggingEvent.getThrowableStrRep(); | |
} | |
alert(formattedMessage); | |
}; | |
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 layout = appender.getLayout(); | |
var formattedMessage = layout.format(loggingEvent); | |
if (layout.ignoresThrowable() && loggingEvent.exception) { | |
formattedMessage += loggingEvent.getThrowableStrRep(); | |
} | |
return formattedMessage; | |
}; | |
if ((typeof opera != "undefined") && opera.postError) { // Opera | |
opera.postError(getFormattedMessage()); | |
} else if (window.console && window.console.log) { // Safari and Firebug | |
var formattedMesage = getFormattedMessage(); | |
// Log to Firebug using its logging methods or revert to the console.log | |
// method in Safari | |
if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) { | |
window.console.debug(formattedMesage); | |
} else if (window.console.info && Level.INFO.equals(loggingEvent.level)) { | |
window.console.info(formattedMesage); | |
} else if (window.console.warn && Level.WARN.equals(loggingEvent.level)) { | |
window.console.warn(formattedMesage); | |
} else if (window.console.error && loggingEvent.level.isGreaterOrEqual(Level.ERROR)) { | |
window.console.error(formattedMesage); | |
} else { | |
window.console.log(formattedMesage); | |
} | |
} | |
}; | |
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 xmlHttpFactories = [ | |
function() { return new XMLHttpRequest(); }, | |
function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, | |
function() { return new ActiveXObject("Microsoft.XMLHTTP"); } | |
]; | |
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(); | |
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) { | |
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())) { | |
var currentFormattedMessage = appender.getLayout().format(currentLoggingEvent); | |
if (appender.getLayout().ignoresThrowable()) { | |
currentFormattedMessage += currentLoggingEvent.getThrowableStrRep(); | |
} | |
formattedMessages.push(currentFormattedMessage); | |
} | |
// 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) { | |
if (xmlHttp.overrideMimeType) { | |
xmlHttp.overrideMimeType(appender.getLayout().getContentType()); | |
} | |
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); | |
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(); | |
} | |
if (sendAllRemaining()) { | |
return "Sending log messages"; | |
} | |
}; | |
} | |
// 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;', | |
' }', | |
'', | |
' 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;', | |
' }', | |
'', | |
' 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; | |
var showLogEntryDeleteButtons = this.defaults.showLogEntryDeleteButtons; | |
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().format(loggingEvent); | |
if (this.getLayout().ignoresThrowable()) { | |
formattedMessage += loggingEvent.getThrowableStrRep(); | |
} | |
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() { | |
var currentLoggingEvent; | |
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, | |
showLogEntryDeleteButtons: 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 = ""; | |
var isFirst = true; | |
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 = ">"; | |
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 (var 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 (var 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 (var 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 (var 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++) { | |
var win = appender.getCommandWindow(); | |
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(); | |
})(); | |
/* ---------------------------------------------------------------------- */ | |
// 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(); | |
}; | |
} | |
} | |
// Ensure that the log4javascript object is available in the window. This | |
// is necessary for log4javascript to be available in IE if loaded using | |
// Dojo's module system | |
window.log4javascript = log4javascript; | |
return log4javascript; | |
})(); |