All Downloads are FREE. Search and download functionalities are using the official Maven repository.

log4javascript.log4javascript_uncompressed.js Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
/**
 * Copyright 2014 Tim Down.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * log4javascript
 *
 * log4javascript is a logging framework for JavaScript based on log4j
 * for Java. This file contains all core log4javascript code and is the only
 * file required to use log4javascript, unless you require support for
 * document.domain, in which case you will also need console.html, which must be
 * stored in the same directory as the main log4javascript.js file.
 *
 * Author: Tim Down 
 * Version: 1.4.10
 * Edition: log4javascript
 * Build date: 28 September 2014
 * Website: http://log4javascript.org
 */

(function(factory, root) {
	if (typeof define == "function" && define.amd) {
		// AMD. Register as an anonymous module.
		define(factory);
	} else if (typeof module != "undefined" && typeof exports == "object") {
		// Node/CommonJS style
		module.exports = factory();
	} else {
		// No AMD or CommonJS support so we place log4javascript in (probably) the global variable
		root.log4javascript = factory();
	}
})(function() {
	// Array-related stuff. Next three methods are solely for IE5, which is missing them
	if (!Array.prototype.push) {
		Array.prototype.push = function() {
			for (var i = 0, len = arguments.length; i < len; i++){
				this[this.length] = arguments[i];
			}
			return this.length;
		};
	}

	if (!Array.prototype.shift) {
		Array.prototype.shift = function() {
			if (this.length > 0) {
				var firstItem = this[0];
				for (var i = 0, len = this.length - 1; i < len; i++) {
					this[i] = this[i + 1];
				}
				this.length = this.length - 1;
				return firstItem;
			}
		};
	}

	if (!Array.prototype.splice) {
		Array.prototype.splice = function(startIndex, deleteCount) {
			var itemsAfterDeleted = this.slice(startIndex + deleteCount);
			var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);
			this.length = startIndex;
			// Copy the arguments into a proper Array object
			var argumentsArray = [];
			for (var i = 0, len = arguments.length; i < len; i++) {
				argumentsArray[i] = arguments[i];
			}
			var itemsToAppend = (argumentsArray.length > 2) ?
				itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;
			for (i = 0, len = itemsToAppend.length; i < len; i++) {
				this.push(itemsToAppend[i]);
			}
			return itemsDeleted;
		};
	}

	/* ---------------------------------------------------------------------- */

	function isUndefined(obj) {
		return typeof obj == "undefined";
	}

	/* ---------------------------------------------------------------------- */
	// Custom event support

	function EventSupport() {}

	EventSupport.prototype = {
		eventTypes: [],
		eventListeners: {},
		setEventTypes: function(eventTypesParam) {
			if (eventTypesParam instanceof Array) {
				this.eventTypes = eventTypesParam;
				this.eventListeners = {};
				for (var i = 0, len = this.eventTypes.length; i < len; i++) {
					this.eventListeners[this.eventTypes[i]] = [];
				}
			} else {
				handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array");
			}
		},

		addEventListener: function(eventType, listener) {
			if (typeof listener == "function") {
				if (!array_contains(this.eventTypes, eventType)) {
					handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'");
				}
				this.eventListeners[eventType].push(listener);
			} else {
				handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function");
			}
		},

		removeEventListener: function(eventType, listener) {
			if (typeof listener == "function") {
				if (!array_contains(this.eventTypes, eventType)) {
					handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'");
				}
				array_remove(this.eventListeners[eventType], listener);
			} else {
				handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function");
			}
		},

		dispatchEvent: function(eventType, eventArgs) {
			if (array_contains(this.eventTypes, eventType)) {
				var listeners = this.eventListeners[eventType];
				for (var i = 0, len = listeners.length; i < len; i++) {
					listeners[i](this, eventType, eventArgs);
				}
			} else {
				handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'");
			}
		}
	};

	/* -------------------------------------------------------------------------- */

	var applicationStartDate = new Date();
	var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" +
		Math.floor(Math.random() * 100000000);
	var emptyFunction = function() {};
	var newLine = "\r\n";
	var pageLoaded = false;

	// Create main log4javascript object; this will be assigned public properties
	function Log4JavaScript() {}
	Log4JavaScript.prototype = new EventSupport();

	var log4javascript = new Log4JavaScript();
	log4javascript.version = "1.4.10";
	log4javascript.edition = "log4javascript";

	/* -------------------------------------------------------------------------- */
	// Utility functions

	function toStr(obj) {
		if (obj && obj.toString) {
			return obj.toString();
		} else {
			return String(obj);
		}
	}

	function getExceptionMessage(ex) {
		if (ex.message) {
			return ex.message;
		} else if (ex.description) {
			return ex.description;
		} else {
			return toStr(ex);
		}
	}

	// Gets the portion of the URL after the last slash
	function getUrlFileName(url) {
		var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
		return url.substr(lastSlashIndex + 1);
	}

	// Returns a nicely formatted representation of an error
	function getExceptionStringRep(ex) {
		if (ex) {
			var exStr = "Exception: " + getExceptionMessage(ex);
			try {
				if (ex.lineNumber) {
					exStr += " on line number " + ex.lineNumber;
				}
				if (ex.fileName) {
					exStr += " in file " + getUrlFileName(ex.fileName);
				}
			} catch (localEx) {
				logLog.warn("Unable to obtain file and line information for error");
			}
			if (showStackTraces && ex.stack) {
				exStr += newLine + "Stack trace:" + newLine + ex.stack;
			}
			return exStr;
		}
		return null;
	}

	function bool(obj) {
		return Boolean(obj);
	}

	function trim(str) {
		return str.replace(/^\s+/, "").replace(/\s+$/, "");
	}

	function splitIntoLines(text) {
		// Ensure all line breaks are \n only
		var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
		return text2.split("\n");
	}

	var urlEncode = (typeof window.encodeURIComponent != "undefined") ?
		function(str) {
			return encodeURIComponent(str);
		}: 
		function(str) {
			return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D");
		};

	function array_remove(arr, val) {
		var index = -1;
		for (var i = 0, len = arr.length; i < len; i++) {
			if (arr[i] === val) {
				index = i;
				break;
			}
		}
		if (index >= 0) {
			arr.splice(index, 1);
			return true;
		} else {
			return false;
		}
	}

	function array_contains(arr, val) {
		for(var i = 0, len = arr.length; i < len; i++) {
			if (arr[i] == val) {
				return true;
			}
		}
		return false;
	}

	function extractBooleanFromParam(param, defaultValue) {
		if (isUndefined(param)) {
			return defaultValue;
		} else {
			return bool(param);
		}
	}

	function extractStringFromParam(param, defaultValue) {
		if (isUndefined(param)) {
			return defaultValue;
		} else {
			return String(param);
		}
	}

	function extractIntFromParam(param, defaultValue) {
		if (isUndefined(param)) {
			return defaultValue;
		} else {
			try {
				var value = parseInt(param, 10);
				return isNaN(value) ? defaultValue : value;
			} catch (ex) {
				logLog.warn("Invalid int param " + param, ex);
				return defaultValue;
			}
		}
	}

	function extractFunctionFromParam(param, defaultValue) {
		if (typeof param == "function") {
			return param;
		} else {
			return defaultValue;
		}
	}

	function isError(err) {
		return (err instanceof Error);
	}

	if (!Function.prototype.apply){
		Function.prototype.apply = function(obj, args) {
			var methodName = "__apply__";
			if (typeof obj[methodName] != "undefined") {
				methodName += String(Math.random()).substr(2);
			}
			obj[methodName] = this;

			var argsStrings = [];
			for (var i = 0, len = args.length; i < len; i++) {
				argsStrings[i] = "args[" + i + "]";
			}
			var script = "obj." + methodName + "(" + argsStrings.join(",") + ")";
			var returnValue = eval(script);
			delete obj[methodName];
			return returnValue;
		};
	}

	if (!Function.prototype.call){
		Function.prototype.call = function(obj) {
			var args = [];
			for (var i = 1, len = arguments.length; i < len; i++) {
				args[i - 1] = arguments[i];
			}
			return this.apply(obj, args);
		};
	}

	/* ---------------------------------------------------------------------- */
	// Simple logging for log4javascript itself

	var logLog = {
		quietMode: false,

		debugMessages: [],

		setQuietMode: function(quietMode) {
			this.quietMode = bool(quietMode);
		},

		numberOfErrors: 0,

		alertAllErrors: false,

		setAlertAllErrors: function(alertAllErrors) {
			this.alertAllErrors = alertAllErrors;
		},

		debug: function(message) {
			this.debugMessages.push(message);
		},

		displayDebug: function() {
			alert(this.debugMessages.join(newLine));
		},

		warn: function(message, exception) {
		},

		error: function(message, exception) {
			if (++this.numberOfErrors == 1 || this.alertAllErrors) {
				if (!this.quietMode) {
					var alertMessage = "log4javascript error: " + message;
					if (exception) {
						alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception);
					}
					alert(alertMessage);
				}
			}
		}
	};
	log4javascript.logLog = logLog;

	log4javascript.setEventTypes(["load", "error"]);

	function handleError(message, exception) {
		logLog.error(message, exception);
		log4javascript.dispatchEvent("error", { "message": message, "exception": exception });
	}

	log4javascript.handleError = handleError;

	/* ---------------------------------------------------------------------- */

	var enabled = !((typeof log4javascript_disabled != "undefined") &&
					log4javascript_disabled);

	log4javascript.setEnabled = function(enable) {
		enabled = bool(enable);
	};

	log4javascript.isEnabled = function() {
		return enabled;
	};

	var useTimeStampsInMilliseconds = true;

	log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) {
		useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
	};

	log4javascript.isTimeStampsInMilliseconds = function() {
		return useTimeStampsInMilliseconds;
	};
	

	// This evaluates the given expression in the current scope, thus allowing
	// scripts to access private variables. Particularly useful for testing
	log4javascript.evalInScope = function(expr) {
		return eval(expr);
	};

	var showStackTraces = false;

	log4javascript.setShowStackTraces = function(show) {
		showStackTraces = bool(show);
	};

	/* ---------------------------------------------------------------------- */
	// Levels

	var Level = function(level, name) {
		this.level = level;
		this.name = name;
	};

	Level.prototype = {
		toString: function() {
			return this.name;
		},
		equals: function(level) {
			return this.level == level.level;
		},
		isGreaterOrEqual: function(level) {
			return this.level >= level.level;
		}
	};

	Level.ALL = new Level(Number.MIN_VALUE, "ALL");
	Level.TRACE = new Level(10000, "TRACE");
	Level.DEBUG = new Level(20000, "DEBUG");
	Level.INFO = new Level(30000, "INFO");
	Level.WARN = new Level(40000, "WARN");
	Level.ERROR = new Level(50000, "ERROR");
	Level.FATAL = new Level(60000, "FATAL");
	Level.OFF = new Level(Number.MAX_VALUE, "OFF");

	log4javascript.Level = Level;

	/* ---------------------------------------------------------------------- */
	// Timers

	function Timer(name, level) {
		this.name = name;
		this.level = isUndefined(level) ? Level.INFO : level;
		this.start = new Date();
	}

	Timer.prototype.getElapsedTime = function() {
		return new Date().getTime() - this.start.getTime();
	};

	/* ---------------------------------------------------------------------- */
	// Loggers

	var anonymousLoggerName = "[anonymous]";
	var defaultLoggerName = "[default]";
	var nullLoggerName = "[null]";
	var rootLoggerName = "root";

	function Logger(name) {
		this.name = name;
		this.parent = null;
		this.children = [];

		var appenders = [];
		var loggerLevel = null;
		var isRoot = (this.name === rootLoggerName);
		var isNull = (this.name === nullLoggerName);

		var appenderCache = null;
		var appenderCacheInvalidated = false;
		
		this.addChild = function(childLogger) {
			this.children.push(childLogger);
			childLogger.parent = this;
			childLogger.invalidateAppenderCache();
		};

		// Additivity
		var additive = true;
		this.getAdditivity = function() {
			return additive;
		};

		this.setAdditivity = function(additivity) {
			var valueChanged = (additive != additivity);
			additive = additivity;
			if (valueChanged) {
				this.invalidateAppenderCache();
			}
		};

		// Create methods that use the appenders variable in this scope
		this.addAppender = function(appender) {
			if (isNull) {
				handleError("Logger.addAppender: you may not add an appender to the null logger");
			} else {
				if (appender instanceof log4javascript.Appender) {
					if (!array_contains(appenders, appender)) {
						appenders.push(appender);
						appender.setAddedToLogger(this);
						this.invalidateAppenderCache();
					}
				} else {
					handleError("Logger.addAppender: appender supplied ('" +
						toStr(appender) + "') is not a subclass of Appender");
				}
			}
		};

		this.removeAppender = function(appender) {
			array_remove(appenders, appender);
			appender.setRemovedFromLogger(this);
			this.invalidateAppenderCache();
		};

		this.removeAllAppenders = function() {
			var appenderCount = appenders.length;
			if (appenderCount > 0) {
				for (var i = 0; i < appenderCount; i++) {
					appenders[i].setRemovedFromLogger(this);
				}
				appenders.length = 0;
				this.invalidateAppenderCache();
			}
		};

		this.getEffectiveAppenders = function() {
			if (appenderCache === null || appenderCacheInvalidated) {
				// Build appender cache
				var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ?
					[] : this.parent.getEffectiveAppenders();
				appenderCache = parentEffectiveAppenders.concat(appenders);
				appenderCacheInvalidated = false;
			}
			return appenderCache;
		};
		
		this.invalidateAppenderCache = function() {
			appenderCacheInvalidated = true;
			for (var i = 0, len = this.children.length; i < len; i++) {
				this.children[i].invalidateAppenderCache();
			}
		};

		this.log = function(level, params) {
			if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) {
				// Check whether last param is an exception
				var exception;
				var finalParamIndex = params.length - 1;
				var lastParam = params[finalParamIndex];
				if (params.length > 1 && isError(lastParam)) {
					exception = lastParam;
					finalParamIndex--;
				}

				// Construct genuine array for the params
				var messages = [];
				for (var i = 0; i <= finalParamIndex; i++) {
					messages[i] = params[i];
				}

				var loggingEvent = new LoggingEvent(
					this, new Date(), level, messages, exception);

				this.callAppenders(loggingEvent);
			}
		};

		this.callAppenders = function(loggingEvent) {
			var effectiveAppenders = this.getEffectiveAppenders();
			for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
				effectiveAppenders[i].doAppend(loggingEvent);
			}
		};

		this.setLevel = function(level) {
			// Having a level of null on the root logger would be very bad.
			if (isRoot && level === null) {
				handleError("Logger.setLevel: you cannot set the level of the root logger to null");
			} else if (level instanceof Level) {
				loggerLevel = level;
			} else {
				handleError("Logger.setLevel: level supplied to logger " +
					this.name + " is not an instance of log4javascript.Level");
			}
		};

		this.getLevel = function() {
			return loggerLevel;
		};

		this.getEffectiveLevel = function() {
			for (var logger = this; logger !== null; logger = logger.parent) {
				var level = logger.getLevel();
				if (level !== null) {
					return level;
				}
			}
		};

		this.group = function(name, initiallyExpanded) {
			if (enabled) {
				var effectiveAppenders = this.getEffectiveAppenders();
				for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
					effectiveAppenders[i].group(name, initiallyExpanded);
				}
			}
		};

		this.groupEnd = function() {
			if (enabled) {
				var effectiveAppenders = this.getEffectiveAppenders();
				for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
					effectiveAppenders[i].groupEnd();
				}
			}
		};

		var timers = {};

		this.time = function(name, level) {
			if (enabled) {
				if (isUndefined(name)) {
					handleError("Logger.time: a name for the timer must be supplied");
				} else if (level && !(level instanceof Level)) {
					handleError("Logger.time: level supplied to timer " +
						name + " is not an instance of log4javascript.Level");
				} else {
					timers[name] = new Timer(name, level);
				}
			}
		};

		this.timeEnd = function(name) {
			if (enabled) {
				if (isUndefined(name)) {
					handleError("Logger.timeEnd: a name for the timer must be supplied");
				} else if (timers[name]) {
					var timer = timers[name];
					var milliseconds = timer.getElapsedTime();
					this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]);
					delete timers[name];
				} else {
					logLog.warn("Logger.timeEnd: no timer found with name " + name);
				}
			}
		};

		this.assert = function(expr) {
			if (enabled && !expr) {
				var args = [];
				for (var i = 1, len = arguments.length; i < len; i++) {
					args.push(arguments[i]);
				}
				args = (args.length > 0) ? args : ["Assertion Failure"];
				args.push(newLine);
				args.push(expr);
				this.log(Level.ERROR, args);
			}
		};

		this.toString = function() {
			return "Logger[" + this.name + "]";
		};
	}

	Logger.prototype = {
		trace: function() {
			this.log(Level.TRACE, arguments);
		},

		debug: function() {
			this.log(Level.DEBUG, arguments);
		},

		info: function() {
			this.log(Level.INFO, arguments);
		},

		warn: function() {
			this.log(Level.WARN, arguments);
		},

		error: function() {
			this.log(Level.ERROR, arguments);
		},

		fatal: function() {
			this.log(Level.FATAL, arguments);
		},

		isEnabledFor: function(level) {
			return level.isGreaterOrEqual(this.getEffectiveLevel());
		},

		isTraceEnabled: function() {
			return this.isEnabledFor(Level.TRACE);
		},

		isDebugEnabled: function() {
			return this.isEnabledFor(Level.DEBUG);
		},

		isInfoEnabled: function() {
			return this.isEnabledFor(Level.INFO);
		},

		isWarnEnabled: function() {
			return this.isEnabledFor(Level.WARN);
		},

		isErrorEnabled: function() {
			return this.isEnabledFor(Level.ERROR);
		},

		isFatalEnabled: function() {
			return this.isEnabledFor(Level.FATAL);
		}
	};

	Logger.prototype.trace.isEntryPoint = true;
	Logger.prototype.debug.isEntryPoint = true;
	Logger.prototype.info.isEntryPoint = true;
	Logger.prototype.warn.isEntryPoint = true;
	Logger.prototype.error.isEntryPoint = true;
	Logger.prototype.fatal.isEntryPoint = true;

	/* ---------------------------------------------------------------------- */
	// Logger access methods

	// Hashtable of loggers keyed by logger name
	var loggers = {};
	var loggerNames = [];

	var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG;
	var rootLogger = new Logger(rootLoggerName);
	rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);

	log4javascript.getRootLogger = function() {
		return rootLogger;
	};

	log4javascript.getLogger = function(loggerName) {
		// Use default logger if loggerName is not specified or invalid
		if (typeof loggerName != "string") {
			loggerName = anonymousLoggerName;
			logLog.warn("log4javascript.getLogger: non-string logger name "	+
				toStr(loggerName) + " supplied, returning anonymous logger");
		}

		// Do not allow retrieval of the root logger by name
		if (loggerName == rootLoggerName) {
			handleError("log4javascript.getLogger: root logger may not be obtained by name");
		}

		// Create the logger for this name if it doesn't already exist
		if (!loggers[loggerName]) {
			var logger = new Logger(loggerName);
			loggers[loggerName] = logger;
			loggerNames.push(loggerName);

			// Set up parent logger, if it doesn't exist
			var lastDotIndex = loggerName.lastIndexOf(".");
			var parentLogger;
			if (lastDotIndex > -1) {
				var parentLoggerName = loggerName.substring(0, lastDotIndex);
				parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc.
			} else {
				parentLogger = rootLogger;
			}
			parentLogger.addChild(logger);
		}
		return loggers[loggerName];
	};

	var defaultLogger = null;
	log4javascript.getDefaultLogger = function() {
		if (!defaultLogger) {
			defaultLogger = createDefaultLogger();
		}
		return defaultLogger;
	};

	var nullLogger = null;
	log4javascript.getNullLogger = function() {
		if (!nullLogger) {
			nullLogger = new Logger(nullLoggerName);
			nullLogger.setLevel(Level.OFF);
		}
		return nullLogger;
	};

	// Destroys all loggers
	log4javascript.resetConfiguration = function() {
		rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
		loggers = {};
	};

	/* ---------------------------------------------------------------------- */
	// Logging events

	var LoggingEvent = function(logger, timeStamp, level, messages,
			exception) {
		this.logger = logger;
		this.timeStamp = timeStamp;
		this.timeStampInMilliseconds = timeStamp.getTime();
		this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000);
		this.milliseconds = this.timeStamp.getMilliseconds();
		this.level = level;
		this.messages = messages;
		this.exception = exception;
	};

	LoggingEvent.prototype = {
		getThrowableStrRep: function() {
			return this.exception ?
				getExceptionStringRep(this.exception) : "";
		},
		getCombinedMessages: function() {
			return (this.messages.length == 1) ? this.messages[0] :
				   this.messages.join(newLine);
		},
		toString: function() {
			return "LoggingEvent[" + this.level + "]";
		}
	};

	log4javascript.LoggingEvent = LoggingEvent;

	/* ---------------------------------------------------------------------- */
	// Layout prototype

	var Layout = function() {
	};

	Layout.prototype = {
		defaults: {
			loggerKey: "logger",
			timeStampKey: "timestamp",
			millisecondsKey: "milliseconds",
			levelKey: "level",
			messageKey: "message",
			exceptionKey: "exception",
			urlKey: "url"
		},
		loggerKey: "logger",
		timeStampKey: "timestamp",
		millisecondsKey: "milliseconds",
		levelKey: "level",
		messageKey: "message",
		exceptionKey: "exception",
		urlKey: "url",
		batchHeader: "",
		batchFooter: "",
		batchSeparator: "",
		returnsPostData: false,
		overrideTimeStampsSetting: false,
		useTimeStampsInMilliseconds: null,

		format: function() {
			handleError("Layout.format: layout supplied has no format() method");
		},

		ignoresThrowable: function() {
			handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");
		},

		getContentType: function() {
			return "text/plain";
		},

		allowBatching: function() {
			return true;
		},

		setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) {
			this.overrideTimeStampsSetting = true;
			this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
		},

		isTimeStampsInMilliseconds: function() {
			return this.overrideTimeStampsSetting ?
				this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds;
		},

		getTimeStampValue: function(loggingEvent) {
			return this.isTimeStampsInMilliseconds() ?
				loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds;
		},

		getDataValues: function(loggingEvent, combineMessages) {
			var dataValues = [
				[this.loggerKey, loggingEvent.logger.name],
				[this.timeStampKey, this.getTimeStampValue(loggingEvent)],
				[this.levelKey, loggingEvent.level.name],
				[this.urlKey, window.location.href],
				[this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages]
			];
			if (!this.isTimeStampsInMilliseconds()) {
				dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]);
			}
			if (loggingEvent.exception) {
				dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]);
			}
			if (this.hasCustomFields()) {
				for (var i = 0, len = this.customFields.length; i < len; i++) {
					var val = this.customFields[i].value;

					// Check if the value is a function. If so, execute it, passing it the
					// current layout and the logging event
					if (typeof val === "function") {
						val = val(this, loggingEvent);
					}
					dataValues.push([this.customFields[i].name, val]);
				}
			}
			return dataValues;
		},

		setKeys: function(loggerKey, timeStampKey, levelKey, messageKey,
				exceptionKey, urlKey, millisecondsKey) {
			this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey);
			this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey);
			this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey);
			this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey);
			this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey);
			this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey);
			this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey);
		},

		setCustomField: function(name, value) {
			var fieldUpdated = false;
			for (var i = 0, len = this.customFields.length; i < len; i++) {
				if (this.customFields[i].name === name) {
					this.customFields[i].value = value;
					fieldUpdated = true;
				}
			}
			if (!fieldUpdated) {
				this.customFields.push({"name": name, "value": value});
			}
		},

		hasCustomFields: function() {
			return (this.customFields.length > 0);
		},

		formatWithException: function(loggingEvent) {
			var formatted = this.format(loggingEvent);
			if (loggingEvent.exception && this.ignoresThrowable()) {
				formatted += loggingEvent.getThrowableStrRep();
			}
			return formatted;
		},

		toString: function() {
			handleError("Layout.toString: all layouts must override this method");
		}
	};

	log4javascript.Layout = Layout;

	/* ---------------------------------------------------------------------- */
	// Appender prototype

	var Appender = function() {};

	Appender.prototype = new EventSupport();

	Appender.prototype.layout = new PatternLayout();
	Appender.prototype.threshold = Level.ALL;
	Appender.prototype.loggers = [];

	// Performs threshold checks before delegating actual logging to the
	// subclass's specific append method.
	Appender.prototype.doAppend = function(loggingEvent) {
		if (enabled && loggingEvent.level.level >= this.threshold.level) {
			this.append(loggingEvent);
		}
	};

	Appender.prototype.append = function(loggingEvent) {};

	Appender.prototype.setLayout = function(layout) {
		if (layout instanceof Layout) {
			this.layout = layout;
		} else {
			handleError("Appender.setLayout: layout supplied to " +
				this.toString() + " is not a subclass of Layout");
		}
	};

	Appender.prototype.getLayout = function() {
		return this.layout;
	};

	Appender.prototype.setThreshold = function(threshold) {
		if (threshold instanceof Level) {
			this.threshold = threshold;
		} else {
			handleError("Appender.setThreshold: threshold supplied to " +
				this.toString() + " is not a subclass of Level");
		}
	};

	Appender.prototype.getThreshold = function() {
		return this.threshold;
	};

	Appender.prototype.setAddedToLogger = function(logger) {
		this.loggers.push(logger);
	};

	Appender.prototype.setRemovedFromLogger = function(logger) {
		array_remove(this.loggers, logger);
	};

	Appender.prototype.group = emptyFunction;
	Appender.prototype.groupEnd = emptyFunction;

	Appender.prototype.toString = function() {
		handleError("Appender.toString: all appenders must override this method");
	};

	log4javascript.Appender = Appender;

	/* ---------------------------------------------------------------------- */
	// SimpleLayout 

	function SimpleLayout() {
		this.customFields = [];
	}

	SimpleLayout.prototype = new Layout();

	SimpleLayout.prototype.format = function(loggingEvent) {
		return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages();
	};

	SimpleLayout.prototype.ignoresThrowable = function() {
	    return true;
	};

	SimpleLayout.prototype.toString = function() {
	    return "SimpleLayout";
	};

	log4javascript.SimpleLayout = SimpleLayout;
	/* ----------------------------------------------------------------------- */
	// NullLayout 

	function NullLayout() {
		this.customFields = [];
	}

	NullLayout.prototype = new Layout();

	NullLayout.prototype.format = function(loggingEvent) {
		return loggingEvent.messages;
	};

	NullLayout.prototype.ignoresThrowable = function() {
	    return true;
	};

	NullLayout.prototype.formatWithException = function(loggingEvent) {
		var messages = loggingEvent.messages, ex = loggingEvent.exception;
		return ex ? messages.concat([ex]) : messages;
	};

	NullLayout.prototype.toString = function() {
	    return "NullLayout";
	};

	log4javascript.NullLayout = NullLayout;
