Micha Kops avatar Micha Kops committed b0a18ac

initial import.

Comments (0)

Files changed (7)

+.settings
+target
+.project
+.classpath
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2012 Micha Kops 
+
+   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.
+
+
+# Confluence Log4Javascript Plugin #
+
+---------------
+
+## About ##
+
+Allows you to use the log4javascript logging API in your Confluence instance.
+
+Confluence port based on Tim Down's popular JavaScript logging framework [log4javascript].
+
+---------------
+
+## License ##
+
+This plugin is distributed under the [Apache License, Version 2.0].
+
+---------------
+
+## Download ##
+
+Download the plugin using your Confluence's Universal Plugin Manager or download the plugin from the [Atlassian Plugin Exchange].
+
+----------------
+
+## Usage ##
+
+	// 1. creates a new logger
+	var log = log4javascript.getLogger();
+
+	// 2. create an appender . we're using the PopUpAppender here
+	var appender = new log4javascript.PopUpAppender();
+
+	// 3. adds the appender to the logger
+	log.addAppender(appender);
+
+	// 4. do some logging
+	log.warn("this is a warning");
+
+----------------
+
+## Issue Tracking ##
+
+Please use the [issue-tracker] to report issues or feature requests.
+
+----------------
+
+## Contact ##
+
+Please feel free to contact me at my website, [www.hascode.com] but for issues, please use the [issue-tracker].
+
+-----------------
+
+
+  [log4javascript]:http://log4javascript.org/
+  [Apache License, Version 2.0]:http://www.apache.org/licenses/LICENSE-2.0.html
+  [Atlassian Plugin Exchange]:https://plugins.atlassian.com/plugins/com.hascode.confluence.javascript-logging-plugin
+  [www.hascode.com]:http://www.hascode.com
+  [issue-tracker]:https://bitbucket.org/hascode/confluence-log4javascript-plugin/issues
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.hascode.confluence</groupId>
+	<artifactId>javascript-logging-plugin</artifactId>
+	<version>0.1.0</version>
+	<name>javascript-logging-plugin</name>
+	<description>Allows you to use the log4javascript logging API in your Confluence instance.</description>
+	<packaging>atlassian-plugin</packaging>
+
+	<organization>
+		<name>hasCode.com</name>
+		<url>http://www.hascode.com/</url>
+	</organization>
+	<developers>
+		<developer>
+			<name>Micha Kops</name>
+			<url>http://www.hascode.com</url>
+		</developer>
+	</developers>
+
+	<licenses>
+		<license>
+			<name>Apache 2</name>
+			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+			<distribution>repo</distribution>
+			<comments>A business-friendly OSS license</comments>
+		</license>
+	</licenses>
+
+	<dependencies>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.6</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.confluence</groupId>
+			<artifactId>confluence</artifactId>
+			<version>${confluence.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.confluence.plugin</groupId>
+			<artifactId>func-test</artifactId>
+			<version>2.3</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.jwebunit</groupId>
+			<artifactId>jwebunit-htmlunit-plugin</artifactId>
+			<version>2.2</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.nekohtml</groupId>
+			<artifactId>nekohtml</artifactId>
+			<version>1.9.12</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-all</artifactId>
+			<version>1.8.5</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>com.atlassian.maven.plugins</groupId>
+				<artifactId>maven-confluence-plugin</artifactId>
+				<version>${amps.version}</version>
+				<extensions>true</extensions>
+				<configuration>
+					<productVersion>${confluence.version}</productVersion>
+					<productDataVersion>${confluence.data.version}</productDataVersion>
+					<instructions />
+				</configuration>
+			</plugin>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<properties>
+		<confluence.version>4.1.6</confluence.version>
+		<confluence.data.version>3.5</confluence.data.version>
+		<amps.version>3.10.1</amps.version>
+	</properties>
+</project>

src/main/resources/atlassian-plugin.xml

+<?xml version="1.0" encoding="UTF-8"?>
+
+<atlassian-plugin key="${project.groupId}.${project.artifactId}"
+	name="${project.name}" plugins-version="2">
+	<plugin-info>
+		<description>${project.description}</description>
+		<version>${project.version}</version>
+		<vendor name="${project.organization.name}" url="${project.organization.url}" />
+	</plugin-info>
+	<xhtml-macro name="javascript-logging-plugin" class="com.hascode.confluence.ExampleMacro"
+		key="my-macro">
+		<parameters />
+	</xhtml-macro>
+	<resource type="i18n" name="i18n"
+		location="com.hascode.confluence.javascript-logging-plugin" />
+	<web-resource name="Confluence Logging JS Resources"
+		i18n-name-key="confluence-logging-js-resources.name" key="confluence-logging-js-resources">
+		<description key="confluence-logging-js-resources.description">Logging Webresources (disable if not needed)</description>
+		<resource name="log4javascript.js" type="download"
+			location="webresources/js/log4javascript-min.js">
+		</resource>
+		<context>main</context>
+	</web-resource>
+</atlassian-plugin>

src/main/resources/com/hascode/confluence/javascript-logging-plugin.properties

+
+confluence-logging-js-resources.name=Confluence Logging JS Resources
+confluence-logging-js-resources.description=The Confluence Logging JS Resources Plugin

src/main/resources/webresources/js/log4javascript.js

+/**
+ * Copyright 2009 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.
+ * 
+ * Modified 2012 Micha Kops
+ */
+
+(function($) {
+	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;
+			var argumentsArray = [];
+			for ( var i = 0, len = arguments.length; i < len; i++) {
+				argumentsArray[i] = arguments[i];
+			}
+			var itemsToAppend = (argumentsArray.length > 2) ? itemsAfterDeleted = argumentsArray
+					.slice(2).concat(itemsAfterDeleted)
+					: itemsAfterDeleted;
+			for (i = 0, len = itemsToAppend.length; i < len; i++) {
+				this.push(itemsToAppend[i]);
+			}
+			return itemsDeleted;
+		};
+	}
+	var log4javascript = (function() {
+		function isUndefined(obj) {
+			return typeof obj == "undefined";
+		}
+		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;
+		function Log4JavaScript() {
+		}
+		Log4JavaScript.prototype = new EventSupport();
+		log4javascript = new Log4JavaScript();
+		log4javascript.version = "1.4.2";
+		log4javascript.edition = "log4javascript";
+		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);
+			}
+		}
+		function getUrlFileName(url) {
+			var lastSlashIndex = Math.max(url.lastIndexOf("/"), url
+					.lastIndexOf("\\"));
+			return url.substr(lastSlashIndex + 1);
+		}
+		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) {
+			var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+			return text2.split("\n");
+		}
+		var urlEncode = (typeof window.encodeURIComponent != "undefined") ? function(
+				str) {
+			return encodeURIComponent(str);
+		}
+				: function(str) {
+					return escape(str).replace(/\+/g, "%2B").replace(/"/g,
+							"%22").replace(/'/g, "%27").replace(/\//g, "%2F")
+							.replace(/=/g, "%3D");
+				};
+		var urlDecode = (typeof window.decodeURIComponent != "undefined") ? function(
+				str) {
+			return decodeURIComponent(str);
+		}
+				: function(str) {
+					return unescape(str).replace(/%2B/g, "+").replace(/%22/g,
+							"\"").replace(/%27/g, "'").replace(/%2F/g, "/")
+							.replace(/%3D/g, "=");
+				};
+		function array_remove(arr, val) {
+			var index = -1;
+			for ( var i = 0, len = arr.length; i < len; i++) {
+				if (arr[i] === val) {
+					index = i;
+					break;
+				}
+			}
+			if (index >= 0) {
+				arr.splice(index, 1);
+				return true;
+			} else {
+				return false;
+			}
+		}
+		function array_contains(arr, val) {
+			for ( var i = 0, len = arr.length; i < len; i++) {
+				if (arr[i] == val) {
+					return true;
+				}
+			}
+			return false;
+		}
+		function extractBooleanFromParam(param, defaultValue) {
+			if (isUndefined(param)) {
+				return defaultValue;
+			} else {
+				return bool(param);
+			}
+		}
+		function extractStringFromParam(param, defaultValue) {
+			if (isUndefined(param)) {
+				return defaultValue;
+			} else {
+				return String(param);
+			}
+		}
+		function extractIntFromParam(param, defaultValue) {
+			if (isUndefined(param)) {
+				return defaultValue;
+			} else {
+				try {
+					var value = parseInt(param, 10);
+					return isNaN(value) ? defaultValue : value;
+				} catch (ex) {
+					logLog.warn("Invalid int param " + param, ex);
+					return defaultValue;
+				}
+			}
+		}
+		function extractFunctionFromParam(param, defaultValue) {
+			if (typeof param == "function") {
+				return param;
+			} else {
+				return defaultValue;
+			}
+		}
+		function isError(err) {
+			return (err instanceof Error);
+		}
+		if (!Function.prototype.apply) {
+			Function.prototype.apply = function(obj, args) {
+				var methodName = "__apply__";
+				if (typeof obj[methodName] != "undefined") {
+					methodName += String(Math.random()).substr(2);
+				}
+				obj[methodName] = this;
+				var argsStrings = [];
+				for ( var i = 0, len = args.length; i < len; i++) {
+					argsStrings[i] = "args[" + i + "]";
+				}
+				var script = "obj." + methodName + "(" + argsStrings.join(",")
+						+ ")";
+				var returnValue = eval(script);
+				delete obj[methodName];
+				return returnValue;
+			};
+		}
+		if (!Function.prototype.call) {
+			Function.prototype.call = function(obj) {
+				var args = [];
+				for ( var i = 1, len = arguments.length; i < len; i++) {
+					args[i - 1] = arguments[i];
+				}
+				return this.apply(obj, args);
+			};
+		}
+		function getListenersPropertyName(eventName) {
+			return "__log4javascript_listeners__" + eventName;
+		}
+		function addEvent(node, eventName, listener, useCapture, win) {
+			win = win ? win : window;
+			if (node.addEventListener) {
+				node.addEventListener(eventName, listener, useCapture);
+			} else if (node.attachEvent) {
+				node.attachEvent("on" + eventName, listener);
+			} else {
+				var propertyName = getListenersPropertyName(eventName);
+				if (!node[propertyName]) {
+					node[propertyName] = [];
+					node["on" + eventName] = function(evt) {
+						evt = getEvent(evt, win);
+						var listenersPropertyName = getListenersPropertyName(eventName);
+						var listeners = this[listenersPropertyName].concat([]);
+						var currentListener;
+						while ((currentListener = listeners.shift())) {
+							currentListener.call(this, evt);
+						}
+					};
+				}
+				node[propertyName].push(listener);
+			}
+		}
+		function removeEvent(node, eventName, listener, useCapture) {
+			if (node.removeEventListener) {
+				node.removeEventListener(eventName, listener, useCapture);
+			} else if (node.detachEvent) {
+				node.detachEvent("on" + eventName, listener);
+			} else {
+				var propertyName = getListenersPropertyName(eventName);
+				if (node[propertyName]) {
+					array_remove(node[propertyName], listener);
+				}
+			}
+		}
+		function getEvent(evt, win) {
+			win = win ? win : window;
+			return evt ? evt : win.event;
+		}
+		function stopEventPropagation(evt) {
+			if (evt.stopPropagation) {
+				evt.stopPropagation();
+			} else if (typeof evt.cancelBubble != "undefined") {
+				evt.cancelBubble = true;
+			}
+			evt.returnValue = false;
+		}
+		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;
+		};
+		log4javascript.evalInScope = function(expr) {
+			return eval(expr);
+		};
+		var showStackTraces = false;
+		log4javascript.setShowStackTraces = function(show) {
+			showStackTraces = bool(show);
+		};
+		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;
+		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();
+		};
+		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();
+			};
+			var additive = true;
+			this.getAdditivity = function() {
+				return additive;
+			};
+			this.setAdditivity = function(additivity) {
+				var valueChanged = (additive != additivity);
+				additive = additivity;
+				if (valueChanged) {
+					this.invalidateAppenderCache();
+				}
+			};
+			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) {
+					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())) {
+					var exception;
+					var finalParamIndex = params.length - 1;
+					var lastParam = params[finalParamIndex];
+					if (params.length > 1 && isError(lastParam)) {
+						exception = lastParam;
+						finalParamIndex--;
+					}
+					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) {
+				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(name) {
+				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;
+		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) {
+			if (!(typeof loggerName == "string")) {
+				loggerName = anonymousLoggerName;
+				logLog.warn("log4javascript.getLogger: non-string logger name "
+						+ toStr(loggerName)
+						+ " supplied, returning anonymous logger");
+			}
+			if (loggerName == rootLoggerName) {
+				handleError("log4javascript.getLogger: root logger may not be obtained by name");
+			}
+			if (!loggers[loggerName]) {
+				var logger = new Logger(loggerName);
+				loggers[loggerName] = logger;
+				loggerNames.push(loggerName);
+				var lastDotIndex = loggerName.lastIndexOf(".");
+				var parentLogger;
+				if (lastDotIndex > -1) {
+					var parentLoggerName = loggerName
+							.substring(0, lastDotIndex);
+					parentLogger = log4javascript.getLogger(parentLoggerName);
+				} else {
+					parentLogger = rootLogger;
+				}
+				parentLogger.addChild(logger);
+			}
+			return loggers[loggerName];
+		};
+		var defaultLogger = null;
+		log4javascript.getDefaultLogger = function() {
+			if (!defaultLogger) {
+				defaultLogger = log4javascript.getLogger(defaultLoggerName);
+				var a = new log4javascript.PopUpAppender();
+				defaultLogger.addAppender(a);
+			}
+			return defaultLogger;
+		};
+		var nullLogger = null;
+		log4javascript.getNullLogger = function() {
+			if (!nullLogger) {
+				nullLogger = new Logger(nullLoggerName);
+				nullLogger.setLevel(Level.OFF);
+			}
+			return nullLogger;
+		};
+		log4javascript.resetConfiguration = function() {
+			rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
+			loggers = {};
+		};
+		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;
+		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;
+						if (typeof val === "function") {
+							val = val(this, loggingEvent);
+						}
+						dataValues.push([ this.customFields[i].name, val ]);
+					}
+				}
+				return dataValues;
+			},
+			setKeys : function(loggerKey, timeStampKey, levelKey, messageKey,
+					exceptionKey, urlKey, millisecondsKey) {
+				this.loggerKey = extractStringFromParam(loggerKey,
+						this.defaults.loggerKey);
+				this.timeStampKey = extractStringFromParam(timeStampKey,
+						this.defaults.timeStampKey);
+				this.levelKey = extractStringFromParam(levelKey,
+						this.defaults.levelKey);
+				this.messageKey = extractStringFromParam(messageKey,
+						this.defaults.messageKey);
+				this.exceptionKey = extractStringFromParam(exceptionKey,
+						this.defaults.exceptionKey);
+				this.urlKey = extractStringFromParam(urlKey,
+						this.defaults.urlKey);
+				this.millisecondsKey = extractStringFromParam(millisecondsKey,
+						this.defaults.millisecondsKey);
+			},
+			setCustomField : function(name, value) {
+				var fieldUpdated = false;
+				for ( var i = 0, len = this.customFields.length; i < len; i++) {
+					if (this.customFields[i].name === name) {
+						this.customFields[i].value = value;
+						fieldUpdated = true;
+					}
+				}
+				if (!fieldUpdated) {
+					this.customFields.push({
+						"name" : name,
+						"value" : value
+					});
+				}
+			},
+			hasCustomFields : function() {
+				return (this.customFields.length > 0);
+			},
+			toString : function() {
+				handleError("Layout.toString: all layouts must override this method");
+			}
+		};
+		log4javascript.Layout = Layout;
+		var Appender = function() {
+		};
+		Appender.prototype = new EventSupport();
+		Appender.prototype.layout = new PatternLayout();
+		Appender.prototype.threshold = Level.ALL;
+		Appender.prototype.loggers = [];
+		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;
+		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;
+		function NullLayout() {
+			this.customFields = [];
+		}
+		NullLayout.prototype = new Layout();
+		NullLayout.prototype.format = function(loggingEvent) {
+			return loggingEvent.messages;
+		};
+		NullLayout.prototype.ignoresThrowable = function() {
+			return true;
+		};
+		NullLayout.prototype.toString = function() {
+			return "NullLayout";
+		};
+		log4javascript.NullLayout = NullLayout;
+		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(/\]\]>/, "]]>]]&gt;<![CDATA[");
+		};
+		XmlLayout.prototype.format = function(loggingEvent) {
+			var layout = this;
+			var i, len;
+			function formatMessage(message) {
+				message = (typeof message === "string") ? message
+						: toStr(message);
+				return "<log4javascript:message><![CDATA["
+						+ layout.escapeCdata(message)
+						+ "]]></log4javascript:message>";
+			}
+			var str = "<log4javascript:event logger=\""
+					+ loggingEvent.logger.name + "\" timestamp=\""
+					+ this.getTimeStampValue(loggingEvent) + "\"";
+			if (!this.isTimeStampsInMilliseconds()) {
+				str += " milliseconds=\"" + loggingEvent.milliseconds + "\"";
+			}
+			str += " level=\"" + loggingEvent.level.name + "\">" + newLine;
+			if (this.combineMessages) {
+				str += formatMessage(loggingEvent.getCombinedMessages());
+			} else {
+				str += "<log4javascript:messages>" + newLine;
+				for (i = 0, len = loggingEvent.messages.length; i < len; i++) {
+					str += formatMessage(loggingEvent.messages[i]) + newLine;
+				}
+				str += "</log4javascript:messages>" + newLine;
+			}
+			if (this.hasCustomFields()) {
+				for (i = 0, len = this.customFields.length; i < len; i++) {
+					str += "<log4javascript:customfield name=\""
+							+ this.customFields[i].name + "\"><![CDATA["
+							+ this.customFields[i].value.toString()
+							+ "]]></log4javascript:customfield>" + newLine;
+				}
+			}
+			if (loggingEvent.exception) {
+				str += "<log4javascript:exception><![CDATA["
+						+ getExceptionStringRep(loggingEvent.exception)
+						+ "]]></log4javascript:exception>" + newLine;
+			}
+			str += "</log4javascript:event>" + newLine + newLine;
+			return str;
+		};
+		XmlLayout.prototype.ignoresThrowable = function() {
+			return false;
+		};
+		XmlLayout.prototype.toString = function() {
+			return "XmlLayout";
+		};
+		log4javascript.XmlLayout = XmlLayout;
+		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.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) {
+				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;
+		function HttpPostDataLayout() {
+			this.setKeys();
+			this.customFields = [];
+			this.returnsPostData = true;
+		}
+		HttpPostDataLayout.prototype = new Layout();
+		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;
+		function formatObjectExpansion(obj, depth, indentation) {
+			var objectsExpanded = [];
+			function doFormat(obj, depth, indentation) {
+				var i, j, len, childDepth, childIndentation, childLines, expansion, childExpansion;
+				if (!indentation) {
+					indentation = "";
+				}
+				function formatString(text) {
+					var lines = splitIntoLines(text);
+					for ( var j = 1, jLen = lines.length; j < jLen; j++) {
+						lines[j] = indentation + lines[j];
+					}
+					return lines.join(newLine);
+				}
+				if (obj === null) {
+					return "null";
+				} else if (typeof obj == "undefined") {
+					return "undefined";
+				} else if (typeof obj == "string") {
+					return formatString(obj);
+				} else if (typeof obj == "object"
+						&& array_contains(objectsExpanded, obj)) {
+					try {
+						expansion = toStr(obj);
+					} catch (ex) {
+						expansion = "Error formatting property. Details: "
+								+ getExceptionStringRep(ex);
+					}
+					return expansion + " [already expanded]";
+				} else if ((obj instanceof Array) && depth > 0) {
+					objectsExpanded.push(obj);
+					expansion = "[" + newLine;
+					childDepth = depth - 1;
+					childIndentation = indentation + "  ";
+					childLines = [];
+					for (i = 0, len = obj.length; i < len; i++) {
+						try {
+							childExpansion = doFormat(obj[i], childDepth,
+									childIndentation);
+							childLines.push(childIndentation + childExpansion);
+						} catch (ex) {
+							childLines
+									.push(childIndentation
+											+ "Error formatting array member. Details: "
+											+ getExceptionStringRep(ex) + "");
+						}
+					}
+					expansion += childLines.join("," + newLine) + newLine
+							+ indentation + "]";
+					return expansion;
+				} else if (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);
+		}
+		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() {
+				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;
+			};
+			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;
+				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 (quotedString) {
+						if (quotedString == "''") {
+							formattedString += "'";
+						} else {
+							formattedString += quotedString.substring(1,
+									quotedString.length - 1);
+						}
+					} else if (otherLetters) {
+					} else if (otherCharacters) {
+						formattedString += otherCharacters;
+					} else if (patternLetters) {
+						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();
+							break;
+						}
+						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) {
+								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 {
+								formattedString += formatNumber(rawData + 1,
+										numberOfLetters);
+							}
+							break;
+						case TIMEZONE:
+							var isPositive = (rawData > 0);
+							var prefix = isPositive ? "-" : "+";
+							var absData = Math.abs(rawData);
+							var hours = "" + Math.floor(absData / 60);
+							hours = padWithZeroes(hours, 2);
+							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;
+		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;
+			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];
+				if (text) {
+					formattedString += "" + text;
+				} else {
+					var replacement = "";
+					switch (conversionCharacter) {
+					case "a":
+					case "m":
+						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":
+						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":
+						var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
+						if (specifier) {
+							dateFormat = specifier;
+							if (dateFormat == "ISO8601") {
+								dateFormat = PatternLayout.ISO8601_DATEFORMAT;
+							} else if (dateFormat == "ABSOLUTE") {
+								dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
+							} else if (dateFormat == "DATE") {
+								dateFormat = PatternLayout.DATETIME_DATEFORMAT;
+							}
+						}
+						replacement = (new SimpleDateFormat(dateFormat))
+								.format(loggingEvent.timeStamp);
+						break;
+					case "f":
+						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;
+								}
+							}
+							replacement = this.customFields[fieldIndex].value;
+						}
+						break;
+					case "n":
+						replacement = newLine;
+						break;
+					case "p":
+						replacement = loggingEvent.level.name;
+						break;
+					case "r":
+						replacement = ""
+								+ loggingEvent.timeStamp
+										.getDifference(applicationStartDate);
+						break;
+					case "%":
+						replacement = "%";
+						break;
+					default:
+						replacement = matchedString;
+						break;
+					}
+					var l;
+					if (truncation) {
+						l = parseInt(truncation.substr(1), 10);
+						var strLen = replacement.length;
+						if (l < strLen) {
+							replacement = replacement.substring(strLen - l,
+									strLen);
+						}
+					}
+					if (padding) {
+						if (padding.charAt(0) == "-") {
+							l = parseInt(padding.substr(1), 10);
+							while (replacement.length < l) {
+								replacement += " ";
+							}
+						} else {
+							l = parseInt(padding, 10);
+							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;
+		function AlertAppender() {
+		}
+		AlertAppender.prototype = new Appender();
+		AlertAppender.prototype.layout = new SimpleLayout();
+		AlertAppender.prototype.append = function(loggingEvent) {
+			var formattedMessage = this.getLayout().format(loggingEvent);
+			if (this.getLayout().ignoresThrowable()) {
+				formattedMessage += loggingEvent.getThrowableStrRep();
+			}
+			alert(formattedMessage);
+		};
+		AlertAppender.prototype.toString = function() {
+			return "AlertAppender";
+		};
+		log4javascript.AlertAppender = AlertAppender;
+		function BrowserConsoleAppender() {
+		}
+		BrowserConsoleAppender.prototype = new log4javascript.Appender();
+		BrowserConsoleAppender.prototype.layout = new NullLayout();
+		BrowserConsoleAppender.prototype.threshold = Level.DEBUG;
+		BrowserConsoleAppender.prototype.append = function(loggingEvent) {
+			var appender = this;
+			var getFormattedMessage = function() {
+				var layout = appender.getLayout();
+				var formattedMessage = layout.format(loggingEvent);
+				if (layout.ignoresThrowable() && loggingEvent.exception) {
+					formattedMessage += loggingEvent.getThrowableStrRep();
+				}
+				return formattedMessage;
+			};
+			if ((typeof opera != "undefined") && opera.postError) {