/* ---------------------------------------------------------------------- */
	// XmlLayout

	function XmlLayout(combineMessages) {
		this.combineMessages = extractBooleanFromParam(combineMessages, true);
		this.customFields = [];
	}

	XmlLayout.prototype = new Layout();

	XmlLayout.prototype.isCombinedMessages = function() {
		return this.combineMessages;
	};

	XmlLayout.prototype.getContentType = function() {
		return "text/xml";
	};

	XmlLayout.prototype.escapeCdata = function(str) {
		return str.replace(/\]\]>/, "]]>]]>";
		}

		var str = "" + newLine;
		if (this.combineMessages) {
			str += formatMessage(loggingEvent.getCombinedMessages());
		} else {
			str += "" + newLine;
			for (i = 0, len = loggingEvent.messages.length; i < len; i++) {
				str += formatMessage(loggingEvent.messages[i]) + newLine;
			}
			str += "" + newLine;
		}
		if (this.hasCustomFields()) {
			for (i = 0, len = this.customFields.length; i < len; i++) {
				str += "" + newLine;
			}
		}
		if (loggingEvent.exception) {
			str += "" + newLine;
		}
		str += "" + newLine + newLine;
		return str;
	};

	XmlLayout.prototype.ignoresThrowable = function() {
	    return false;
	};

	XmlLayout.prototype.toString = function() {
	    return "XmlLayout";
	};

	log4javascript.XmlLayout = XmlLayout;
	/* ---------------------------------------------------------------------- */
	// JsonLayout related

	function escapeNewLines(str) {
		return str.replace(/\r\n|\r|\n/g, "\\r\\n");
	}

	function JsonLayout(readable, combineMessages) {
		this.readable = extractBooleanFromParam(readable, false);
		this.combineMessages = extractBooleanFromParam(combineMessages, true);
		this.batchHeader = this.readable ? "[" + newLine : "[";
		this.batchFooter = this.readable ? "]" + newLine : "]";
		this.batchSeparator = this.readable ? "," + newLine : ",";
		this.setKeys();
		this.colon = this.readable ? ": " : ":";
		this.tab = this.readable ? "\t" : "";
		this.lineBreak = this.readable ? newLine : "";
		this.customFields = [];
	}

	/* ---------------------------------------------------------------------- */
	// JsonLayout

	JsonLayout.prototype = new Layout();

	JsonLayout.prototype.isReadable = function() {
		return this.readable;
	};

	JsonLayout.prototype.isCombinedMessages = function() {
		return this.combineMessages;
	};

    JsonLayout.prototype.format = function(loggingEvent) {
        var layout = this;
        var dataValues = this.getDataValues(loggingEvent, this.combineMessages);
        var str = "{" + this.lineBreak;
        var i, len;

        function formatValue(val, prefix, expand) {
            // Check the type of the data value to decide whether quotation marks
            // or expansion are required
            var formattedValue;
            var valType = typeof val;
            if (val instanceof Date) {
                formattedValue = String(val.getTime());
            } else if (expand && (val instanceof Array)) {
                formattedValue = "[" + layout.lineBreak;
                for (var i = 0, len = val.length; i < len; i++) {
                    var childPrefix = prefix + layout.tab;
                    formattedValue += childPrefix + formatValue(val[i], childPrefix, false);
                    if (i < val.length - 1) {
                        formattedValue += ",";
                    }
                    formattedValue += layout.lineBreak;
                }
                formattedValue += prefix + "]";
            } else if (valType !== "number" && valType !== "boolean") {
                formattedValue = "\"" + escapeNewLines(toStr(val).replace(/\"/g, "\\\"")) + "\"";
            } else {
                formattedValue = val;
            }
            return formattedValue;
        }

        for (i = 0, len = dataValues.length - 1; i <= len; i++) {
            str += this.tab + "\"" + dataValues[i][0] + "\"" + this.colon + formatValue(dataValues[i][1], this.tab, true);
            if (i < len) {
                str += ",";
            }
            str += this.lineBreak;
        }

        str += "}" + this.lineBreak;
        return str;
    };

	JsonLayout.prototype.ignoresThrowable = function() {
	    return false;
	};

	JsonLayout.prototype.toString = function() {
	    return "JsonLayout";
	};

	JsonLayout.prototype.getContentType = function() {
		return "application/json";
	};

	log4javascript.JsonLayout = JsonLayout;
	/* ---------------------------------------------------------------------- */
	// HttpPostDataLayout

	function HttpPostDataLayout() {
		this.setKeys();
		this.customFields = [];
		this.returnsPostData = true;
	}

	HttpPostDataLayout.prototype = new Layout();

	// Disable batching
	HttpPostDataLayout.prototype.allowBatching = function() {
		return false;
	};

	HttpPostDataLayout.prototype.format = function(loggingEvent) {
		var dataValues = this.getDataValues(loggingEvent);
		var queryBits = [];
		for (var i = 0, len = dataValues.length; i < len; i++) {
			var val = (dataValues[i][1] instanceof Date) ?
				String(dataValues[i][1].getTime()) : dataValues[i][1];
			queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(val));
		}
		return queryBits.join("&");
	};

	HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) {
	    return false;
	};

	HttpPostDataLayout.prototype.toString = function() {
	    return "HttpPostDataLayout";
	};

	log4javascript.HttpPostDataLayout = HttpPostDataLayout;
	/* ---------------------------------------------------------------------- */
	// formatObjectExpansion

	function formatObjectExpansion(obj, depth, indentation) {
		var objectsExpanded = [];

		function doFormat(obj, depth, indentation) {
			var i, len, childDepth, childIndentation, childLines, expansion,
				childExpansion;

			if (!indentation) {
				indentation = "";
			}

			function formatString(text) {
				var lines = splitIntoLines(text);
				for (var j = 1, jLen = lines.length; j < jLen; j++) {
					lines[j] = indentation + lines[j];
				}
				return lines.join(newLine);
			}

			if (obj === null) {
				return "null";
			} else if (typeof obj == "undefined") {
				return "undefined";
			} else if (typeof obj == "string") {
				return formatString(obj);
			} else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) {
				try {
					expansion = toStr(obj);
				} catch (ex) {
					expansion = "Error formatting property. Details: " + getExceptionStringRep(ex);
				}
				return expansion + " [already expanded]";
			} else if ((obj instanceof Array) && depth > 0) {
				objectsExpanded.push(obj);
				expansion = "[" + newLine;
				childDepth = depth - 1;
				childIndentation = indentation + "  ";
				childLines = [];
				for (i = 0, len = obj.length; i < len; i++) {
					try {
						childExpansion = doFormat(obj[i], childDepth, childIndentation);
						childLines.push(childIndentation + childExpansion);
					} catch (ex) {
						childLines.push(childIndentation + "Error formatting array member. Details: " +
							getExceptionStringRep(ex) + "");
					}
				}
				expansion += childLines.join("," + newLine) + newLine + indentation + "]";
				return expansion;
            } else if (Object.prototype.toString.call(obj) == "[object Date]") {
                return obj.toString();
			} else if (typeof obj == "object" && depth > 0) {
				objectsExpanded.push(obj);
				expansion = "{" + newLine;
				childDepth = depth - 1;
				childIndentation = indentation + "  ";
				childLines = [];
				for (i in obj) {
					try {
						childExpansion = doFormat(obj[i], childDepth, childIndentation);
						childLines.push(childIndentation + i + ": " + childExpansion);
					} catch (ex) {
						childLines.push(childIndentation + i + ": Error formatting property. Details: " +
							getExceptionStringRep(ex));
					}
				}
				expansion += childLines.join("," + newLine) + newLine + indentation + "}";
				return expansion;
			} else {
				return formatString(toStr(obj));
			}
		}
		return doFormat(obj, depth, indentation);
	}
	/* ---------------------------------------------------------------------- */
	// Date-related stuff

	var SimpleDateFormat;

	(function() {
		var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;
		var monthNames = ["January", "February", "March", "April", "May", "June",
			"July", "August", "September", "October", "November", "December"];
		var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
		var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
		var types = {
			G : TEXT2,
			y : YEAR,
			M : MONTH,
			w : NUMBER,
			W : NUMBER,
			D : NUMBER,
			d : NUMBER,
			F : NUMBER,
			E : TEXT3,
			a : TEXT2,
			H : NUMBER,
			k : NUMBER,
			K : NUMBER,
			h : NUMBER,
			m : NUMBER,
			s : NUMBER,
			S : NUMBER,
			Z : TIMEZONE
		};
		var ONE_DAY = 24 * 60 * 60 * 1000;
		var ONE_WEEK = 7 * ONE_DAY;
		var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;

		var newDateAtMidnight = function(year, month, day) {
			var d = new Date(year, month, day, 0, 0, 0);
			d.setMilliseconds(0);
			return d;
		};

		Date.prototype.getDifference = function(date) {
			return this.getTime() - date.getTime();
		};

		Date.prototype.isBefore = function(d) {
			return this.getTime() < d.getTime();
		};

		Date.prototype.getUTCTime = function() {
			return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
					this.getSeconds(), this.getMilliseconds());
		};

		Date.prototype.getTimeSince = function(d) {
			return this.getUTCTime() - d.getUTCTime();
		};

		Date.prototype.getPreviousSunday = function() {
			// Using midday avoids any possibility of DST messing things up
			var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
			var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
			return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
					previousSunday.getDate());
		};

		Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
			if (isUndefined(this.minimalDaysInFirstWeek)) {
				minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
			}
			var previousSunday = this.getPreviousSunday();
			var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
			var numberOfSundays = previousSunday.isBefore(startOfYear) ?
				0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
			var numberOfDaysInFirstWeek =  7 - startOfYear.getDay();
			var weekInYear = numberOfSundays;
			if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
				weekInYear--;
			}
			return weekInYear;
		};

		Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
			if (isUndefined(this.minimalDaysInFirstWeek)) {
				minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
			}
			var previousSunday = this.getPreviousSunday();
			var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
			var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
				0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK);
			var numberOfDaysInFirstWeek =  7 - startOfMonth.getDay();
			var weekInMonth = numberOfSundays;
			if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
				weekInMonth++;
			}
			return weekInMonth;
		};

		Date.prototype.getDayInYear = function() {
			var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
			return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
		};

		/* ------------------------------------------------------------------ */

		SimpleDateFormat = function(formatString) {
			this.formatString = formatString;
		};

		/**
		 * Sets the minimum number of days in a week in order for that week to
		 * be considered as belonging to a particular month or year
		 */
		SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
			this.minimalDaysInFirstWeek = days;
		};

		SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() {
			return isUndefined(this.minimalDaysInFirstWeek)	?
				DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
		};

		var padWithZeroes = function(str, len) {
			while (str.length < len) {
				str = "0" + str;
			}
			return str;
		};

		var formatText = function(data, numberOfLetters, minLength) {
			return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
		};

		var formatNumber = function(data, numberOfLetters) {
			var dataString = "" + data;
			// Pad with 0s as necessary
			return padWithZeroes(dataString, numberOfLetters);
		};

		SimpleDateFormat.prototype.format = function(date) {
			var formattedString = "";
			var result;
			var searchString = this.formatString;
			while ((result = regex.exec(searchString))) {
				var quotedString = result[1];
				var patternLetters = result[2];
				var otherLetters = result[3];
				var otherCharacters = result[4];

				// If the pattern matched is quoted string, output the text between the quotes
				if (quotedString) {
					if (quotedString == "''") {
						formattedString += "'";
					} else {
						formattedString += quotedString.substring(1, quotedString.length - 1);
					}
				} else if (otherLetters) {
					// Swallow non-pattern letters by doing nothing here
				} else if (otherCharacters) {
					// Simply output other characters
					formattedString += otherCharacters;
				} else if (patternLetters) {
					// Replace pattern letters
					var patternLetter = patternLetters.charAt(0);
					var numberOfLetters = patternLetters.length;
					var rawData = "";
					switch(patternLetter) {
						case "G":
							rawData = "AD";
							break;
						case "y":
							rawData = date.getFullYear();
							break;
						case "M":
							rawData = date.getMonth();
							break;
						case "w":
							rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
							break;
						case "W":
							rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
							break;
						case "D":
							rawData = date.getDayInYear();
							break;
						case "d":
							rawData = date.getDate();
							break;
						case "F":
							rawData = 1 + Math.floor((date.getDate() - 1) / 7);
							break;
						case "E":
							rawData = dayNames[date.getDay()];
							break;
						case "a":
							rawData = (date.getHours() >= 12) ? "PM" : "AM";
							break;
						case "H":
							rawData = date.getHours();
							break;
						case "k":
							rawData = date.getHours() || 24;
							break;
						case "K":
							rawData = date.getHours() % 12;
							break;
						case "h":
							rawData = (date.getHours() % 12) || 12;
							break;
						case "m":
							rawData = date.getMinutes();
							break;
						case "s":
							rawData = date.getSeconds();
							break;
						case "S":
							rawData = date.getMilliseconds();
							break;
						case "Z":
							rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time.
							break;
					}
					// Format the raw data depending on the type
					switch(types[patternLetter]) {
						case TEXT2:
							formattedString += formatText(rawData, numberOfLetters, 2);
							break;
						case TEXT3:
							formattedString += formatText(rawData, numberOfLetters, 3);
							break;
						case NUMBER:
							formattedString += formatNumber(rawData, numberOfLetters);
							break;
						case YEAR:
							if (numberOfLetters <= 3) {
								// Output a 2-digit year
								var dataString = "" + rawData;
								formattedString += dataString.substr(2, 2);
							} else {
								formattedString += formatNumber(rawData, numberOfLetters);
							}
							break;
						case MONTH:
							if (numberOfLetters >= 3) {
								formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
							} else {
								// NB. Months returned by getMonth are zero-based
								formattedString += formatNumber(rawData + 1, numberOfLetters);
							}
							break;
						case TIMEZONE:
							var isPositive = (rawData > 0);
							// The following line looks like a mistake but isn't
							// because of the way getTimezoneOffset measures.
							var prefix = isPositive ? "-" : "+";
							var absData = Math.abs(rawData);

							// Hours
							var hours = "" + Math.floor(absData / 60);
							hours = padWithZeroes(hours, 2);
							// Minutes
							var minutes = "" + (absData % 60);
							minutes = padWithZeroes(minutes, 2);

							formattedString += prefix + hours + minutes;
							break;
					}
				}
				searchString = searchString.substr(result.index + result[0].length);
			}
			return formattedString;
		};
	})();

	log4javascript.SimpleDateFormat = SimpleDateFormat;

	/* ---------------------------------------------------------------------- */
	// PatternLayout

	function PatternLayout(pattern) {
		if (pattern) {
			this.pattern = pattern;
		} else {
			this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
		}
		this.customFields = [];
	}

	PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
	PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n";
	PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
	PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS";
	PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS";

	PatternLayout.prototype = new Layout();

	PatternLayout.prototype.format = function(loggingEvent) {
		var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;
		var formattedString = "";
		var result;
		var searchString = this.pattern;

		// Cannot use regex global flag since it doesn't work with exec in IE5
		while ((result = regex.exec(searchString))) {
			var matchedString = result[0];
			var padding = result[1];
			var truncation = result[2];
			var conversionCharacter = result[3];
			var specifier = result[5];
			var text = result[6];

			// Check if the pattern matched was just normal text
			if (text) {
				formattedString += "" + text;
			} else {
				// Create a raw replacement string based on the conversion
				// character and specifier
				var replacement = "";
				switch(conversionCharacter) {
					case "a": // Array of messages
					case "m": // Message
						var depth = 0;
						if (specifier) {
							depth = parseInt(specifier, 10);
							if (isNaN(depth)) {
								handleError("PatternLayout.format: invalid specifier '" +
									specifier + "' for conversion character '" + conversionCharacter +
									"' - should be a number");
								depth = 0;
							}
						}
						var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages;
						for (var i = 0, len = messages.length; i < len; i++) {
							if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) {
								replacement += " ";
							}
							if (depth === 0) {
								replacement += messages[i];
							} else {
								replacement += formatObjectExpansion(messages[i], depth);
							}
						}
						break;
					case "c": // Logger name
						var loggerName = loggingEvent.logger.name;
						if (specifier) {
							var precision = parseInt(specifier, 10);
							var loggerNameBits = loggingEvent.logger.name.split(".");
							if (precision >= loggerNameBits.length) {
								replacement = loggerName;
							} else {
								replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
							}
						} else {
							replacement = loggerName;
						}
						break;
					case "d": // Date
						var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
						if (specifier) {
							dateFormat = specifier;
							// Pick up special cases
							if (dateFormat == "ISO8601") {
								dateFormat = PatternLayout.ISO8601_DATEFORMAT;
							} else if (dateFormat == "ABSOLUTE") {
								dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
							} else if (dateFormat == "DATE") {
								dateFormat = PatternLayout.DATETIME_DATEFORMAT;
							}
						}
						// Format the date
						replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);
						break;
					case "f": // Custom field
						if (this.hasCustomFields()) {
							var fieldIndex = 0;
							if (specifier) {
								fieldIndex = parseInt(specifier, 10);
								if (isNaN(fieldIndex)) {
									handleError("PatternLayout.format: invalid specifier '" +
										specifier + "' for conversion character 'f' - should be a number");
								} else if (fieldIndex === 0) {
									handleError("PatternLayout.format: invalid specifier '" +
										specifier + "' for conversion character 'f' - must be greater than zero");
								} else if (fieldIndex > this.customFields.length) {
									handleError("PatternLayout.format: invalid specifier '" +
										specifier + "' for conversion character 'f' - there aren't that many custom fields");
								} else {
									fieldIndex = fieldIndex - 1;
								}
							}
                            var val = this.customFields[fieldIndex].value;
                            if (typeof val == "function") {
                                val = val(this, loggingEvent);
                            }
                            replacement = val;
						}
						break;
					case "n": // New line
						replacement = newLine;
						break;
					case "p": // Level
						replacement = loggingEvent.level.name;
						break;
					case "r": // Milliseconds since log4javascript startup
						replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate);
						break;
					case "%": // Literal % sign
						replacement = "%";
						break;
					default:
						replacement = matchedString;
						break;
				}
				// Format the replacement according to any padding or
				// truncation specified
				var l;

				// First, truncation
				if (truncation) {
					l = parseInt(truncation.substr(1), 10);
					var strLen = replacement.length;
					if (l < strLen) {
						replacement = replacement.substring(strLen - l, strLen);
					}
				}
				// Next, padding
				if (padding) {
					if (padding.charAt(0) == "-") {
						l = parseInt(padding.substr(1), 10);
						// Right pad with spaces
						while (replacement.length < l) {
							replacement += " ";
						}
					} else {
						l = parseInt(padding, 10);
						// Left pad with spaces
						while (replacement.length < l) {
							replacement = " " + replacement;
						}
					}
				}
				formattedString += replacement;
			}
			searchString = searchString.substr(result.index + result[0].length);
		}
		return formattedString;
	};

	PatternLayout.prototype.ignoresThrowable = function() {
	    return true;
	};

	PatternLayout.prototype.toString = function() {
	    return "PatternLayout";
	};

	log4javascript.PatternLayout = PatternLayout;
	/* ---------------------------------------------------------------------- */
	// AlertAppender

	function AlertAppender() {}

	AlertAppender.prototype = new Appender();

	AlertAppender.prototype.layout = new SimpleLayout();

	AlertAppender.prototype.append = function(loggingEvent) {
		alert( this.getLayout().formatWithException(loggingEvent) );
	};

	AlertAppender.prototype.toString = function() {
		return "AlertAppender";
	};

	log4javascript.AlertAppender = AlertAppender;
	/* ---------------------------------------------------------------------- */
	// BrowserConsoleAppender (only works in Opera and Safari and Firefox with
	// Firebug extension)

	function BrowserConsoleAppender() {}

	BrowserConsoleAppender.prototype = new log4javascript.Appender();
	BrowserConsoleAppender.prototype.layout = new NullLayout();
	BrowserConsoleAppender.prototype.threshold = Level.DEBUG;

	BrowserConsoleAppender.prototype.append = function(loggingEvent) {
		var appender = this;

		var getFormattedMessage = function() {
			var formattedMessage = appender.getLayout().formatWithException(loggingEvent);
			return (typeof formattedMessage == "string") ? [formattedMessage] : formattedMessage;
		};

		var console;

		if ( (console = window.console) && console.log) { // Safari and Firebug
			var formattedMessage = getFormattedMessage();

			// Log to Firebug or the browser console using specific logging
			// methods or revert to console.log otherwise 
			var consoleMethodName;

			if (console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
				consoleMethodName = "debug";
			} else if (console.info && Level.INFO.equals(loggingEvent.level)) {
				consoleMethodName = "info";
			} else if (console.warn && Level.WARN.equals(loggingEvent.level)) {
				consoleMethodName = "warn";
			} else if (console.error && loggingEvent.level.isGreaterOrEqual(Level.ERROR)) {
				consoleMethodName = "error";
			} else {
				consoleMethodName = "log";
			}

			if (console[consoleMethodName].apply) {
				console[consoleMethodName].apply(console, formattedMessage);
			} else {
				console[consoleMethodName](formattedMessage);
			}
		} else if ((typeof opera != "undefined") && opera.postError) { // Opera
			opera.postError(getFormattedMessage());
		}
	};

	BrowserConsoleAppender.prototype.group = function(name) {
		if (window.console && window.console.group) {
			window.console.group(name);
		}
	};

	BrowserConsoleAppender.prototype.groupEnd = function() {
		if (window.console && window.console.groupEnd) {
			window.console.groupEnd();
		}
	};

	BrowserConsoleAppender.prototype.toString = function() {
		return "BrowserConsoleAppender";
	};

	log4javascript.BrowserConsoleAppender = BrowserConsoleAppender;
	/* ---------------------------------------------------------------------- */
	// AjaxAppender related

	var xhrFactory = function() { return new XMLHttpRequest(); };
	var xmlHttpFactories = [
		xhrFactory,
		function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
		function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
	];

	var withCredentialsSupported = false;
	var getXmlHttp = function(errorHandler) {
		// This is only run the first time; the value of getXmlHttp gets
		// replaced with the factory that succeeds on the first run
		var xmlHttp = null, factory;
		for (var i = 0, len = xmlHttpFactories.length; i < len; i++) {
			factory = xmlHttpFactories[i];
			try {
				xmlHttp = factory();
				withCredentialsSupported = (factory == xhrFactory && ("withCredentials" in xmlHttp));
				getXmlHttp = factory;
				return xmlHttp;
			} catch (e) {
			}
		}
		// If we're here, all factories have failed, so throw an error
		if (errorHandler) {
			errorHandler();
		} else {
			handleError("getXmlHttp: unable to obtain XMLHttpRequest object");
		}
	};

	function isHttpRequestSuccessful(xmlHttp) {
		return isUndefined(xmlHttp.status) || xmlHttp.status === 0 ||
			(xmlHttp.status >= 200 && xmlHttp.status < 300) ||
			xmlHttp.status == 1223 /* Fix for IE */;
	}

	/* ---------------------------------------------------------------------- */
	// AjaxAppender

	function AjaxAppender(url, withCredentials) {
		var appender = this;
		var isSupported = true;
		if (!url) {
			handleError("AjaxAppender: URL must be specified in constructor");
			isSupported = false;
		}

		var timed = this.defaults.timed;
		var waitForResponse = this.defaults.waitForResponse;
		var batchSize = this.defaults.batchSize;
		var timerInterval = this.defaults.timerInterval;
		var requestSuccessCallback = this.defaults.requestSuccessCallback;
		var failCallback = this.defaults.failCallback;
		var postVarName = this.defaults.postVarName;
		var sendAllOnUnload = this.defaults.sendAllOnUnload;
		var contentType = this.defaults.contentType;
		var sessionId = null;

		var queuedLoggingEvents = [];
		var queuedRequests = [];
		var headers = [];
		var sending = false;
		var initialized = false;

		// Configuration methods. The function scope is used to prevent
		// direct alteration to the appender configuration properties.
		function checkCanConfigure(configOptionName) {
			if (initialized) {
				handleError("AjaxAppender: configuration option '" +
					configOptionName +
					"' may not be set after the appender has been initialized");
				return false;
			}
			return true;
		}

		this.getSessionId = function() { return sessionId; };
		this.setSessionId = function(sessionIdParam) {
			sessionId = extractStringFromParam(sessionIdParam, null);
			this.layout.setCustomField("sessionid", sessionId);
		};

		this.setLayout = function(layoutParam) {
			if (checkCanConfigure("layout")) {
				this.layout = layoutParam;
				// Set the session id as a custom field on the layout, if not already present
				if (sessionId !== null) {
					this.setSessionId(sessionId);
				}
			}
		};

		this.isTimed = function() { return timed; };
		this.setTimed = function(timedParam) {
			if (checkCanConfigure("timed")) {
				timed = bool(timedParam);
			}
		};

		this.getTimerInterval = function() { return timerInterval; };
		this.setTimerInterval = function(timerIntervalParam) {
			if (checkCanConfigure("timerInterval")) {
				timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
			}
		};

		this.isWaitForResponse = function() { return waitForResponse; };
		this.setWaitForResponse = function(waitForResponseParam) {
			if (checkCanConfigure("waitForResponse")) {
				waitForResponse = bool(waitForResponseParam);
			}
		};

		this.getBatchSize = function() { return batchSize; };
		this.setBatchSize = function(batchSizeParam) {
			if (checkCanConfigure("batchSize")) {
				batchSize = extractIntFromParam(batchSizeParam, batchSize);
			}
		};

		this.isSendAllOnUnload = function() { return sendAllOnUnload; };
		this.setSendAllOnUnload = function(sendAllOnUnloadParam) {
			if (checkCanConfigure("sendAllOnUnload")) {
				sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload);
			}
		};

		this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
			requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
		};

		this.setFailCallback = function(failCallbackParam) {
			failCallback = extractFunctionFromParam(failCallbackParam, failCallback);
		};

		this.getPostVarName = function() { return postVarName; };
		this.setPostVarName = function(postVarNameParam) {
			if (checkCanConfigure("postVarName")) {
				postVarName = extractStringFromParam(postVarNameParam, postVarName);
			}
		};

		this.getHeaders = function() { return headers; };
		this.addHeader = function(name, value) {
			if (name.toLowerCase() == "content-type") {
				contentType = value;
			} else {
				headers.push( { name: name, value: value } );
			}
		};

		// Internal functions
		function sendAll() {
			if (isSupported && enabled) {
				sending = true;
				var currentRequestBatch;
				if (waitForResponse) {
					// Send the first request then use this function as the callback once
					// the response comes back
					if (queuedRequests.length > 0) {
						currentRequestBatch = queuedRequests.shift();
						sendRequest(preparePostData(currentRequestBatch), sendAll);
					} else {
						sending = false;
						if (timed) {
							scheduleSending();
						}
					}
				} else {
					// Rattle off all the requests without waiting to see the response
					while ((currentRequestBatch = queuedRequests.shift())) {
						sendRequest(preparePostData(currentRequestBatch));
					}
					sending = false;
					if (timed) {
						scheduleSending();
					}
				}
			}
		}

		this.sendAll = sendAll;

		// Called when the window unloads. At this point we're past caring about
		// waiting for responses or timers or incomplete batches - everything
		// must go, now
		function sendAllRemaining() {
			var sendingAnything = false;
			if (isSupported && enabled) {
				// Create requests for everything left over, batched as normal
				var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1;
				var currentLoggingEvent;
				var batchedLoggingEvents = [];
				while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
					batchedLoggingEvents.push(currentLoggingEvent);
					if (queuedLoggingEvents.length >= actualBatchSize) {
						// Queue this batch of log entries
						queuedRequests.push(batchedLoggingEvents);
						batchedLoggingEvents = [];
					}
				}
				// If there's a partially completed batch, add it
				if (batchedLoggingEvents.length > 0) {
					queuedRequests.push(batchedLoggingEvents);
				}
				sendingAnything = (queuedRequests.length > 0);
				waitForResponse = false;
				timed = false;
				sendAll();
			}
			return sendingAnything;
		}

		this.sendAllRemaining = sendAllRemaining;

		function preparePostData(batchedLoggingEvents) {
			// Format the logging events
			var formattedMessages = [];
			var currentLoggingEvent;
			var postData = "";
			while ((currentLoggingEvent = batchedLoggingEvents.shift())) {
				formattedMessages.push( appender.getLayout().formatWithException(currentLoggingEvent) );
			}
			// Create the post data string
			if (batchedLoggingEvents.length == 1) {
				postData = formattedMessages.join("");
			} else {
				postData = appender.getLayout().batchHeader +
					formattedMessages.join(appender.getLayout().batchSeparator) +
					appender.getLayout().batchFooter;
			}
			if (contentType == appender.defaults.contentType) {
				postData = appender.getLayout().returnsPostData ? postData :
					urlEncode(postVarName) + "=" + urlEncode(postData);
				// Add the layout name to the post data
				if (postData.length > 0) {
					postData += "&";
				}
				postData += "layout=" + urlEncode(appender.getLayout().toString());
			}
			return postData;
		}

		function scheduleSending() {
			window.setTimeout(sendAll, timerInterval);
		}

		function xmlHttpErrorHandler() {
			var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";
			handleError(msg);
			isSupported = false;
			if (failCallback) {
				failCallback(msg);
			}
		}

		function sendRequest(postData, successCallback) {
			try {
				var xmlHttp = getXmlHttp(xmlHttpErrorHandler);
				if (isSupported) {
					// Add withCredentials to facilitate CORS requests with cookies
					if (withCredentials && withCredentialsSupported) {
						xmlHttp.withCredentials = true;
					}
					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 [
'',
'',
'	',
'		log4javascript',
'		',
'		',
'		',
'		',
'		',
'		',
'		',
'		',
'	',
'',
'	',
'		
', '
', '
', ' Filters:', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '
', ' ', '
', ' Options:', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '
', '
', '
', '
', '
', '
', ' ', ' ', '
', '
', ' ', '', '' ]; }; var defaultCommandLineFunctions = []; ConsoleAppender = function() {}; var consoleAppenderIdCounter = 1; ConsoleAppender.prototype = new Appender(); ConsoleAppender.prototype.create = function(inPage, container, lazyInit, initiallyMinimized, useDocumentWrite, width, height, focusConsoleWindow) { var appender = this; // Common properties var initialized = false; var consoleWindowCreated = false; var consoleWindowLoaded = false; var consoleClosed = false; var queuedLoggingEvents = []; var isSupported = true; var consoleAppenderId = consoleAppenderIdCounter++; // Local variables initiallyMinimized = extractBooleanFromParam(initiallyMinimized, this.defaults.initiallyMinimized); lazyInit = extractBooleanFromParam(lazyInit, this.defaults.lazyInit); useDocumentWrite = extractBooleanFromParam(useDocumentWrite, this.defaults.useDocumentWrite); var newestMessageAtTop = this.defaults.newestMessageAtTop; var scrollToLatestMessage = this.defaults.scrollToLatestMessage; width = width ? width : this.defaults.width; height = height ? height : this.defaults.height; var maxMessages = this.defaults.maxMessages; var showCommandLine = this.defaults.showCommandLine; var commandLineObjectExpansionDepth = this.defaults.commandLineObjectExpansionDepth; var showHideButton = this.defaults.showHideButton; var showCloseButton = this.defaults.showCloseButton; this.setLayout(this.defaults.layout); // Functions whose implementations vary between subclasses var init, createWindow, safeToAppend, getConsoleWindow, open; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. var appenderName = inPage ? "InPageAppender" : "PopUpAppender"; var checkCanConfigure = function(configOptionName) { if (consoleWindowCreated) { handleError(appenderName + ": configuration option '" + configOptionName + "' may not be set after the appender has been initialized"); return false; } return true; }; var consoleWindowExists = function() { return (consoleWindowLoaded && isSupported && !consoleClosed); }; this.isNewestMessageAtTop = function() { return newestMessageAtTop; }; this.setNewestMessageAtTop = function(newestMessageAtTopParam) { newestMessageAtTop = bool(newestMessageAtTopParam); if (consoleWindowExists()) { getConsoleWindow().setNewestAtTop(newestMessageAtTop); } }; this.isScrollToLatestMessage = function() { return scrollToLatestMessage; }; this.setScrollToLatestMessage = function(scrollToLatestMessageParam) { scrollToLatestMessage = bool(scrollToLatestMessageParam); if (consoleWindowExists()) { getConsoleWindow().setScrollToLatest(scrollToLatestMessage); } }; this.getWidth = function() { return width; }; this.setWidth = function(widthParam) { if (checkCanConfigure("width")) { width = extractStringFromParam(widthParam, width); } }; this.getHeight = function() { return height; }; this.setHeight = function(heightParam) { if (checkCanConfigure("height")) { height = extractStringFromParam(heightParam, height); } }; this.getMaxMessages = function() { return maxMessages; }; this.setMaxMessages = function(maxMessagesParam) { maxMessages = extractIntFromParam(maxMessagesParam, maxMessages); if (consoleWindowExists()) { getConsoleWindow().setMaxMessages(maxMessages); } }; this.isShowCommandLine = function() { return showCommandLine; }; this.setShowCommandLine = function(showCommandLineParam) { showCommandLine = bool(showCommandLineParam); if (consoleWindowExists()) { getConsoleWindow().setShowCommandLine(showCommandLine); } }; this.isShowHideButton = function() { return showHideButton; }; this.setShowHideButton = function(showHideButtonParam) { showHideButton = bool(showHideButtonParam); if (consoleWindowExists()) { getConsoleWindow().setShowHideButton(showHideButton); } }; this.isShowCloseButton = function() { return showCloseButton; }; this.setShowCloseButton = function(showCloseButtonParam) { showCloseButton = bool(showCloseButtonParam); if (consoleWindowExists()) { getConsoleWindow().setShowCloseButton(showCloseButton); } }; this.getCommandLineObjectExpansionDepth = function() { return commandLineObjectExpansionDepth; }; this.setCommandLineObjectExpansionDepth = function(commandLineObjectExpansionDepthParam) { commandLineObjectExpansionDepth = extractIntFromParam(commandLineObjectExpansionDepthParam, commandLineObjectExpansionDepth); }; var minimized = initiallyMinimized; this.isInitiallyMinimized = function() { return initiallyMinimized; }; this.setInitiallyMinimized = function(initiallyMinimizedParam) { if (checkCanConfigure("initiallyMinimized")) { initiallyMinimized = bool(initiallyMinimizedParam); minimized = initiallyMinimized; } }; this.isUseDocumentWrite = function() { return useDocumentWrite; }; this.setUseDocumentWrite = function(useDocumentWriteParam) { if (checkCanConfigure("useDocumentWrite")) { useDocumentWrite = bool(useDocumentWriteParam); } }; // Common methods function QueuedLoggingEvent(loggingEvent, formattedMessage) { this.loggingEvent = loggingEvent; this.levelName = loggingEvent.level.name; this.formattedMessage = formattedMessage; } QueuedLoggingEvent.prototype.append = function() { getConsoleWindow().log(this.levelName, this.formattedMessage); }; function QueuedGroup(name, initiallyExpanded) { this.name = name; this.initiallyExpanded = initiallyExpanded; } QueuedGroup.prototype.append = function() { getConsoleWindow().group(this.name, this.initiallyExpanded); }; function QueuedGroupEnd() {} QueuedGroupEnd.prototype.append = function() { getConsoleWindow().groupEnd(); }; var checkAndAppend = function() { // Next line forces a check of whether the window has been closed safeToAppend(); if (!initialized) { init(); } else if (consoleClosed && reopenWhenClosed) { createWindow(); } if (safeToAppend()) { appendQueuedLoggingEvents(); } }; this.append = function(loggingEvent) { if (isSupported) { // Format the message var formattedMessage = appender.getLayout().formatWithException(loggingEvent); queuedLoggingEvents.push(new QueuedLoggingEvent(loggingEvent, formattedMessage)); checkAndAppend(); } }; this.group = function(name, initiallyExpanded) { if (isSupported) { queuedLoggingEvents.push(new QueuedGroup(name, initiallyExpanded)); checkAndAppend(); } }; this.groupEnd = function() { if (isSupported) { queuedLoggingEvents.push(new QueuedGroupEnd()); checkAndAppend(); } }; var appendQueuedLoggingEvents = function() { while (queuedLoggingEvents.length > 0) { queuedLoggingEvents.shift().append(); } if (focusConsoleWindow) { getConsoleWindow().focus(); } }; this.setAddedToLogger = function(logger) { this.loggers.push(logger); if (enabled && !lazyInit) { init(); } }; this.clear = function() { if (consoleWindowExists()) { getConsoleWindow().clearLog(); } queuedLoggingEvents.length = 0; }; this.focus = function() { if (consoleWindowExists()) { getConsoleWindow().focus(); } }; this.focusCommandLine = function() { if (consoleWindowExists()) { getConsoleWindow().focusCommandLine(); } }; this.focusSearch = function() { if (consoleWindowExists()) { getConsoleWindow().focusSearch(); } }; var commandWindow = window; this.getCommandWindow = function() { return commandWindow; }; this.setCommandWindow = function(commandWindowParam) { commandWindow = commandWindowParam; }; this.executeLastCommand = function() { if (consoleWindowExists()) { getConsoleWindow().evalLastCommand(); } }; var commandLayout = new PatternLayout("%m"); this.getCommandLayout = function() { return commandLayout; }; this.setCommandLayout = function(commandLayoutParam) { commandLayout = commandLayoutParam; }; this.evalCommandAndAppend = function(expr) { var commandReturnValue = { appendResult: true, isError: false }; var commandOutput = ""; // Evaluate the command try { var result, i; // The next three lines constitute a workaround for IE. Bizarrely, iframes seem to have no // eval method on the window object initially, but once execScript has been called on // it once then the eval method magically appears. See http://www.thismuchiknow.co.uk/?p=25 if (!commandWindow.eval && commandWindow.execScript) { commandWindow.execScript("null"); } var commandLineFunctionsHash = {}; for (i = 0, len = commandLineFunctions.length; i < len; i++) { commandLineFunctionsHash[commandLineFunctions[i][0]] = commandLineFunctions[i][1]; } // Keep an array of variables that are being changed in the command window so that they // can be restored to their original values afterwards var objectsToRestore = []; var addObjectToRestore = function(name) { objectsToRestore.push([name, commandWindow[name]]); }; addObjectToRestore("appender"); commandWindow.appender = appender; addObjectToRestore("commandReturnValue"); commandWindow.commandReturnValue = commandReturnValue; addObjectToRestore("commandLineFunctionsHash"); commandWindow.commandLineFunctionsHash = commandLineFunctionsHash; var addFunctionToWindow = function(name) { addObjectToRestore(name); commandWindow[name] = function() { return this.commandLineFunctionsHash[name](appender, arguments, commandReturnValue); }; }; for (i = 0, len = commandLineFunctions.length; i < len; i++) { addFunctionToWindow(commandLineFunctions[i][0]); } // Another bizarre workaround to get IE to eval in the global scope if (commandWindow === window && commandWindow.execScript) { addObjectToRestore("evalExpr"); addObjectToRestore("result"); window.evalExpr = expr; commandWindow.execScript("window.result=eval(window.evalExpr);"); result = window.result; } else { result = commandWindow.eval(expr); } commandOutput = isUndefined(result) ? result : formatObjectExpansion(result, commandLineObjectExpansionDepth); // Restore variables in the command window to their original state for (i = 0, len = objectsToRestore.length; i < len; i++) { commandWindow[objectsToRestore[i][0]] = objectsToRestore[i][1]; } } catch (ex) { commandOutput = "Error evaluating command: " + getExceptionStringRep(ex); commandReturnValue.isError = true; } // Append command output if (commandReturnValue.appendResult) { var message = ">>> " + expr; if (!isUndefined(commandOutput)) { message += newLine + commandOutput; } var level = commandReturnValue.isError ? Level.ERROR : Level.INFO; var loggingEvent = new LoggingEvent(null, new Date(), level, [message], null); var mainLayout = this.getLayout(); this.setLayout(commandLayout); this.append(loggingEvent); this.setLayout(mainLayout); } }; var commandLineFunctions = defaultCommandLineFunctions.concat([]); this.addCommandLineFunction = function(functionName, commandLineFunction) { commandLineFunctions.push([functionName, commandLineFunction]); }; var commandHistoryCookieName = "log4javascriptCommandHistory"; this.storeCommandHistory = function(commandHistory) { setCookie(commandHistoryCookieName, commandHistory.join(",")); }; var writeHtml = function(doc) { var lines = getConsoleHtmlLines(); doc.open(); for (var i = 0, len = lines.length; i < len; i++) { doc.writeln(lines[i]); } doc.close(); }; // Set up event listeners this.setEventTypes(["load", "unload"]); var consoleWindowLoadHandler = function() { var win = getConsoleWindow(); win.setAppender(appender); win.setNewestAtTop(newestMessageAtTop); win.setScrollToLatest(scrollToLatestMessage); win.setMaxMessages(maxMessages); win.setShowCommandLine(showCommandLine); win.setShowHideButton(showHideButton); win.setShowCloseButton(showCloseButton); win.setMainWindow(window); // Restore command history stored in cookie var storedValue = getCookie(commandHistoryCookieName); if (storedValue) { win.commandHistory = storedValue.split(","); win.currentCommandIndex = win.commandHistory.length; } appender.dispatchEvent("load", { "win" : win }); }; this.unload = function() { logLog.debug("unload " + this + ", caller: " + this.unload.caller); if (!consoleClosed) { logLog.debug("really doing unload " + this); consoleClosed = true; consoleWindowLoaded = false; consoleWindowCreated = false; appender.dispatchEvent("unload", {}); } }; var pollConsoleWindow = function(windowTest, interval, successCallback, errorMessage) { function doPoll() { try { // Test if the console has been closed while polling if (consoleClosed) { clearInterval(poll); } if (windowTest(getConsoleWindow())) { clearInterval(poll); successCallback(); } } catch (ex) { clearInterval(poll); isSupported = false; handleError(errorMessage, ex); } } // Poll the pop-up since the onload event is not reliable var poll = setInterval(doPoll, interval); }; var getConsoleUrl = function() { var documentDomainSet = (document.domain != location.hostname); return useDocumentWrite ? "" : getBaseUrl() + "console_uncompressed.html" + (documentDomainSet ? "?log4javascript_domain=" + escape(document.domain) : ""); }; // Define methods and properties that vary between subclasses if (inPage) { // InPageAppender var containerElement = null; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. var cssProperties = []; this.addCssProperty = function(name, value) { if (checkCanConfigure("cssProperties")) { cssProperties.push([name, value]); } }; // Define useful variables var windowCreationStarted = false; var iframeContainerDiv; var iframeId = uniqueId + "_InPageAppender_" + consoleAppenderId; this.hide = function() { if (initialized && consoleWindowCreated) { if (consoleWindowExists()) { getConsoleWindow().$("command").blur(); } iframeContainerDiv.style.display = "none"; minimized = true; } }; this.show = function() { if (initialized) { if (consoleWindowCreated) { iframeContainerDiv.style.display = "block"; this.setShowCommandLine(showCommandLine); // Force IE to update minimized = false; } else if (!windowCreationStarted) { createWindow(true); } } }; this.isVisible = function() { return !minimized && !consoleClosed; }; this.close = function(fromButton) { if (!consoleClosed && (!fromButton || confirm("This will permanently remove the console from the page. No more messages will be logged. Do you wish to continue?"))) { iframeContainerDiv.parentNode.removeChild(iframeContainerDiv); this.unload(); } }; // Create open, init, getConsoleWindow and safeToAppend functions open = function() { var initErrorMessage = "InPageAppender.open: unable to create console iframe"; function finalInit() { try { if (!initiallyMinimized) { appender.show(); } consoleWindowLoadHandler(); consoleWindowLoaded = true; appendQueuedLoggingEvents(); } catch (ex) { isSupported = false; handleError(initErrorMessage, ex); } } function writeToDocument() { try { var windowTest = function(win) { return isLoaded(win); }; if (useDocumentWrite) { writeHtml(getConsoleWindow().document); } if (windowTest(getConsoleWindow())) { finalInit(); } else { pollConsoleWindow(windowTest, 100, finalInit, initErrorMessage); } } catch (ex) { isSupported = false; handleError(initErrorMessage, ex); } } minimized = false; iframeContainerDiv = containerElement.appendChild(document.createElement("div")); iframeContainerDiv.style.width = width; iframeContainerDiv.style.height = height; iframeContainerDiv.style.border = "solid gray 1px"; for (var i = 0, len = cssProperties.length; i < len; i++) { iframeContainerDiv.style[cssProperties[i][0]] = cssProperties[i][1]; } var iframeSrc = useDocumentWrite ? "" : " src='" + getConsoleUrl() + "'"; // Adding an iframe using the DOM would be preferable, but it doesn't work // in IE5 on Windows, or in Konqueror prior to version 3.5 - in Konqueror // it creates the iframe fine but I haven't been able to find a way to obtain // the iframe's window object iframeContainerDiv.innerHTML = ""; consoleClosed = false; // Write the console HTML to the iframe var iframeDocumentExistsTest = function(win) { try { return bool(win) && bool(win.document); } catch (ex) { return false; } }; if (iframeDocumentExistsTest(getConsoleWindow())) { writeToDocument(); } else { pollConsoleWindow(iframeDocumentExistsTest, 100, writeToDocument, initErrorMessage); } consoleWindowCreated = true; }; createWindow = function(show) { if (show || !initiallyMinimized) { var pageLoadHandler = function() { if (!container) { // Set up default container element containerElement = document.createElement("div"); containerElement.style.position = "fixed"; containerElement.style.left = "0"; containerElement.style.right = "0"; containerElement.style.bottom = "0"; document.body.appendChild(containerElement); appender.addCssProperty("borderWidth", "1px 0 0 0"); appender.addCssProperty("zIndex", 1000000); // Can't find anything authoritative that says how big z-index can be open(); } else { try { var el = document.getElementById(container); if (el.nodeType == 1) { containerElement = el; } open(); } catch (ex) { handleError("InPageAppender.init: invalid container element '" + container + "' supplied", ex); } } }; // Test the type of the container supplied. First, check if it's an element if (pageLoaded && container && container.appendChild) { containerElement = container; open(); } else if (pageLoaded) { pageLoadHandler(); } else { log4javascript.addEventListener("load", pageLoadHandler); } windowCreationStarted = true; } }; init = function() { createWindow(); initialized = true; }; getConsoleWindow = function() { var iframe = window.frames[iframeId]; if (iframe) { return iframe; } }; safeToAppend = function() { if (isSupported && !consoleClosed) { if (consoleWindowCreated && !consoleWindowLoaded && getConsoleWindow() && isLoaded(getConsoleWindow())) { consoleWindowLoaded = true; } return consoleWindowLoaded; } return false; }; } else { // PopUpAppender // Extract params var useOldPopUp = appender.defaults.useOldPopUp; var complainAboutPopUpBlocking = appender.defaults.complainAboutPopUpBlocking; var reopenWhenClosed = this.defaults.reopenWhenClosed; // Configuration methods. The function scope is used to prevent // direct alteration to the appender configuration properties. this.isUseOldPopUp = function() { return useOldPopUp; }; this.setUseOldPopUp = function(useOldPopUpParam) { if (checkCanConfigure("useOldPopUp")) { useOldPopUp = bool(useOldPopUpParam); } }; this.isComplainAboutPopUpBlocking = function() { return complainAboutPopUpBlocking; }; this.setComplainAboutPopUpBlocking = function(complainAboutPopUpBlockingParam) { if (checkCanConfigure("complainAboutPopUpBlocking")) { complainAboutPopUpBlocking = bool(complainAboutPopUpBlockingParam); } }; this.isFocusPopUp = function() { return focusConsoleWindow; }; this.setFocusPopUp = function(focusPopUpParam) { // This property can be safely altered after logging has started focusConsoleWindow = bool(focusPopUpParam); }; this.isReopenWhenClosed = function() { return reopenWhenClosed; }; this.setReopenWhenClosed = function(reopenWhenClosedParam) { // This property can be safely altered after logging has started reopenWhenClosed = bool(reopenWhenClosedParam); }; this.close = function() { logLog.debug("close " + this); try { popUp.close(); this.unload(); } catch (ex) { // Do nothing } }; this.hide = function() { logLog.debug("hide " + this); if (consoleWindowExists()) { this.close(); } }; this.show = function() { logLog.debug("show " + this); if (!consoleWindowCreated) { open(); } }; this.isVisible = function() { return safeToAppend(); }; // Define useful variables var popUp; // Create open, init, getConsoleWindow and safeToAppend functions open = function() { var windowProperties = "width=" + width + ",height=" + height + ",status,resizable"; var frameInfo = ""; try { var frameEl = window.frameElement; if (frameEl) { frameInfo = "_" + frameEl.tagName + "_" + (frameEl.name || frameEl.id || ""); } } catch (e) { frameInfo = "_inaccessibleParentFrame"; } var windowName = "PopUp_" + location.host.replace(/[^a-z0-9]/gi, "_") + "_" + consoleAppenderId + frameInfo; if (!useOldPopUp || !useDocumentWrite) { // Ensure a previous window isn't used by using a unique name windowName = windowName + "_" + uniqueId; } var checkPopUpClosed = function(win) { if (consoleClosed) { return true; } else { try { return bool(win) && win.closed; } catch(ex) {} } return false; }; var popUpClosedCallback = function() { if (!consoleClosed) { appender.unload(); } }; function finalInit() { getConsoleWindow().setCloseIfOpenerCloses(!useOldPopUp || !useDocumentWrite); consoleWindowLoadHandler(); consoleWindowLoaded = true; appendQueuedLoggingEvents(); pollConsoleWindow(checkPopUpClosed, 500, popUpClosedCallback, "PopUpAppender.checkPopUpClosed: error checking pop-up window"); } try { popUp = window.open(getConsoleUrl(), windowName, windowProperties); consoleClosed = false; consoleWindowCreated = true; if (popUp && popUp.document) { if (useDocumentWrite && useOldPopUp && isLoaded(popUp)) { popUp.mainPageReloaded(); finalInit(); } else { if (useDocumentWrite) { writeHtml(popUp.document); } // Check if the pop-up window object is available var popUpLoadedTest = function(win) { return bool(win) && isLoaded(win); }; if (isLoaded(popUp)) { finalInit(); } else { pollConsoleWindow(popUpLoadedTest, 100, finalInit, "PopUpAppender.init: unable to create console window"); } } } else { isSupported = false; logLog.warn("PopUpAppender.init: pop-ups blocked, please unblock to use PopUpAppender"); if (complainAboutPopUpBlocking) { handleError("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging."); } } } catch (ex) { handleError("PopUpAppender.init: error creating pop-up", ex); } }; createWindow = function() { if (!initiallyMinimized) { open(); } }; init = function() { createWindow(); initialized = true; }; getConsoleWindow = function() { return popUp; }; safeToAppend = function() { if (isSupported && !isUndefined(popUp) && !consoleClosed) { if (popUp.closed || (consoleWindowLoaded && isUndefined(popUp.closed))) { // Extra check for Opera appender.unload(); logLog.debug("PopUpAppender: pop-up closed"); return false; } if (!consoleWindowLoaded && isLoaded(popUp)) { consoleWindowLoaded = true; } } return isSupported && consoleWindowLoaded && !consoleClosed; }; } // Expose getConsoleWindow so that automated tests can check the DOM this.getConsoleWindow = getConsoleWindow; }; ConsoleAppender.addGlobalCommandLineFunction = function(functionName, commandLineFunction) { defaultCommandLineFunctions.push([functionName, commandLineFunction]); }; /* ------------------------------------------------------------------ */ function PopUpAppender(lazyInit, initiallyMinimized, useDocumentWrite, width, height) { this.create(false, null, lazyInit, initiallyMinimized, useDocumentWrite, width, height, this.defaults.focusPopUp); } PopUpAppender.prototype = new ConsoleAppender(); PopUpAppender.prototype.defaults = { layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"), initiallyMinimized: false, focusPopUp: false, lazyInit: true, useOldPopUp: true, complainAboutPopUpBlocking: true, newestMessageAtTop: false, scrollToLatestMessage: true, width: "600", height: "400", reopenWhenClosed: false, maxMessages: null, showCommandLine: true, commandLineObjectExpansionDepth: 1, showHideButton: false, showCloseButton: true, useDocumentWrite: true }; PopUpAppender.prototype.toString = function() { return "PopUpAppender"; }; log4javascript.PopUpAppender = PopUpAppender; /* ------------------------------------------------------------------ */ function InPageAppender(container, lazyInit, initiallyMinimized, useDocumentWrite, width, height) { this.create(true, container, lazyInit, initiallyMinimized, useDocumentWrite, width, height, false); } InPageAppender.prototype = new ConsoleAppender(); InPageAppender.prototype.defaults = { layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"), initiallyMinimized: false, lazyInit: true, newestMessageAtTop: false, scrollToLatestMessage: true, width: "100%", height: "220px", maxMessages: null, showCommandLine: true, commandLineObjectExpansionDepth: 1, showHideButton: false, showCloseButton: false, showLogEntryDeleteButtons: true, useDocumentWrite: true }; InPageAppender.prototype.toString = function() { return "InPageAppender"; }; log4javascript.InPageAppender = InPageAppender; // Next line for backwards compatibility log4javascript.InlineAppender = InPageAppender; })(); /* ---------------------------------------------------------------------- */ // Console extension functions function padWithSpaces(str, len) { if (str.length < len) { var spaces = []; var numberOfSpaces = Math.max(0, len - str.length); for (var i = 0; i < numberOfSpaces; i++) { spaces[i] = " "; } str += spaces.join(""); } return str; } (function() { function dir(obj) { var maxLen = 0; // Obtain the length of the longest property name for (var p in obj) { maxLen = Math.max(toStr(p).length, maxLen); } // Create the nicely formatted property list var propList = []; for (p in obj) { var propNameStr = " " + padWithSpaces(toStr(p), maxLen + 2); var propVal; try { propVal = splitIntoLines(toStr(obj[p])).join(padWithSpaces(newLine, maxLen + 6)); } catch (ex) { propVal = "[Error obtaining property. Details: " + getExceptionMessage(ex) + "]"; } propList.push(propNameStr + propVal); } return propList.join(newLine); } var nodeTypes = { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }; var preFormattedElements = ["script", "pre"]; // This should be the definitive list, as specified by the XHTML 1.0 Transitional DTD var emptyElements = ["br", "img", "hr", "param", "link", "area", "input", "col", "base", "meta"]; var indentationUnit = " "; // Create and return an XHTML string from the node specified function getXhtml(rootNode, includeRootNode, indentation, startNewLine, preformatted) { includeRootNode = (typeof includeRootNode == "undefined") ? true : !!includeRootNode; if (typeof indentation != "string") { indentation = ""; } startNewLine = !!startNewLine; preformatted = !!preformatted; var xhtml; function isWhitespace(node) { return ((node.nodeType == nodeTypes.TEXT_NODE) && /^[ \t\r\n]*$/.test(node.nodeValue)); } function fixAttributeValue(attrValue) { return attrValue.toString().replace(/&/g, "&").replace(/]*>", "i"); if (regex.test(el.outerHTML)) { return RegExp.$1.toLowerCase(); } } return ""; } var lt = "<"; var gt = ">"; var i, len; if (includeRootNode && rootNode.nodeType != nodeTypes.DOCUMENT_FRAGMENT_NODE) { switch (rootNode.nodeType) { case nodeTypes.ELEMENT_NODE: var tagName = rootNode.tagName.toLowerCase(); xhtml = startNewLine ? newLine + indentation : ""; xhtml += lt; // Allow for namespaces, where present var prefix = getNamespace(rootNode); var hasPrefix = !!prefix; if (hasPrefix) { xhtml += prefix + ":"; } xhtml += tagName; for (i = 0, len = rootNode.attributes.length; i < len; i++) { var currentAttr = rootNode.attributes[i]; // Check the attribute is valid. if (! currentAttr.specified || currentAttr.nodeValue === null || currentAttr.nodeName.toLowerCase() === "style" || typeof currentAttr.nodeValue !== "string" || currentAttr.nodeName.indexOf("_moz") === 0) { continue; } xhtml += " " + currentAttr.nodeName.toLowerCase() + "=\""; xhtml += fixAttributeValue(currentAttr.nodeValue); xhtml += "\""; } // Style needs to be done separately as it is not reported as an // attribute in IE if (rootNode.style.cssText) { var styleValue = getStyleAttributeValue(rootNode); if (styleValue !== "") { xhtml += " style=\"" + getStyleAttributeValue(rootNode) + "\""; } } if (array_contains(emptyElements, tagName) || (hasPrefix && !rootNode.hasChildNodes())) { xhtml += "/" + gt; } else { xhtml += gt; // Add output for childNodes collection (which doesn't include attribute nodes) var childStartNewLine = !(rootNode.childNodes.length === 1 && rootNode.childNodes[0].nodeType === nodeTypes.TEXT_NODE); var childPreformatted = array_contains(preFormattedElements, tagName); for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit, childStartNewLine, childPreformatted); } // Add the end tag var endTag = lt + "/" + tagName + gt; xhtml += childStartNewLine ? newLine + indentation + endTag : endTag; } return xhtml; case nodeTypes.TEXT_NODE: if (isWhitespace(rootNode)) { xhtml = ""; } else { if (preformatted) { xhtml = rootNode.nodeValue; } else { // Trim whitespace from each line of the text node var lines = splitIntoLines(trim(rootNode.nodeValue)); var trimmedLines = []; for (i = 0, len = lines.length; i < len; i++) { trimmedLines[i] = trim(lines[i]); } xhtml = trimmedLines.join(newLine + indentation); } if (startNewLine) { xhtml = newLine + indentation + xhtml; } } return xhtml; case nodeTypes.CDATA_SECTION_NODE: return "" + newLine; case nodeTypes.DOCUMENT_NODE: xhtml = ""; // Add output for childNodes collection (which doesn't include attribute nodes) for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation); } return xhtml; default: return ""; } } else { xhtml = ""; // Add output for childNodes collection (which doesn't include attribute nodes) for (i = 0, len = rootNode.childNodes.length; i < len; i++) { xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit); } return xhtml; } } function createCommandLineFunctions() { ConsoleAppender.addGlobalCommandLineFunction("$", function(appender, args, returnValue) { return document.getElementById(args[0]); }); ConsoleAppender.addGlobalCommandLineFunction("dir", function(appender, args, returnValue) { var lines = []; for (var i = 0, len = args.length; i < len; i++) { lines[i] = dir(args[i]); } return lines.join(newLine + newLine); }); ConsoleAppender.addGlobalCommandLineFunction("dirxml", function(appender, args, returnValue) { var lines = []; for (var i = 0, len = args.length; i < len; i++) { lines[i] = getXhtml(args[i]); } return lines.join(newLine + newLine); }); ConsoleAppender.addGlobalCommandLineFunction("cd", function(appender, args, returnValue) { var win, message; if (args.length === 0 || args[0] === "") { win = window; message = "Command line set to run in main window"; } else { if (args[0].window == args[0]) { win = args[0]; message = "Command line set to run in frame '" + args[0].name + "'"; } else { win = window.frames[args[0]]; if (win) { message = "Command line set to run in frame '" + args[0] + "'"; } else { returnValue.isError = true; message = "Frame '" + args[0] + "' does not exist"; win = appender.getCommandWindow(); } } } appender.setCommandWindow(win); return message; }); ConsoleAppender.addGlobalCommandLineFunction("clear", function(appender, args, returnValue) { returnValue.appendResult = false; appender.clear(); }); ConsoleAppender.addGlobalCommandLineFunction("keys", function(appender, args, returnValue) { var keys = []; for (var k in args[0]) { keys.push(k); } return keys; }); ConsoleAppender.addGlobalCommandLineFunction("values", function(appender, args, returnValue) { var values = []; for (var k in args[0]) { try { values.push(args[0][k]); } catch (ex) { logLog.warn("values(): Unable to obtain value for key " + k + ". Details: " + getExceptionMessage(ex)); } } return values; }); ConsoleAppender.addGlobalCommandLineFunction("expansionDepth", function(appender, args, returnValue) { var expansionDepth = parseInt(args[0], 10); if (isNaN(expansionDepth) || expansionDepth < 0) { returnValue.isError = true; return "" + args[0] + " is not a valid expansion depth"; } else { appender.setCommandLineObjectExpansionDepth(expansionDepth); return "Object expansion depth set to " + expansionDepth; } }); } function init() { // Add command line functions createCommandLineFunctions(); } /* ------------------------------------------------------------------ */ init(); })(); /* ---------------------------------------------------------------------- */ function createDefaultLogger() { var logger = log4javascript.getLogger(defaultLoggerName); var a = new log4javascript.PopUpAppender(); logger.addAppender(a); return logger; } /* ---------------------------------------------------------------------- */ // Main load log4javascript.setDocumentReady = function() { pageLoaded = true; log4javascript.dispatchEvent("load", {}); }; if (window.addEventListener) { window.addEventListener("load", log4javascript.setDocumentReady, false); } else if (window.attachEvent) { window.attachEvent("onload", log4javascript.setDocumentReady); } else { var oldOnload = window.onload; if (typeof window.onload != "function") { window.onload = log4javascript.setDocumentReady; } else { window.onload = function(evt) { if (oldOnload) { oldOnload(evt); } log4javascript.setDocumentReady(); }; } } return log4javascript; }, this);




© 2015 - 2024 Weber Informatics LLC | Privacy Policy