2 * Copyright 2013 Tim Down.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
20 * log4javascript is a logging framework for JavaScript based on log4j
\r
21 * for Java. This file contains all core log4javascript code and is the only
\r
22 * file required to use log4javascript, unless you require support for
\r
23 * document.domain, in which case you will also need console.html, which must be
\r
24 * stored in the same directory as the main log4javascript.js file.
\r
26 * Author: Tim Down <tim@log4javascript.org>
\r
28 * Edition: log4javascript
\r
29 * Build date: 19 March 2013
\r
30 * Website: http://log4javascript.org
\r
33 /* -------------------------------------------------------------------------- */
\r
34 // Array-related stuff
\r
36 // Next three methods are solely for IE5, which is missing them
\r
37 if (!Array.prototype.push) {
\r
38 Array.prototype.push = function() {
\r
39 for (var i = 0, len = arguments.length; i < len; i++){
\r
40 this[this.length] = arguments[i];
\r
46 if (!Array.prototype.shift) {
\r
47 Array.prototype.shift = function() {
\r
48 if (this.length > 0) {
\r
49 var firstItem = this[0];
\r
50 for (var i = 0, len = this.length - 1; i < len; i++) {
\r
51 this[i] = this[i + 1];
\r
53 this.length = this.length - 1;
\r
59 if (!Array.prototype.splice) {
\r
60 Array.prototype.splice = function(startIndex, deleteCount) {
\r
61 var itemsAfterDeleted = this.slice(startIndex + deleteCount);
\r
62 var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);
\r
63 this.length = startIndex;
\r
64 // Copy the arguments into a proper Array object
\r
65 var argumentsArray = [];
\r
66 for (var i = 0, len = arguments.length; i < len; i++) {
\r
67 argumentsArray[i] = arguments[i];
\r
69 var itemsToAppend = (argumentsArray.length > 2) ?
\r
70 itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;
\r
71 for (i = 0, len = itemsToAppend.length; i < len; i++) {
\r
72 this.push(itemsToAppend[i]);
\r
74 return itemsDeleted;
\r
78 /* -------------------------------------------------------------------------- */
\r
80 var log4javascript = (function() {
\r
82 function isUndefined(obj) {
\r
83 return typeof obj == "undefined";
\r
86 /* ---------------------------------------------------------------------- */
\r
87 // Custom event support
\r
89 function EventSupport() {}
\r
91 EventSupport.prototype = {
\r
94 setEventTypes: function(eventTypesParam) {
\r
95 if (eventTypesParam instanceof Array) {
\r
96 this.eventTypes = eventTypesParam;
\r
97 this.eventListeners = {};
\r
98 for (var i = 0, len = this.eventTypes.length; i < len; i++) {
\r
99 this.eventListeners[this.eventTypes[i]] = [];
\r
102 handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array");
\r
106 addEventListener: function(eventType, listener) {
\r
107 if (typeof listener == "function") {
\r
108 if (!array_contains(this.eventTypes, eventType)) {
\r
109 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'");
\r
111 this.eventListeners[eventType].push(listener);
\r
113 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function");
\r
117 removeEventListener: function(eventType, listener) {
\r
118 if (typeof listener == "function") {
\r
119 if (!array_contains(this.eventTypes, eventType)) {
\r
120 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'");
\r
122 array_remove(this.eventListeners[eventType], listener);
\r
124 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function");
\r
128 dispatchEvent: function(eventType, eventArgs) {
\r
129 if (array_contains(this.eventTypes, eventType)) {
\r
130 var listeners = this.eventListeners[eventType];
\r
131 for (var i = 0, len = listeners.length; i < len; i++) {
\r
132 listeners[i](this, eventType, eventArgs);
\r
135 handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'");
\r
140 /* -------------------------------------------------------------------------- */
\r
142 var applicationStartDate = new Date();
\r
143 var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" +
\r
144 Math.floor(Math.random() * 100000000);
\r
145 var emptyFunction = function() {};
\r
146 var newLine = "\r\n";
\r
147 var pageLoaded = false;
\r
149 // Create main log4javascript object; this will be assigned public properties
\r
150 function Log4JavaScript() {}
\r
151 Log4JavaScript.prototype = new EventSupport();
\r
153 log4javascript = new Log4JavaScript();
\r
154 log4javascript.version = "1.4.6";
\r
155 log4javascript.edition = "log4javascript";
\r
157 /* -------------------------------------------------------------------------- */
\r
158 // Utility functions
\r
160 function toStr(obj) {
\r
161 if (obj && obj.toString) {
\r
162 return obj.toString();
\r
164 return String(obj);
\r
168 function getExceptionMessage(ex) {
\r
171 } else if (ex.description) {
\r
172 return ex.description;
\r
178 // Gets the portion of the URL after the last slash
\r
179 function getUrlFileName(url) {
\r
180 var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
\r
181 return url.substr(lastSlashIndex + 1);
\r
184 // Returns a nicely formatted representation of an error
\r
185 function getExceptionStringRep(ex) {
\r
187 var exStr = "Exception: " + getExceptionMessage(ex);
\r
189 if (ex.lineNumber) {
\r
190 exStr += " on line number " + ex.lineNumber;
\r
193 exStr += " in file " + getUrlFileName(ex.fileName);
\r
195 } catch (localEx) {
\r
196 logLog.warn("Unable to obtain file and line information for error");
\r
198 if (showStackTraces && ex.stack) {
\r
199 exStr += newLine + "Stack trace:" + newLine + ex.stack;
\r
206 function bool(obj) {
\r
207 return Boolean(obj);
\r
210 function trim(str) {
\r
211 return str.replace(/^\s+/, "").replace(/\s+$/, "");
\r
214 function splitIntoLines(text) {
\r
215 // Ensure all line breaks are \n only
\r
216 var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
\r
217 return text2.split("\n");
\r
220 var urlEncode = (typeof window.encodeURIComponent != "undefined") ?
\r
222 return encodeURIComponent(str);
\r
225 return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D");
\r
228 var urlDecode = (typeof window.decodeURIComponent != "undefined") ?
\r
230 return decodeURIComponent(str);
\r
233 return unescape(str).replace(/%2B/g, "+").replace(/%22/g, "\"").replace(/%27/g, "'").replace(/%2F/g, "/").replace(/%3D/g, "=");
\r
236 function array_remove(arr, val) {
\r
238 for (var i = 0, len = arr.length; i < len; i++) {
\r
239 if (arr[i] === val) {
\r
245 arr.splice(index, 1);
\r
252 function array_contains(arr, val) {
\r
253 for(var i = 0, len = arr.length; i < len; i++) {
\r
254 if (arr[i] == val) {
\r
261 function extractBooleanFromParam(param, defaultValue) {
\r
262 if (isUndefined(param)) {
\r
263 return defaultValue;
\r
265 return bool(param);
\r
269 function extractStringFromParam(param, defaultValue) {
\r
270 if (isUndefined(param)) {
\r
271 return defaultValue;
\r
273 return String(param);
\r
277 function extractIntFromParam(param, defaultValue) {
\r
278 if (isUndefined(param)) {
\r
279 return defaultValue;
\r
282 var value = parseInt(param, 10);
\r
283 return isNaN(value) ? defaultValue : value;
\r
285 logLog.warn("Invalid int param " + param, ex);
\r
286 return defaultValue;
\r
291 function extractFunctionFromParam(param, defaultValue) {
\r
292 if (typeof param == "function") {
\r
295 return defaultValue;
\r
299 function isError(err) {
\r
300 return (err instanceof Error);
\r
303 if (!Function.prototype.apply){
\r
304 Function.prototype.apply = function(obj, args) {
\r
305 var methodName = "__apply__";
\r
306 if (typeof obj[methodName] != "undefined") {
\r
307 methodName += String(Math.random()).substr(2);
\r
309 obj[methodName] = this;
\r
311 var argsStrings = [];
\r
312 for (var i = 0, len = args.length; i < len; i++) {
\r
313 argsStrings[i] = "args[" + i + "]";
\r
315 var script = "obj." + methodName + "(" + argsStrings.join(",") + ")";
\r
316 var returnValue = eval(script);
\r
317 delete obj[methodName];
\r
318 return returnValue;
\r
322 if (!Function.prototype.call){
\r
323 Function.prototype.call = function(obj) {
\r
325 for (var i = 1, len = arguments.length; i < len; i++) {
\r
326 args[i - 1] = arguments[i];
\r
328 return this.apply(obj, args);
\r
332 function getListenersPropertyName(eventName) {
\r
333 return "__log4javascript_listeners__" + eventName;
\r
336 function addEvent(node, eventName, listener, useCapture, win) {
\r
337 win = win ? win : window;
\r
338 if (node.addEventListener) {
\r
339 node.addEventListener(eventName, listener, useCapture);
\r
340 } else if (node.attachEvent) {
\r
341 node.attachEvent("on" + eventName, listener);
\r
343 var propertyName = getListenersPropertyName(eventName);
\r
344 if (!node[propertyName]) {
\r
345 node[propertyName] = [];
\r
346 // Set event handler
\r
347 node["on" + eventName] = function(evt) {
\r
348 evt = getEvent(evt, win);
\r
349 var listenersPropertyName = getListenersPropertyName(eventName);
\r
351 // Clone the array of listeners to leave the original untouched
\r
352 var listeners = this[listenersPropertyName].concat([]);
\r
353 var currentListener;
\r
355 // Call each listener in turn
\r
356 while ((currentListener = listeners.shift())) {
\r
357 currentListener.call(this, evt);
\r
361 node[propertyName].push(listener);
\r
365 function removeEvent(node, eventName, listener, useCapture) {
\r
366 if (node.removeEventListener) {
\r
367 node.removeEventListener(eventName, listener, useCapture);
\r
368 } else if (node.detachEvent) {
\r
369 node.detachEvent("on" + eventName, listener);
\r
371 var propertyName = getListenersPropertyName(eventName);
\r
372 if (node[propertyName]) {
\r
373 array_remove(node[propertyName], listener);
\r
378 function getEvent(evt, win) {
\r
379 win = win ? win : window;
\r
380 return evt ? evt : win.event;
\r
383 function stopEventPropagation(evt) {
\r
384 if (evt.stopPropagation) {
\r
385 evt.stopPropagation();
\r
386 } else if (typeof evt.cancelBubble != "undefined") {
\r
387 evt.cancelBubble = true;
\r
389 evt.returnValue = false;
\r
392 /* ---------------------------------------------------------------------- */
\r
393 // Simple logging for log4javascript itself
\r
400 setQuietMode: function(quietMode) {
\r
401 this.quietMode = bool(quietMode);
\r
406 alertAllErrors: false,
\r
408 setAlertAllErrors: function(alertAllErrors) {
\r
409 this.alertAllErrors = alertAllErrors;
\r
412 debug: function(message) {
\r
413 this.debugMessages.push(message);
\r
416 displayDebug: function() {
\r
417 alert(this.debugMessages.join(newLine));
\r
420 warn: function(message, exception) {
\r
423 error: function(message, exception) {
\r
424 if (++this.numberOfErrors == 1 || this.alertAllErrors) {
\r
425 if (!this.quietMode) {
\r
426 var alertMessage = "log4javascript error: " + message;
\r
428 alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception);
\r
430 alert(alertMessage);
\r
435 log4javascript.logLog = logLog;
\r
437 log4javascript.setEventTypes(["load", "error"]);
\r
439 function handleError(message, exception) {
\r
440 logLog.error(message, exception);
\r
441 log4javascript.dispatchEvent("error", { "message": message, "exception": exception });
\r
444 log4javascript.handleError = handleError;
\r
446 /* ---------------------------------------------------------------------- */
\r
448 var enabled = !((typeof log4javascript_disabled != "undefined") &&
\r
449 log4javascript_disabled);
\r
451 log4javascript.setEnabled = function(enable) {
\r
452 enabled = bool(enable);
\r
455 log4javascript.isEnabled = function() {
\r
459 var useTimeStampsInMilliseconds = true;
\r
461 log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) {
\r
462 useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
\r
465 log4javascript.isTimeStampsInMilliseconds = function() {
\r
466 return useTimeStampsInMilliseconds;
\r
470 // This evaluates the given expression in the current scope, thus allowing
\r
471 // scripts to access private variables. Particularly useful for testing
\r
472 log4javascript.evalInScope = function(expr) {
\r
476 var showStackTraces = false;
\r
478 log4javascript.setShowStackTraces = function(show) {
\r
479 showStackTraces = bool(show);
\r
482 /* ---------------------------------------------------------------------- */
\r
485 var Level = function(level, name) {
\r
486 this.level = level;
\r
490 Level.prototype = {
\r
491 toString: function() {
\r
494 equals: function(level) {
\r
495 return this.level == level.level;
\r
497 isGreaterOrEqual: function(level) {
\r
498 return this.level >= level.level;
\r
502 Level.ALL = new Level(Number.MIN_VALUE, "ALL");
\r
503 Level.TRACE = new Level(10000, "TRACE");
\r
504 Level.DEBUG = new Level(20000, "DEBUG");
\r
505 Level.INFO = new Level(30000, "INFO");
\r
506 Level.WARN = new Level(40000, "WARN");
\r
507 Level.ERROR = new Level(50000, "ERROR");
\r
508 Level.FATAL = new Level(60000, "FATAL");
\r
509 Level.OFF = new Level(Number.MAX_VALUE, "OFF");
\r
511 log4javascript.Level = Level;
\r
513 /* ---------------------------------------------------------------------- */
\r
516 function Timer(name, level) {
\r
518 this.level = isUndefined(level) ? Level.INFO : level;
\r
519 this.start = new Date();
\r
522 Timer.prototype.getElapsedTime = function() {
\r
523 return new Date().getTime() - this.start.getTime();
\r
526 /* ---------------------------------------------------------------------- */
\r
529 var anonymousLoggerName = "[anonymous]";
\r
530 var defaultLoggerName = "[default]";
\r
531 var nullLoggerName = "[null]";
\r
532 var rootLoggerName = "root";
\r
534 function Logger(name) {
\r
536 this.parent = null;
\r
537 this.children = [];
\r
539 var appenders = [];
\r
540 var loggerLevel = null;
\r
541 var isRoot = (this.name === rootLoggerName);
\r
542 var isNull = (this.name === nullLoggerName);
\r
544 var appenderCache = null;
\r
545 var appenderCacheInvalidated = false;
\r
547 this.addChild = function(childLogger) {
\r
548 this.children.push(childLogger);
\r
549 childLogger.parent = this;
\r
550 childLogger.invalidateAppenderCache();
\r
554 var additive = true;
\r
555 this.getAdditivity = function() {
\r
559 this.setAdditivity = function(additivity) {
\r
560 var valueChanged = (additive != additivity);
\r
561 additive = additivity;
\r
562 if (valueChanged) {
\r
563 this.invalidateAppenderCache();
\r
567 // Create methods that use the appenders variable in this scope
\r
568 this.addAppender = function(appender) {
\r
570 handleError("Logger.addAppender: you may not add an appender to the null logger");
\r
572 if (appender instanceof log4javascript.Appender) {
\r
573 if (!array_contains(appenders, appender)) {
\r
574 appenders.push(appender);
\r
575 appender.setAddedToLogger(this);
\r
576 this.invalidateAppenderCache();
\r
579 handleError("Logger.addAppender: appender supplied ('" +
\r
580 toStr(appender) + "') is not a subclass of Appender");
\r
585 this.removeAppender = function(appender) {
\r
586 array_remove(appenders, appender);
\r
587 appender.setRemovedFromLogger(this);
\r
588 this.invalidateAppenderCache();
\r
591 this.removeAllAppenders = function() {
\r
592 var appenderCount = appenders.length;
\r
593 if (appenderCount > 0) {
\r
594 for (var i = 0; i < appenderCount; i++) {
\r
595 appenders[i].setRemovedFromLogger(this);
\r
597 appenders.length = 0;
\r
598 this.invalidateAppenderCache();
\r
602 this.getEffectiveAppenders = function() {
\r
603 if (appenderCache === null || appenderCacheInvalidated) {
\r
604 // Build appender cache
\r
605 var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ?
\r
606 [] : this.parent.getEffectiveAppenders();
\r
607 appenderCache = parentEffectiveAppenders.concat(appenders);
\r
608 appenderCacheInvalidated = false;
\r
610 return appenderCache;
\r
613 this.invalidateAppenderCache = function() {
\r
614 appenderCacheInvalidated = true;
\r
615 for (var i = 0, len = this.children.length; i < len; i++) {
\r
616 this.children[i].invalidateAppenderCache();
\r
620 this.log = function(level, params) {
\r
621 if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) {
\r
622 // Check whether last param is an exception
\r
624 var finalParamIndex = params.length - 1;
\r
625 var lastParam = params[finalParamIndex];
\r
626 if (params.length > 1 && isError(lastParam)) {
\r
627 exception = lastParam;
\r
631 // Construct genuine array for the params
\r
633 for (var i = 0; i <= finalParamIndex; i++) {
\r
634 messages[i] = params[i];
\r
637 var loggingEvent = new LoggingEvent(
\r
638 this, new Date(), level, messages, exception);
\r
640 this.callAppenders(loggingEvent);
\r
644 this.callAppenders = function(loggingEvent) {
\r
645 var effectiveAppenders = this.getEffectiveAppenders();
\r
646 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
647 effectiveAppenders[i].doAppend(loggingEvent);
\r
651 this.setLevel = function(level) {
\r
652 // Having a level of null on the root logger would be very bad.
\r
653 if (isRoot && level === null) {
\r
654 handleError("Logger.setLevel: you cannot set the level of the root logger to null");
\r
655 } else if (level instanceof Level) {
\r
656 loggerLevel = level;
\r
658 handleError("Logger.setLevel: level supplied to logger " +
\r
659 this.name + " is not an instance of log4javascript.Level");
\r
663 this.getLevel = function() {
\r
664 return loggerLevel;
\r
667 this.getEffectiveLevel = function() {
\r
668 for (var logger = this; logger !== null; logger = logger.parent) {
\r
669 var level = logger.getLevel();
\r
670 if (level !== null) {
\r
676 this.group = function(name, initiallyExpanded) {
\r
678 var effectiveAppenders = this.getEffectiveAppenders();
\r
679 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
680 effectiveAppenders[i].group(name, initiallyExpanded);
\r
685 this.groupEnd = function() {
\r
687 var effectiveAppenders = this.getEffectiveAppenders();
\r
688 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
689 effectiveAppenders[i].groupEnd();
\r
696 this.time = function(name, level) {
\r
698 if (isUndefined(name)) {
\r
699 handleError("Logger.time: a name for the timer must be supplied");
\r
700 } else if (level && !(level instanceof Level)) {
\r
701 handleError("Logger.time: level supplied to timer " +
\r
702 name + " is not an instance of log4javascript.Level");
\r
704 timers[name] = new Timer(name, level);
\r
709 this.timeEnd = function(name) {
\r
711 if (isUndefined(name)) {
\r
712 handleError("Logger.timeEnd: a name for the timer must be supplied");
\r
713 } else if (timers[name]) {
\r
714 var timer = timers[name];
\r
715 var milliseconds = timer.getElapsedTime();
\r
716 this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]);
\r
717 delete timers[name];
\r
719 logLog.warn("Logger.timeEnd: no timer found with name " + name);
\r
724 this.assert = function(expr) {
\r
725 if (enabled && !expr) {
\r
727 for (var i = 1, len = arguments.length; i < len; i++) {
\r
728 args.push(arguments[i]);
\r
730 args = (args.length > 0) ? args : ["Assertion Failure"];
\r
731 args.push(newLine);
\r
733 this.log(Level.ERROR, args);
\r
737 this.toString = function() {
\r
738 return "Logger[" + this.name + "]";
\r
742 Logger.prototype = {
\r
743 trace: function() {
\r
744 this.log(Level.TRACE, arguments);
\r
747 debug: function() {
\r
748 this.log(Level.DEBUG, arguments);
\r
752 this.log(Level.INFO, arguments);
\r
756 this.log(Level.WARN, arguments);
\r
759 error: function() {
\r
760 this.log(Level.ERROR, arguments);
\r
763 fatal: function() {
\r
764 this.log(Level.FATAL, arguments);
\r
767 isEnabledFor: function(level) {
\r
768 return level.isGreaterOrEqual(this.getEffectiveLevel());
\r
771 isTraceEnabled: function() {
\r
772 return this.isEnabledFor(Level.TRACE);
\r
775 isDebugEnabled: function() {
\r
776 return this.isEnabledFor(Level.DEBUG);
\r
779 isInfoEnabled: function() {
\r
780 return this.isEnabledFor(Level.INFO);
\r
783 isWarnEnabled: function() {
\r
784 return this.isEnabledFor(Level.WARN);
\r
787 isErrorEnabled: function() {
\r
788 return this.isEnabledFor(Level.ERROR);
\r
791 isFatalEnabled: function() {
\r
792 return this.isEnabledFor(Level.FATAL);
\r
796 Logger.prototype.trace.isEntryPoint = true;
\r
797 Logger.prototype.debug.isEntryPoint = true;
\r
798 Logger.prototype.info.isEntryPoint = true;
\r
799 Logger.prototype.warn.isEntryPoint = true;
\r
800 Logger.prototype.error.isEntryPoint = true;
\r
801 Logger.prototype.fatal.isEntryPoint = true;
\r
803 /* ---------------------------------------------------------------------- */
\r
804 // Logger access methods
\r
806 // Hashtable of loggers keyed by logger name
\r
808 var loggerNames = [];
\r
810 var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG;
\r
811 var rootLogger = new Logger(rootLoggerName);
\r
812 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
\r
814 log4javascript.getRootLogger = function() {
\r
818 log4javascript.getLogger = function(loggerName) {
\r
819 // Use default logger if loggerName is not specified or invalid
\r
820 if (!(typeof loggerName == "string")) {
\r
821 loggerName = anonymousLoggerName;
\r
822 logLog.warn("log4javascript.getLogger: non-string logger name " +
\r
823 toStr(loggerName) + " supplied, returning anonymous logger");
\r
826 // Do not allow retrieval of the root logger by name
\r
827 if (loggerName == rootLoggerName) {
\r
828 handleError("log4javascript.getLogger: root logger may not be obtained by name");
\r
831 // Create the logger for this name if it doesn't already exist
\r
832 if (!loggers[loggerName]) {
\r
833 var logger = new Logger(loggerName);
\r
834 loggers[loggerName] = logger;
\r
835 loggerNames.push(loggerName);
\r
837 // Set up parent logger, if it doesn't exist
\r
838 var lastDotIndex = loggerName.lastIndexOf(".");
\r
840 if (lastDotIndex > -1) {
\r
841 var parentLoggerName = loggerName.substring(0, lastDotIndex);
\r
842 parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc.
\r
844 parentLogger = rootLogger;
\r
846 parentLogger.addChild(logger);
\r
848 return loggers[loggerName];
\r
851 var defaultLogger = null;
\r
852 log4javascript.getDefaultLogger = function() {
\r
853 if (!defaultLogger) {
\r
854 defaultLogger = log4javascript.getLogger(defaultLoggerName);
\r
855 var a = new log4javascript.PopUpAppender();
\r
856 defaultLogger.addAppender(a);
\r
858 return defaultLogger;
\r
861 var nullLogger = null;
\r
862 log4javascript.getNullLogger = function() {
\r
864 nullLogger = new Logger(nullLoggerName);
\r
865 nullLogger.setLevel(Level.OFF);
\r
870 // Destroys all loggers
\r
871 log4javascript.resetConfiguration = function() {
\r
872 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
\r
876 /* ---------------------------------------------------------------------- */
\r
879 var LoggingEvent = function(logger, timeStamp, level, messages,
\r
881 this.logger = logger;
\r
882 this.timeStamp = timeStamp;
\r
883 this.timeStampInMilliseconds = timeStamp.getTime();
\r
884 this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000);
\r
885 this.milliseconds = this.timeStamp.getMilliseconds();
\r
886 this.level = level;
\r
887 this.messages = messages;
\r
888 this.exception = exception;
\r
891 LoggingEvent.prototype = {
\r
892 getThrowableStrRep: function() {
\r
893 return this.exception ?
\r
894 getExceptionStringRep(this.exception) : "";
\r
896 getCombinedMessages: function() {
\r
897 return (this.messages.length == 1) ? this.messages[0] :
\r
898 this.messages.join(newLine);
\r
900 toString: function() {
\r
901 return "LoggingEvent[" + this.level + "]";
\r
905 log4javascript.LoggingEvent = LoggingEvent;
\r
907 /* ---------------------------------------------------------------------- */
\r
908 // Layout prototype
\r
910 var Layout = function() {
\r
913 Layout.prototype = {
\r
915 loggerKey: "logger",
\r
916 timeStampKey: "timestamp",
\r
917 millisecondsKey: "milliseconds",
\r
919 messageKey: "message",
\r
920 exceptionKey: "exception",
\r
923 loggerKey: "logger",
\r
924 timeStampKey: "timestamp",
\r
925 millisecondsKey: "milliseconds",
\r
927 messageKey: "message",
\r
928 exceptionKey: "exception",
\r
932 batchSeparator: "",
\r
933 returnsPostData: false,
\r
934 overrideTimeStampsSetting: false,
\r
935 useTimeStampsInMilliseconds: null,
\r
937 format: function() {
\r
938 handleError("Layout.format: layout supplied has no format() method");
\r
941 ignoresThrowable: function() {
\r
942 handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");
\r
945 getContentType: function() {
\r
946 return "text/plain";
\r
949 allowBatching: function() {
\r
953 setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) {
\r
954 this.overrideTimeStampsSetting = true;
\r
955 this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
\r
958 isTimeStampsInMilliseconds: function() {
\r
959 return this.overrideTimeStampsSetting ?
\r
960 this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds;
\r
963 getTimeStampValue: function(loggingEvent) {
\r
964 return this.isTimeStampsInMilliseconds() ?
\r
965 loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds;
\r
968 getDataValues: function(loggingEvent, combineMessages) {
\r
970 [this.loggerKey, loggingEvent.logger.name],
\r
971 [this.timeStampKey, this.getTimeStampValue(loggingEvent)],
\r
972 [this.levelKey, loggingEvent.level.name],
\r
973 [this.urlKey, window.location.href],
\r
974 [this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages]
\r
976 if (!this.isTimeStampsInMilliseconds()) {
\r
977 dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]);
\r
979 if (loggingEvent.exception) {
\r
980 dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]);
\r
982 if (this.hasCustomFields()) {
\r
983 for (var i = 0, len = this.customFields.length; i < len; i++) {
\r
984 var val = this.customFields[i].value;
\r
986 // Check if the value is a function. If so, execute it, passing it the
\r
987 // current layout and the logging event
\r
988 if (typeof val === "function") {
\r
989 val = val(this, loggingEvent);
\r
991 dataValues.push([this.customFields[i].name, val]);
\r
997 setKeys: function(loggerKey, timeStampKey, levelKey, messageKey,
\r
998 exceptionKey, urlKey, millisecondsKey) {
\r
999 this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey);
\r
1000 this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey);
\r
1001 this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey);
\r
1002 this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey);
\r
1003 this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey);
\r
1004 this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey);
\r
1005 this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey);
\r
1008 setCustomField: function(name, value) {
\r
1009 var fieldUpdated = false;
\r
1010 for (var i = 0, len = this.customFields.length; i < len; i++) {
\r
1011 if (this.customFields[i].name === name) {
\r
1012 this.customFields[i].value = value;
\r
1013 fieldUpdated = true;
\r
1016 if (!fieldUpdated) {
\r
1017 this.customFields.push({"name": name, "value": value});
\r
1021 hasCustomFields: function() {
\r
1022 return (this.customFields.length > 0);
\r
1025 toString: function() {
\r
1026 handleError("Layout.toString: all layouts must override this method");
\r
1030 log4javascript.Layout = Layout;
\r
1032 /* ---------------------------------------------------------------------- */
\r
1033 // Appender prototype
\r
1035 var Appender = function() {};
\r
1037 Appender.prototype = new EventSupport();
\r
1039 Appender.prototype.layout = new PatternLayout();
\r
1040 Appender.prototype.threshold = Level.ALL;
\r
1041 Appender.prototype.loggers = [];
\r
1043 // Performs threshold checks before delegating actual logging to the
\r
1044 // subclass's specific append method.
\r
1045 Appender.prototype.doAppend = function(loggingEvent) {
\r
1046 if (enabled && loggingEvent.level.level >= this.threshold.level) {
\r
1047 this.append(loggingEvent);
\r
1051 Appender.prototype.append = function(loggingEvent) {};
\r
1053 Appender.prototype.setLayout = function(layout) {
\r
1054 if (layout instanceof Layout) {
\r
1055 this.layout = layout;
\r
1057 handleError("Appender.setLayout: layout supplied to " +
\r
1058 this.toString() + " is not a subclass of Layout");
\r
1062 Appender.prototype.getLayout = function() {
\r
1063 return this.layout;
\r
1066 Appender.prototype.setThreshold = function(threshold) {
\r
1067 if (threshold instanceof Level) {
\r
1068 this.threshold = threshold;
\r
1070 handleError("Appender.setThreshold: threshold supplied to " +
\r
1071 this.toString() + " is not a subclass of Level");
\r
1075 Appender.prototype.getThreshold = function() {
\r
1076 return this.threshold;
\r
1079 Appender.prototype.setAddedToLogger = function(logger) {
\r
1080 this.loggers.push(logger);
\r
1083 Appender.prototype.setRemovedFromLogger = function(logger) {
\r
1084 array_remove(this.loggers, logger);
\r
1087 Appender.prototype.group = emptyFunction;
\r
1088 Appender.prototype.groupEnd = emptyFunction;
\r
1090 Appender.prototype.toString = function() {
\r
1091 handleError("Appender.toString: all appenders must override this method");
\r
1094 log4javascript.Appender = Appender;
\r
1096 /* ---------------------------------------------------------------------- */
\r
1099 function SimpleLayout() {
\r
1100 this.customFields = [];
\r
1103 SimpleLayout.prototype = new Layout();
\r
1105 SimpleLayout.prototype.format = function(loggingEvent) {
\r
1106 return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages();
\r
1109 SimpleLayout.prototype.ignoresThrowable = function() {
\r
1113 SimpleLayout.prototype.toString = function() {
\r
1114 return "SimpleLayout";
\r
1117 log4javascript.SimpleLayout = SimpleLayout;
\r
1118 /* ----------------------------------------------------------------------- */
\r
1121 function NullLayout() {
\r
1122 this.customFields = [];
\r
1125 NullLayout.prototype = new Layout();
\r
1127 NullLayout.prototype.format = function(loggingEvent) {
\r
1128 return loggingEvent.messages;
\r
1131 NullLayout.prototype.ignoresThrowable = function() {
\r
1135 NullLayout.prototype.toString = function() {
\r
1136 return "NullLayout";
\r
1139 log4javascript.NullLayout = NullLayout;
\r
1140 /* ---------------------------------------------------------------------- */
\r
1143 function XmlLayout(combineMessages) {
\r
1144 this.combineMessages = extractBooleanFromParam(combineMessages, true);
\r
1145 this.customFields = [];
\r
1148 XmlLayout.prototype = new Layout();
\r
1150 XmlLayout.prototype.isCombinedMessages = function() {
\r
1151 return this.combineMessages;
\r
1154 XmlLayout.prototype.getContentType = function() {
\r
1155 return "text/xml";
\r
1158 XmlLayout.prototype.escapeCdata = function(str) {
\r
1159 return str.replace(/\]\]>/, "]]>]]><![CDATA[");
\r
1162 XmlLayout.prototype.format = function(loggingEvent) {
\r
1163 var layout = this;
\r
1165 function formatMessage(message) {
\r
1166 message = (typeof message === "string") ? message : toStr(message);
\r
1167 return "<log4javascript:message><![CDATA[" +
\r
1168 layout.escapeCdata(message) + "]]></log4javascript:message>";
\r
1171 var str = "<log4javascript:event logger=\"" + loggingEvent.logger.name +
\r
1172 "\" timestamp=\"" + this.getTimeStampValue(loggingEvent) + "\"";
\r
1173 if (!this.isTimeStampsInMilliseconds()) {
\r
1174 str += " milliseconds=\"" + loggingEvent.milliseconds + "\"";
\r
1176 str += " level=\"" + loggingEvent.level.name + "\">" + newLine;
\r
1177 if (this.combineMessages) {
\r
1178 str += formatMessage(loggingEvent.getCombinedMessages());
\r
1180 str += "<log4javascript:messages>" + newLine;
\r
1181 for (i = 0, len = loggingEvent.messages.length; i < len; i++) {
\r
1182 str += formatMessage(loggingEvent.messages[i]) + newLine;
\r
1184 str += "</log4javascript:messages>" + newLine;
\r
1186 if (this.hasCustomFields()) {
\r
1187 for (i = 0, len = this.customFields.length; i < len; i++) {
\r
1188 str += "<log4javascript:customfield name=\"" +
\r
1189 this.customFields[i].name + "\"><![CDATA[" +
\r
1190 this.customFields[i].value.toString() +
\r
1191 "]]></log4javascript:customfield>" + newLine;
\r
1194 if (loggingEvent.exception) {
\r
1195 str += "<log4javascript:exception><![CDATA[" +
\r
1196 getExceptionStringRep(loggingEvent.exception) +
\r
1197 "]]></log4javascript:exception>" + newLine;
\r
1199 str += "</log4javascript:event>" + newLine + newLine;
\r
1203 XmlLayout.prototype.ignoresThrowable = function() {
\r
1207 XmlLayout.prototype.toString = function() {
\r
1208 return "XmlLayout";
\r
1211 log4javascript.XmlLayout = XmlLayout;
\r
1212 /* ---------------------------------------------------------------------- */
\r
1213 // JsonLayout related
\r
1215 function escapeNewLines(str) {
\r
1216 return str.replace(/\r\n|\r|\n/g, "\\r\\n");
\r
1219 function JsonLayout(readable, combineMessages) {
\r
1220 this.readable = extractBooleanFromParam(readable, false);
\r
1221 this.combineMessages = extractBooleanFromParam(combineMessages, true);
\r
1222 this.batchHeader = this.readable ? "[" + newLine : "[";
\r
1223 this.batchFooter = this.readable ? "]" + newLine : "]";
\r
1224 this.batchSeparator = this.readable ? "," + newLine : ",";
\r
1226 this.colon = this.readable ? ": " : ":";
\r
1227 this.tab = this.readable ? "\t" : "";
\r
1228 this.lineBreak = this.readable ? newLine : "";
\r
1229 this.customFields = [];
\r
1232 /* ---------------------------------------------------------------------- */
\r
1235 JsonLayout.prototype = new Layout();
\r
1237 JsonLayout.prototype.isReadable = function() {
\r
1238 return this.readable;
\r
1241 JsonLayout.prototype.isCombinedMessages = function() {
\r
1242 return this.combineMessages;
\r
1245 JsonLayout.prototype.format = function(loggingEvent) {
\r
1246 var layout = this;
\r
1247 var dataValues = this.getDataValues(loggingEvent, this.combineMessages);
\r
1248 var str = "{" + this.lineBreak;
\r
1251 function formatValue(val, prefix, expand) {
\r
1252 // Check the type of the data value to decide whether quotation marks
\r
1253 // or expansion are required
\r
1254 var formattedValue;
\r
1255 var valType = typeof val;
\r
1256 if (val instanceof Date) {
\r
1257 formattedValue = String(val.getTime());
\r
1258 } else if (expand && (val instanceof Array)) {
\r
1259 formattedValue = "[" + layout.lineBreak;
\r
1260 for (var i = 0, len = val.length; i < len; i++) {
\r
1261 var childPrefix = prefix + layout.tab;
\r
1262 formattedValue += childPrefix + formatValue(val[i], childPrefix, false);
\r
1263 if (i < val.length - 1) {
\r
1264 formattedValue += ",";
\r
1266 formattedValue += layout.lineBreak;
\r
1268 formattedValue += prefix + "]";
\r
1269 } else if (valType !== "number" && valType !== "boolean") {
\r
1270 formattedValue = "\"" + escapeNewLines(toStr(val).replace(/\"/g, "\\\"")) + "\"";
\r
1272 formattedValue = val;
\r
1274 return formattedValue;
\r
1277 for (i = 0, len = dataValues.length - 1; i <= len; i++) {
\r
1278 str += this.tab + "\"" + dataValues[i][0] + "\"" + this.colon + formatValue(dataValues[i][1], this.tab, true);
\r
1282 str += this.lineBreak;
\r
1285 str += "}" + this.lineBreak;
\r
1289 JsonLayout.prototype.ignoresThrowable = function() {
\r
1293 JsonLayout.prototype.toString = function() {
\r
1294 return "JsonLayout";
\r
1297 JsonLayout.prototype.getContentType = function() {
\r
1298 return "application/json";
\r
1301 log4javascript.JsonLayout = JsonLayout;
\r
1302 /* ---------------------------------------------------------------------- */
\r
1303 // HttpPostDataLayout
\r
1305 function HttpPostDataLayout() {
\r
1307 this.customFields = [];
\r
1308 this.returnsPostData = true;
\r
1311 HttpPostDataLayout.prototype = new Layout();
\r
1313 // Disable batching
\r
1314 HttpPostDataLayout.prototype.allowBatching = function() {
\r
1318 HttpPostDataLayout.prototype.format = function(loggingEvent) {
\r
1319 var dataValues = this.getDataValues(loggingEvent);
\r
1320 var queryBits = [];
\r
1321 for (var i = 0, len = dataValues.length; i < len; i++) {
\r
1322 var val = (dataValues[i][1] instanceof Date) ?
\r
1323 String(dataValues[i][1].getTime()) : dataValues[i][1];
\r
1324 queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(val));
\r
1326 return queryBits.join("&");
\r
1329 HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) {
\r
1333 HttpPostDataLayout.prototype.toString = function() {
\r
1334 return "HttpPostDataLayout";
\r
1337 log4javascript.HttpPostDataLayout = HttpPostDataLayout;
\r
1338 /* ---------------------------------------------------------------------- */
\r
1339 // formatObjectExpansion
\r
1341 function formatObjectExpansion(obj, depth, indentation) {
\r
1342 var objectsExpanded = [];
\r
1344 function doFormat(obj, depth, indentation) {
\r
1345 var i, j, len, childDepth, childIndentation, childLines, expansion,
\r
1348 if (!indentation) {
\r
1352 function formatString(text) {
\r
1353 var lines = splitIntoLines(text);
\r
1354 for (var j = 1, jLen = lines.length; j < jLen; j++) {
\r
1355 lines[j] = indentation + lines[j];
\r
1357 return lines.join(newLine);
\r
1360 if (obj === null) {
\r
1362 } else if (typeof obj == "undefined") {
\r
1363 return "undefined";
\r
1364 } else if (typeof obj == "string") {
\r
1365 return formatString(obj);
\r
1366 } else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) {
\r
1368 expansion = toStr(obj);
\r
1370 expansion = "Error formatting property. Details: " + getExceptionStringRep(ex);
\r
1372 return expansion + " [already expanded]";
\r
1373 } else if ((obj instanceof Array) && depth > 0) {
\r
1374 objectsExpanded.push(obj);
\r
1375 expansion = "[" + newLine;
\r
1376 childDepth = depth - 1;
\r
1377 childIndentation = indentation + " ";
\r
1379 for (i = 0, len = obj.length; i < len; i++) {
\r
1381 childExpansion = doFormat(obj[i], childDepth, childIndentation);
\r
1382 childLines.push(childIndentation + childExpansion);
\r
1384 childLines.push(childIndentation + "Error formatting array member. Details: " +
\r
1385 getExceptionStringRep(ex) + "");
\r
1388 expansion += childLines.join("," + newLine) + newLine + indentation + "]";
\r
1390 } else if (Object.prototype.toString.call(obj) == "[object Date]") {
\r
1391 return obj.toString();
\r
1392 } else if (typeof obj == "object" && depth > 0) {
\r
1393 objectsExpanded.push(obj);
\r
1394 expansion = "{" + newLine;
\r
1395 childDepth = depth - 1;
\r
1396 childIndentation = indentation + " ";
\r
1400 childExpansion = doFormat(obj[i], childDepth, childIndentation);
\r
1401 childLines.push(childIndentation + i + ": " + childExpansion);
\r
1403 childLines.push(childIndentation + i + ": Error formatting property. Details: " +
\r
1404 getExceptionStringRep(ex));
\r
1407 expansion += childLines.join("," + newLine) + newLine + indentation + "}";
\r
1410 return formatString(toStr(obj));
\r
1413 return doFormat(obj, depth, indentation);
\r
1415 /* ---------------------------------------------------------------------- */
\r
1416 // Date-related stuff
\r
1418 var SimpleDateFormat;
\r
1421 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']+)/;
\r
1422 var monthNames = ["January", "February", "March", "April", "May", "June",
\r
1423 "July", "August", "September", "October", "November", "December"];
\r
1424 var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
\r
1425 var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
\r
1446 var ONE_DAY = 24 * 60 * 60 * 1000;
\r
1447 var ONE_WEEK = 7 * ONE_DAY;
\r
1448 var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;
\r
1450 var newDateAtMidnight = function(year, month, day) {
\r
1451 var d = new Date(year, month, day, 0, 0, 0);
\r
1452 d.setMilliseconds(0);
\r
1456 Date.prototype.getDifference = function(date) {
\r
1457 return this.getTime() - date.getTime();
\r
1460 Date.prototype.isBefore = function(d) {
\r
1461 return this.getTime() < d.getTime();
\r
1464 Date.prototype.getUTCTime = function() {
\r
1465 return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
\r
1466 this.getSeconds(), this.getMilliseconds());
\r
1469 Date.prototype.getTimeSince = function(d) {
\r
1470 return this.getUTCTime() - d.getUTCTime();
\r
1473 Date.prototype.getPreviousSunday = function() {
\r
1474 // Using midday avoids any possibility of DST messing things up
\r
1475 var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
\r
1476 var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
\r
1477 return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
\r
1478 previousSunday.getDate());
\r
1481 Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
\r
1482 if (isUndefined(this.minimalDaysInFirstWeek)) {
\r
1483 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
\r
1485 var previousSunday = this.getPreviousSunday();
\r
1486 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
\r
1487 var numberOfSundays = previousSunday.isBefore(startOfYear) ?
\r
1488 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
\r
1489 var numberOfDaysInFirstWeek = 7 - startOfYear.getDay();
\r
1490 var weekInYear = numberOfSundays;
\r
1491 if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
\r
1494 return weekInYear;
\r
1497 Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
\r
1498 if (isUndefined(this.minimalDaysInFirstWeek)) {
\r
1499 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
\r
1501 var previousSunday = this.getPreviousSunday();
\r
1502 var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
\r
1503 var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
\r
1504 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK);
\r
1505 var numberOfDaysInFirstWeek = 7 - startOfMonth.getDay();
\r
1506 var weekInMonth = numberOfSundays;
\r
1507 if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
\r
1510 return weekInMonth;
\r
1513 Date.prototype.getDayInYear = function() {
\r
1514 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
\r
1515 return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
\r
1518 /* ------------------------------------------------------------------ */
\r
1520 SimpleDateFormat = function(formatString) {
\r
1521 this.formatString = formatString;
\r
1525 * Sets the minimum number of days in a week in order for that week to
\r
1526 * be considered as belonging to a particular month or year
\r
1528 SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
\r
1529 this.minimalDaysInFirstWeek = days;
\r
1532 SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() {
\r
1533 return isUndefined(this.minimalDaysInFirstWeek) ?
\r
1534 DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
\r
1537 var padWithZeroes = function(str, len) {
\r
1538 while (str.length < len) {
\r
1544 var formatText = function(data, numberOfLetters, minLength) {
\r
1545 return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
\r
1548 var formatNumber = function(data, numberOfLetters) {
\r
1549 var dataString = "" + data;
\r
1550 // Pad with 0s as necessary
\r
1551 return padWithZeroes(dataString, numberOfLetters);
\r
1554 SimpleDateFormat.prototype.format = function(date) {
\r
1555 var formattedString = "";
\r
1557 var searchString = this.formatString;
\r
1558 while ((result = regex.exec(searchString))) {
\r
1559 var quotedString = result[1];
\r
1560 var patternLetters = result[2];
\r
1561 var otherLetters = result[3];
\r
1562 var otherCharacters = result[4];
\r
1564 // If the pattern matched is quoted string, output the text between the quotes
\r
1565 if (quotedString) {
\r
1566 if (quotedString == "''") {
\r
1567 formattedString += "'";
\r
1569 formattedString += quotedString.substring(1, quotedString.length - 1);
\r
1571 } else if (otherLetters) {
\r
1572 // Swallow non-pattern letters by doing nothing here
\r
1573 } else if (otherCharacters) {
\r
1574 // Simply output other characters
\r
1575 formattedString += otherCharacters;
\r
1576 } else if (patternLetters) {
\r
1577 // Replace pattern letters
\r
1578 var patternLetter = patternLetters.charAt(0);
\r
1579 var numberOfLetters = patternLetters.length;
\r
1581 switch(patternLetter) {
\r
1586 rawData = date.getFullYear();
\r
1589 rawData = date.getMonth();
\r
1592 rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
\r
1595 rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
\r
1598 rawData = date.getDayInYear();
\r
1601 rawData = date.getDate();
\r
1604 rawData = 1 + Math.floor((date.getDate() - 1) / 7);
\r
1607 rawData = dayNames[date.getDay()];
\r
1610 rawData = (date.getHours() >= 12) ? "PM" : "AM";
\r
1613 rawData = date.getHours();
\r
1616 rawData = date.getHours() || 24;
\r
1619 rawData = date.getHours() % 12;
\r
1622 rawData = (date.getHours() % 12) || 12;
\r
1625 rawData = date.getMinutes();
\r
1628 rawData = date.getSeconds();
\r
1631 rawData = date.getMilliseconds();
\r
1634 rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time.
\r
1637 // Format the raw data depending on the type
\r
1638 switch(types[patternLetter]) {
\r
1640 formattedString += formatText(rawData, numberOfLetters, 2);
\r
1643 formattedString += formatText(rawData, numberOfLetters, 3);
\r
1646 formattedString += formatNumber(rawData, numberOfLetters);
\r
1649 if (numberOfLetters <= 3) {
\r
1650 // Output a 2-digit year
\r
1651 var dataString = "" + rawData;
\r
1652 formattedString += dataString.substr(2, 2);
\r
1654 formattedString += formatNumber(rawData, numberOfLetters);
\r
1658 if (numberOfLetters >= 3) {
\r
1659 formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
\r
1661 // NB. Months returned by getMonth are zero-based
\r
1662 formattedString += formatNumber(rawData + 1, numberOfLetters);
\r
1666 var isPositive = (rawData > 0);
\r
1667 // The following line looks like a mistake but isn't
\r
1668 // because of the way getTimezoneOffset measures.
\r
1669 var prefix = isPositive ? "-" : "+";
\r
1670 var absData = Math.abs(rawData);
\r
1673 var hours = "" + Math.floor(absData / 60);
\r
1674 hours = padWithZeroes(hours, 2);
\r
1676 var minutes = "" + (absData % 60);
\r
1677 minutes = padWithZeroes(minutes, 2);
\r
1679 formattedString += prefix + hours + minutes;
\r
1683 searchString = searchString.substr(result.index + result[0].length);
\r
1685 return formattedString;
\r
1689 log4javascript.SimpleDateFormat = SimpleDateFormat;
\r
1691 /* ---------------------------------------------------------------------- */
\r
1694 function PatternLayout(pattern) {
\r
1696 this.pattern = pattern;
\r
1698 this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
\r
1700 this.customFields = [];
\r
1703 PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
\r
1704 PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n";
\r
1705 PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
\r
1706 PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS";
\r
1707 PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS";
\r
1709 PatternLayout.prototype = new Layout();
\r
1711 PatternLayout.prototype.format = function(loggingEvent) {
\r
1712 var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;
\r
1713 var formattedString = "";
\r
1715 var searchString = this.pattern;
\r
1717 // Cannot use regex global flag since it doesn't work with exec in IE5
\r
1718 while ((result = regex.exec(searchString))) {
\r
1719 var matchedString = result[0];
\r
1720 var padding = result[1];
\r
1721 var truncation = result[2];
\r
1722 var conversionCharacter = result[3];
\r
1723 var specifier = result[5];
\r
1724 var text = result[6];
\r
1726 // Check if the pattern matched was just normal text
\r
1728 formattedString += "" + text;
\r
1730 // Create a raw replacement string based on the conversion
\r
1731 // character and specifier
\r
1732 var replacement = "";
\r
1733 switch(conversionCharacter) {
\r
1734 case "a": // Array of messages
\r
1735 case "m": // Message
\r
1738 depth = parseInt(specifier, 10);
\r
1739 if (isNaN(depth)) {
\r
1740 handleError("PatternLayout.format: invalid specifier '" +
\r
1741 specifier + "' for conversion character '" + conversionCharacter +
\r
1742 "' - should be a number");
\r
1746 var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages;
\r
1747 for (var i = 0, len = messages.length; i < len; i++) {
\r
1748 if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) {
\r
1749 replacement += " ";
\r
1751 if (depth === 0) {
\r
1752 replacement += messages[i];
\r
1754 replacement += formatObjectExpansion(messages[i], depth);
\r
1758 case "c": // Logger name
\r
1759 var loggerName = loggingEvent.logger.name;
\r
1761 var precision = parseInt(specifier, 10);
\r
1762 var loggerNameBits = loggingEvent.logger.name.split(".");
\r
1763 if (precision >= loggerNameBits.length) {
\r
1764 replacement = loggerName;
\r
1766 replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
\r
1769 replacement = loggerName;
\r
1773 var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
\r
1775 dateFormat = specifier;
\r
1776 // Pick up special cases
\r
1777 if (dateFormat == "ISO8601") {
\r
1778 dateFormat = PatternLayout.ISO8601_DATEFORMAT;
\r
1779 } else if (dateFormat == "ABSOLUTE") {
\r
1780 dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
\r
1781 } else if (dateFormat == "DATE") {
\r
1782 dateFormat = PatternLayout.DATETIME_DATEFORMAT;
\r
1785 // Format the date
\r
1786 replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);
\r
1788 case "f": // Custom field
\r
1789 if (this.hasCustomFields()) {
\r
1790 var fieldIndex = 0;
\r
1792 fieldIndex = parseInt(specifier, 10);
\r
1793 if (isNaN(fieldIndex)) {
\r
1794 handleError("PatternLayout.format: invalid specifier '" +
\r
1795 specifier + "' for conversion character 'f' - should be a number");
\r
1796 } else if (fieldIndex === 0) {
\r
1797 handleError("PatternLayout.format: invalid specifier '" +
\r
1798 specifier + "' for conversion character 'f' - must be greater than zero");
\r
1799 } else if (fieldIndex > this.customFields.length) {
\r
1800 handleError("PatternLayout.format: invalid specifier '" +
\r
1801 specifier + "' for conversion character 'f' - there aren't that many custom fields");
\r
1803 fieldIndex = fieldIndex - 1;
\r
1806 var val = this.customFields[fieldIndex].value;
\r
1807 if (typeof val == "function") {
\r
1808 val = val(this, loggingEvent);
\r
1810 replacement = val;
\r
1813 case "n": // New line
\r
1814 replacement = newLine;
\r
1816 case "p": // Level
\r
1817 replacement = loggingEvent.level.name;
\r
1819 case "r": // Milliseconds since log4javascript startup
\r
1820 replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate);
\r
1822 case "%": // Literal % sign
\r
1823 replacement = "%";
\r
1826 replacement = matchedString;
\r
1829 // Format the replacement according to any padding or
\r
1830 // truncation specified
\r
1833 // First, truncation
\r
1835 l = parseInt(truncation.substr(1), 10);
\r
1836 var strLen = replacement.length;
\r
1838 replacement = replacement.substring(strLen - l, strLen);
\r
1843 if (padding.charAt(0) == "-") {
\r
1844 l = parseInt(padding.substr(1), 10);
\r
1845 // Right pad with spaces
\r
1846 while (replacement.length < l) {
\r
1847 replacement += " ";
\r
1850 l = parseInt(padding, 10);
\r
1851 // Left pad with spaces
\r
1852 while (replacement.length < l) {
\r
1853 replacement = " " + replacement;
\r
1857 formattedString += replacement;
\r
1859 searchString = searchString.substr(result.index + result[0].length);
\r
1861 return formattedString;
\r
1864 PatternLayout.prototype.ignoresThrowable = function() {
\r
1868 PatternLayout.prototype.toString = function() {
\r
1869 return "PatternLayout";
\r
1872 log4javascript.PatternLayout = PatternLayout;
\r
1873 /* ---------------------------------------------------------------------- */
\r
1876 function AlertAppender() {}
\r
1878 AlertAppender.prototype = new Appender();
\r
1880 AlertAppender.prototype.layout = new SimpleLayout();
\r
1882 AlertAppender.prototype.append = function(loggingEvent) {
\r
1883 var formattedMessage = this.getLayout().format(loggingEvent);
\r
1884 if (this.getLayout().ignoresThrowable()) {
\r
1885 formattedMessage += loggingEvent.getThrowableStrRep();
\r
1887 alert(formattedMessage);
\r
1890 AlertAppender.prototype.toString = function() {
\r
1891 return "AlertAppender";
\r
1894 log4javascript.AlertAppender = AlertAppender;
\r
1895 /* ---------------------------------------------------------------------- */
\r
1896 // BrowserConsoleAppender (only works in Opera and Safari and Firefox with
\r
1897 // Firebug extension)
\r
1899 function BrowserConsoleAppender() {}
\r
1901 BrowserConsoleAppender.prototype = new log4javascript.Appender();
\r
1902 BrowserConsoleAppender.prototype.layout = new NullLayout();
\r
1903 BrowserConsoleAppender.prototype.threshold = Level.DEBUG;
\r
1905 BrowserConsoleAppender.prototype.append = function(loggingEvent) {
\r
1906 var appender = this;
\r
1908 var getFormattedMessage = function() {
\r
1909 var layout = appender.getLayout();
\r
1910 var formattedMessage = layout.format(loggingEvent);
\r
1911 if (layout.ignoresThrowable() && loggingEvent.exception) {
\r
1912 formattedMessage += loggingEvent.getThrowableStrRep();
\r
1914 return formattedMessage;
\r
1917 if ((typeof opera != "undefined") && opera.postError) { // Opera
\r
1918 opera.postError(getFormattedMessage());
\r
1919 } else if (window.console && window.console.log) { // Safari and Firebug
\r
1920 var formattedMesage = getFormattedMessage();
\r
1921 // Log to Firebug using its logging methods or revert to the console.log
\r
1922 // method in Safari
\r
1923 if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
\r
1924 window.console.debug(formattedMesage);
\r
1925 } else if (window.console.info && Level.INFO.equals(loggingEvent.level)) {
\r
1926 window.console.info(formattedMesage);
\r
1927 } else if (window.console.warn && Level.WARN.equals(loggingEvent.level)) {
\r
1928 window.console.warn(formattedMesage);
\r
1929 } else if (window.console.error && loggingEvent.level.isGreaterOrEqual(Level.ERROR)) {
\r
1930 window.console.error(formattedMesage);
\r
1932 window.console.log(formattedMesage);
\r
1937 BrowserConsoleAppender.prototype.group = function(name) {
\r
1938 if (window.console && window.console.group) {
\r
1939 window.console.group(name);
\r
1943 BrowserConsoleAppender.prototype.groupEnd = function() {
\r
1944 if (window.console && window.console.groupEnd) {
\r
1945 window.console.groupEnd();
\r
1949 BrowserConsoleAppender.prototype.toString = function() {
\r
1950 return "BrowserConsoleAppender";
\r
1953 log4javascript.BrowserConsoleAppender = BrowserConsoleAppender;
\r
1954 /* ---------------------------------------------------------------------- */
\r
1955 // AjaxAppender related
\r
1957 var xmlHttpFactories = [
\r
1958 function() { return new XMLHttpRequest(); },
\r
1959 function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
\r
1960 function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
\r
1963 var getXmlHttp = function(errorHandler) {
\r
1964 // This is only run the first time; the value of getXmlHttp gets
\r
1965 // replaced with the factory that succeeds on the first run
\r
1966 var xmlHttp = null, factory;
\r
1967 for (var i = 0, len = xmlHttpFactories.length; i < len; i++) {
\r
1968 factory = xmlHttpFactories[i];
\r
1970 xmlHttp = factory();
\r
1971 getXmlHttp = factory;
\r
1976 // If we're here, all factories have failed, so throw an error
\r
1977 if (errorHandler) {
\r
1980 handleError("getXmlHttp: unable to obtain XMLHttpRequest object");
\r
1984 function isHttpRequestSuccessful(xmlHttp) {
\r
1985 return isUndefined(xmlHttp.status) || xmlHttp.status === 0 ||
\r
1986 (xmlHttp.status >= 200 && xmlHttp.status < 300) ||
\r
1987 xmlHttp.status == 1223 /* Fix for IE */;
\r
1990 /* ---------------------------------------------------------------------- */
\r
1993 function AjaxAppender(url) {
\r
1994 var appender = this;
\r
1995 var isSupported = true;
\r
1997 handleError("AjaxAppender: URL must be specified in constructor");
\r
1998 isSupported = false;
\r
2001 var timed = this.defaults.timed;
\r
2002 var waitForResponse = this.defaults.waitForResponse;
\r
2003 var batchSize = this.defaults.batchSize;
\r
2004 var timerInterval = this.defaults.timerInterval;
\r
2005 var requestSuccessCallback = this.defaults.requestSuccessCallback;
\r
2006 var failCallback = this.defaults.failCallback;
\r
2007 var postVarName = this.defaults.postVarName;
\r
2008 var sendAllOnUnload = this.defaults.sendAllOnUnload;
\r
2009 var contentType = this.defaults.contentType;
\r
2010 var sessionId = null;
\r
2012 var queuedLoggingEvents = [];
\r
2013 var queuedRequests = [];
\r
2015 var sending = false;
\r
2016 var initialized = false;
\r
2018 // Configuration methods. The function scope is used to prevent
\r
2019 // direct alteration to the appender configuration properties.
\r
2020 function checkCanConfigure(configOptionName) {
\r
2021 if (initialized) {
\r
2022 handleError("AjaxAppender: configuration option '" +
\r
2023 configOptionName +
\r
2024 "' may not be set after the appender has been initialized");
\r
2030 this.getSessionId = function() { return sessionId; };
\r
2031 this.setSessionId = function(sessionIdParam) {
\r
2032 sessionId = extractStringFromParam(sessionIdParam, null);
\r
2033 this.layout.setCustomField("sessionid", sessionId);
\r
2036 this.setLayout = function(layoutParam) {
\r
2037 if (checkCanConfigure("layout")) {
\r
2038 this.layout = layoutParam;
\r
2039 // Set the session id as a custom field on the layout, if not already present
\r
2040 if (sessionId !== null) {
\r
2041 this.setSessionId(sessionId);
\r
2046 this.isTimed = function() { return timed; };
\r
2047 this.setTimed = function(timedParam) {
\r
2048 if (checkCanConfigure("timed")) {
\r
2049 timed = bool(timedParam);
\r
2053 this.getTimerInterval = function() { return timerInterval; };
\r
2054 this.setTimerInterval = function(timerIntervalParam) {
\r
2055 if (checkCanConfigure("timerInterval")) {
\r
2056 timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
\r
2060 this.isWaitForResponse = function() { return waitForResponse; };
\r
2061 this.setWaitForResponse = function(waitForResponseParam) {
\r
2062 if (checkCanConfigure("waitForResponse")) {
\r
2063 waitForResponse = bool(waitForResponseParam);
\r
2067 this.getBatchSize = function() { return batchSize; };
\r
2068 this.setBatchSize = function(batchSizeParam) {
\r
2069 if (checkCanConfigure("batchSize")) {
\r
2070 batchSize = extractIntFromParam(batchSizeParam, batchSize);
\r
2074 this.isSendAllOnUnload = function() { return sendAllOnUnload; };
\r
2075 this.setSendAllOnUnload = function(sendAllOnUnloadParam) {
\r
2076 if (checkCanConfigure("sendAllOnUnload")) {
\r
2077 sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload);
\r
2081 this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
\r
2082 requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
\r
2085 this.setFailCallback = function(failCallbackParam) {
\r
2086 failCallback = extractFunctionFromParam(failCallbackParam, failCallback);
\r
2089 this.getPostVarName = function() { return postVarName; };
\r
2090 this.setPostVarName = function(postVarNameParam) {
\r
2091 if (checkCanConfigure("postVarName")) {
\r
2092 postVarName = extractStringFromParam(postVarNameParam, postVarName);
\r
2096 this.getHeaders = function() { return headers; };
\r
2097 this.addHeader = function(name, value) {
\r
2098 if (name.toLowerCase() == "content-type") {
\r
2099 contentType = value;
\r
2101 headers.push( { name: name, value: value } );
\r
2105 // Internal functions
\r
2106 function sendAll() {
\r
2107 if (isSupported && enabled) {
\r
2109 var currentRequestBatch;
\r
2110 if (waitForResponse) {
\r
2111 // Send the first request then use this function as the callback once
\r
2112 // the response comes back
\r
2113 if (queuedRequests.length > 0) {
\r
2114 currentRequestBatch = queuedRequests.shift();
\r
2115 sendRequest(preparePostData(currentRequestBatch), sendAll);
\r
2119 scheduleSending();
\r
2123 // Rattle off all the requests without waiting to see the response
\r
2124 while ((currentRequestBatch = queuedRequests.shift())) {
\r
2125 sendRequest(preparePostData(currentRequestBatch));
\r
2129 scheduleSending();
\r
2135 this.sendAll = sendAll;
\r
2137 // Called when the window unloads. At this point we're past caring about
\r
2138 // waiting for responses or timers or incomplete batches - everything
\r
2140 function sendAllRemaining() {
\r
2141 var sendingAnything = false;
\r
2142 if (isSupported && enabled) {
\r
2143 // Create requests for everything left over, batched as normal
\r
2144 var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1;
\r
2145 var currentLoggingEvent;
\r
2146 var batchedLoggingEvents = [];
\r
2147 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
\r
2148 batchedLoggingEvents.push(currentLoggingEvent);
\r
2149 if (queuedLoggingEvents.length >= actualBatchSize) {
\r
2150 // Queue this batch of log entries
\r
2151 queuedRequests.push(batchedLoggingEvents);
\r
2152 batchedLoggingEvents = [];
\r
2155 // If there's a partially completed batch, add it
\r
2156 if (batchedLoggingEvents.length > 0) {
\r
2157 queuedRequests.push(batchedLoggingEvents);
\r
2159 sendingAnything = (queuedRequests.length > 0);
\r
2160 waitForResponse = false;
\r
2164 return sendingAnything;
\r
2167 this.sendAllRemaining = sendAllRemaining;
\r
2169 function preparePostData(batchedLoggingEvents) {
\r
2170 // Format the logging events
\r
2171 var formattedMessages = [];
\r
2172 var currentLoggingEvent;
\r
2173 var postData = "";
\r
2174 while ((currentLoggingEvent = batchedLoggingEvents.shift())) {
\r
2175 var currentFormattedMessage = appender.getLayout().format(currentLoggingEvent);
\r
2176 if (appender.getLayout().ignoresThrowable()) {
\r
2177 currentFormattedMessage += currentLoggingEvent.getThrowableStrRep();
\r
2179 formattedMessages.push(currentFormattedMessage);
\r
2181 // Create the post data string
\r
2182 if (batchedLoggingEvents.length == 1) {
\r
2183 postData = formattedMessages.join("");
\r
2185 postData = appender.getLayout().batchHeader +
\r
2186 formattedMessages.join(appender.getLayout().batchSeparator) +
\r
2187 appender.getLayout().batchFooter;
\r
2189 if (contentType == appender.defaults.contentType) {
\r
2190 postData = appender.getLayout().returnsPostData ? postData :
\r
2191 urlEncode(postVarName) + "=" + urlEncode(postData);
\r
2192 // Add the layout name to the post data
\r
2193 if (postData.length > 0) {
\r
2196 postData += "layout=" + urlEncode(appender.getLayout().toString());
\r
2201 function scheduleSending() {
\r
2202 window.setTimeout(sendAll, timerInterval);
\r
2205 function xmlHttpErrorHandler() {
\r
2206 var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";
\r
2208 isSupported = false;
\r
2209 if (failCallback) {
\r
2210 failCallback(msg);
\r
2214 function sendRequest(postData, successCallback) {
\r
2216 var xmlHttp = getXmlHttp(xmlHttpErrorHandler);
\r
2217 if (isSupported) {
\r
2218 if (xmlHttp.overrideMimeType) {
\r
2219 xmlHttp.overrideMimeType(appender.getLayout().getContentType());
\r
2221 xmlHttp.onreadystatechange = function() {
\r
2222 if (xmlHttp.readyState == 4) {
\r
2223 if (isHttpRequestSuccessful(xmlHttp)) {
\r
2224 if (requestSuccessCallback) {
\r
2225 requestSuccessCallback(xmlHttp);
\r
2227 if (successCallback) {
\r
2228 successCallback(xmlHttp);
\r
2231 var msg = "AjaxAppender.append: XMLHttpRequest request to URL " +
\r
2232 url + " returned status code " + xmlHttp.status;
\r
2234 if (failCallback) {
\r
2235 failCallback(msg);
\r
2238 xmlHttp.onreadystatechange = emptyFunction;
\r
2242 xmlHttp.open("POST", url, true);
\r
2244 for (var i = 0, header; header = headers[i++]; ) {
\r
2245 xmlHttp.setRequestHeader(header.name, header.value);
\r
2247 xmlHttp.setRequestHeader("Content-Type", contentType);
\r
2248 } catch (headerEx) {
\r
2249 var msg = "AjaxAppender.append: your browser's XMLHttpRequest implementation" +
\r
2250 " does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled";
\r
2252 isSupported = false;
\r
2253 if (failCallback) {
\r
2254 failCallback(msg);
\r
2258 xmlHttp.send(postData);
\r
2261 var errMsg = "AjaxAppender.append: error sending log message to " + url;
\r
2262 handleError(errMsg, ex);
\r
2263 isSupported = false;
\r
2264 if (failCallback) {
\r
2265 failCallback(errMsg + ". Details: " + getExceptionStringRep(ex));
\r
2270 this.append = function(loggingEvent) {
\r
2271 if (isSupported) {
\r
2272 if (!initialized) {
\r
2275 queuedLoggingEvents.push(loggingEvent);
\r
2276 var actualBatchSize = this.getLayout().allowBatching() ? batchSize : 1;
\r
2278 if (queuedLoggingEvents.length >= actualBatchSize) {
\r
2279 var currentLoggingEvent;
\r
2280 var batchedLoggingEvents = [];
\r
2281 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
\r
2282 batchedLoggingEvents.push(currentLoggingEvent);
\r
2284 // Queue this batch of log entries
\r
2285 queuedRequests.push(batchedLoggingEvents);
\r
2287 // If using a timer, the queue of requests will be processed by the
\r
2288 // timer function, so nothing needs to be done here.
\r
2289 if (!timed && (!waitForResponse || (waitForResponse && !sending))) {
\r
2297 initialized = true;
\r
2298 // Add unload event to send outstanding messages
\r
2299 if (sendAllOnUnload) {
\r
2300 var oldBeforeUnload = window.onbeforeunload;
\r
2301 window.onbeforeunload = function() {
\r
2302 if (oldBeforeUnload) {
\r
2303 oldBeforeUnload();
\r
2305 if (sendAllRemaining()) {
\r
2306 return "Sending log messages";
\r
2312 scheduleSending();
\r
2317 AjaxAppender.prototype = new Appender();
\r
2319 AjaxAppender.prototype.defaults = {
\r
2320 waitForResponse: false,
\r
2322 timerInterval: 1000,
\r
2324 sendAllOnUnload: false,
\r
2325 requestSuccessCallback: null,
\r
2326 failCallback: null,
\r
2327 postVarName: "data",
\r
2328 contentType: "application/x-www-form-urlencoded"
\r
2331 AjaxAppender.prototype.layout = new HttpPostDataLayout();
\r
2333 AjaxAppender.prototype.toString = function() {
\r
2334 return "AjaxAppender";
\r
2337 log4javascript.AjaxAppender = AjaxAppender;
\r
2338 /* ---------------------------------------------------------------------- */
\r
2339 // PopUpAppender and InPageAppender related
\r
2341 function setCookie(name, value, days, path) {
\r
2343 path = path ? "; path=" + path : "";
\r
2345 var date = new Date();
\r
2346 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
\r
2347 expires = "; expires=" + date.toGMTString();
\r
2351 document.cookie = escape(name) + "=" + escape(value) + expires + path;
\r
2354 function getCookie(name) {
\r
2355 var nameEquals = escape(name) + "=";
\r
2356 var ca = document.cookie.split(";");
\r
2357 for (var i = 0, len = ca.length; i < len; i++) {
\r
2359 while (c.charAt(0) === " ") {
\r
2360 c = c.substring(1, c.length);
\r
2362 if (c.indexOf(nameEquals) === 0) {
\r
2363 return unescape(c.substring(nameEquals.length, c.length));
\r
2369 // Gets the base URL of the location of the log4javascript script.
\r
2370 // This is far from infallible.
\r
2371 function getBaseUrl() {
\r
2372 var scripts = document.getElementsByTagName("script");
\r
2373 for (var i = 0, len = scripts.length; i < len; ++i) {
\r
2374 if (scripts[i].src.indexOf("log4javascript") != -1) {
\r
2375 var lastSlash = scripts[i].src.lastIndexOf("/");
\r
2376 return (lastSlash == -1) ? "" : scripts[i].src.substr(0, lastSlash + 1);
\r
2382 function isLoaded(win) {
\r
2384 return bool(win.loaded);
\r
2390 /* ---------------------------------------------------------------------- */
\r
2391 // ConsoleAppender (prototype for PopUpAppender and InPageAppender)
\r
2393 var ConsoleAppender;
\r
2395 // Create an anonymous function to protect base console methods
\r
2397 var getConsoleHtmlLines = function() {
\r
2399 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
\r
2400 '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">',
\r
2402 ' <title>log4javascript</title>',
\r
2403 ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
\r
2404 ' <!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->',
\r
2405 ' <meta http-equiv="X-UA-Compatible" content="IE=7" />',
\r
2406 ' <script type="text/javascript">var isIe = false, isIePre7 = false;</script>',
\r
2407 ' <!--[if IE]><script type="text/javascript">isIe = true</script><![endif]-->',
\r
2408 ' <!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]-->',
\r
2409 ' <script type="text/javascript">',
\r
2411 ' var loggingEnabled = true;',
\r
2412 ' var logQueuedEventsTimer = null;',
\r
2413 ' var logEntries = [];',
\r
2414 ' var logEntriesAndSeparators = [];',
\r
2415 ' var logItems = [];',
\r
2416 ' var renderDelay = 100;',
\r
2417 ' var unrenderedLogItemsExist = false;',
\r
2418 ' var rootGroup, currentGroup = null;',
\r
2419 ' var loaded = false;',
\r
2420 ' var currentLogItem = null;',
\r
2421 ' var logMainContainer;',
\r
2423 ' function copyProperties(obj, props) {',
\r
2424 ' for (var i in props) {',
\r
2425 ' obj[i] = props[i];',
\r
2429 ' /*----------------------------------------------------------------*/',
\r
2431 ' function LogItem() {',
\r
2434 ' LogItem.prototype = {',
\r
2435 ' mainContainer: null,',
\r
2436 ' wrappedContainer: null,',
\r
2437 ' unwrappedContainer: null,',
\r
2440 ' appendToLog: function() {',
\r
2441 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2442 ' this.elementContainers[i].appendToLog();',
\r
2444 ' this.group.update();',
\r
2447 ' doRemove: function(doUpdate, removeFromGroup) {',
\r
2448 ' if (this.rendered) {',
\r
2449 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2450 ' this.elementContainers[i].remove();',
\r
2452 ' this.unwrappedElementContainer = null;',
\r
2453 ' this.wrappedElementContainer = null;',
\r
2454 ' this.mainElementContainer = null;',
\r
2456 ' if (this.group && removeFromGroup) {',
\r
2457 ' this.group.removeChild(this, doUpdate);',
\r
2459 ' if (this === currentLogItem) {',
\r
2460 ' currentLogItem = null;',
\r
2464 ' remove: function(doUpdate, removeFromGroup) {',
\r
2465 ' this.doRemove(doUpdate, removeFromGroup);',
\r
2468 ' render: function() {},',
\r
2470 ' accept: function(visitor) {',
\r
2471 ' visitor.visit(this);',
\r
2474 ' getUnwrappedDomContainer: function() {',
\r
2475 ' return this.group.unwrappedElementContainer.contentDiv;',
\r
2478 ' getWrappedDomContainer: function() {',
\r
2479 ' return this.group.wrappedElementContainer.contentDiv;',
\r
2482 ' getMainDomContainer: function() {',
\r
2483 ' return this.group.mainElementContainer.contentDiv;',
\r
2487 ' LogItem.serializedItemKeys = {LOG_ENTRY: 0, GROUP_START: 1, GROUP_END: 2};',
\r
2489 ' /*----------------------------------------------------------------*/',
\r
2491 ' function LogItemContainerElement() {',
\r
2494 ' LogItemContainerElement.prototype = {',
\r
2495 ' appendToLog: function() {',
\r
2496 ' var insertBeforeFirst = (newestAtTop && this.containerDomNode.hasChildNodes());',
\r
2497 ' if (insertBeforeFirst) {',
\r
2498 ' this.containerDomNode.insertBefore(this.mainDiv, this.containerDomNode.firstChild);',
\r
2500 ' this.containerDomNode.appendChild(this.mainDiv);',
\r
2505 ' /*----------------------------------------------------------------*/',
\r
2507 ' function SeparatorElementContainer(containerDomNode) {',
\r
2508 ' this.containerDomNode = containerDomNode;',
\r
2509 ' this.mainDiv = document.createElement("div");',
\r
2510 ' this.mainDiv.className = "separator";',
\r
2511 ' this.mainDiv.innerHTML = " ";',
\r
2514 ' SeparatorElementContainer.prototype = new LogItemContainerElement();',
\r
2516 ' SeparatorElementContainer.prototype.remove = function() {',
\r
2517 ' this.mainDiv.parentNode.removeChild(this.mainDiv);',
\r
2518 ' this.mainDiv = null;',
\r
2521 ' /*----------------------------------------------------------------*/',
\r
2523 ' function Separator() {',
\r
2524 ' this.rendered = false;',
\r
2527 ' Separator.prototype = new LogItem();',
\r
2529 ' copyProperties(Separator.prototype, {',
\r
2530 ' render: function() {',
\r
2531 ' var containerDomNode = this.group.contentDiv;',
\r
2533 ' this.unwrappedElementContainer = new SeparatorElementContainer(this.getUnwrappedDomContainer());',
\r
2534 ' this.wrappedElementContainer = new SeparatorElementContainer(this.getWrappedDomContainer());',
\r
2535 ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
\r
2537 ' this.mainElementContainer = new SeparatorElementContainer(this.getMainDomContainer());',
\r
2538 ' this.elementContainers = [this.mainElementContainer];',
\r
2540 ' this.content = this.formattedMessage;',
\r
2541 ' this.rendered = true;',
\r
2545 ' /*----------------------------------------------------------------*/',
\r
2547 ' function GroupElementContainer(group, containerDomNode, isRoot, isWrapped) {',
\r
2548 ' this.group = group;',
\r
2549 ' this.containerDomNode = containerDomNode;',
\r
2550 ' this.isRoot = isRoot;',
\r
2551 ' this.isWrapped = isWrapped;',
\r
2552 ' this.expandable = false;',
\r
2554 ' if (this.isRoot) {',
\r
2556 ' this.contentDiv = logMainContainer.appendChild(document.createElement("div"));',
\r
2557 ' this.contentDiv.id = this.isWrapped ? "log_wrapped" : "log_unwrapped";',
\r
2559 ' this.contentDiv = logMainContainer;',
\r
2562 ' var groupElementContainer = this;',
\r
2564 ' this.mainDiv = document.createElement("div");',
\r
2565 ' this.mainDiv.className = "group";',
\r
2567 ' this.headingDiv = this.mainDiv.appendChild(document.createElement("div"));',
\r
2568 ' this.headingDiv.className = "groupheading";',
\r
2570 ' this.expander = this.headingDiv.appendChild(document.createElement("span"));',
\r
2571 ' this.expander.className = "expander unselectable greyedout";',
\r
2572 ' this.expander.unselectable = true;',
\r
2573 ' var expanderText = this.group.expanded ? "-" : "+";',
\r
2574 ' this.expanderTextNode = this.expander.appendChild(document.createTextNode(expanderText));',
\r
2576 ' this.headingDiv.appendChild(document.createTextNode(" " + this.group.name));',
\r
2578 ' this.contentDiv = this.mainDiv.appendChild(document.createElement("div"));',
\r
2579 ' var contentCssClass = this.group.expanded ? "expanded" : "collapsed";',
\r
2580 ' this.contentDiv.className = "groupcontent " + contentCssClass;',
\r
2582 ' this.expander.onclick = function() {',
\r
2583 ' if (groupElementContainer.group.expandable) {',
\r
2584 ' groupElementContainer.group.toggleExpanded();',
\r
2590 ' GroupElementContainer.prototype = new LogItemContainerElement();',
\r
2592 ' copyProperties(GroupElementContainer.prototype, {',
\r
2593 ' toggleExpanded: function() {',
\r
2594 ' if (!this.isRoot) {',
\r
2595 ' var oldCssClass, newCssClass, expanderText;',
\r
2596 ' if (this.group.expanded) {',
\r
2597 ' newCssClass = "expanded";',
\r
2598 ' oldCssClass = "collapsed";',
\r
2599 ' expanderText = "-";',
\r
2601 ' newCssClass = "collapsed";',
\r
2602 ' oldCssClass = "expanded";',
\r
2603 ' expanderText = "+";',
\r
2605 ' replaceClass(this.contentDiv, newCssClass, oldCssClass);',
\r
2606 ' this.expanderTextNode.nodeValue = expanderText;',
\r
2610 ' remove: function() {',
\r
2611 ' if (!this.isRoot) {',
\r
2612 ' this.headingDiv = null;',
\r
2613 ' this.expander.onclick = null;',
\r
2614 ' this.expander = null;',
\r
2615 ' this.expanderTextNode = null;',
\r
2616 ' this.contentDiv = null;',
\r
2617 ' this.containerDomNode = null;',
\r
2618 ' this.mainDiv.parentNode.removeChild(this.mainDiv);',
\r
2619 ' this.mainDiv = null;',
\r
2623 ' reverseChildren: function() {',
\r
2624 ' // Invert the order of the log entries',
\r
2625 ' var node = null;',
\r
2627 ' // Remove all the log container nodes',
\r
2628 ' var childDomNodes = [];',
\r
2629 ' while ((node = this.contentDiv.firstChild)) {',
\r
2630 ' this.contentDiv.removeChild(node);',
\r
2631 ' childDomNodes.push(node);',
\r
2634 ' // Put them all back in reverse order',
\r
2635 ' while ((node = childDomNodes.pop())) {',
\r
2636 ' this.contentDiv.appendChild(node);',
\r
2640 ' update: function() {',
\r
2641 ' if (!this.isRoot) {',
\r
2642 ' if (this.group.expandable) {',
\r
2643 ' removeClass(this.expander, "greyedout");',
\r
2645 ' addClass(this.expander, "greyedout");',
\r
2650 ' clear: function() {',
\r
2651 ' if (this.isRoot) {',
\r
2652 ' this.contentDiv.innerHTML = "";',
\r
2657 ' /*----------------------------------------------------------------*/',
\r
2659 ' function Group(name, isRoot, initiallyExpanded) {',
\r
2660 ' this.name = name;',
\r
2661 ' this.group = null;',
\r
2662 ' this.isRoot = isRoot;',
\r
2663 ' this.initiallyExpanded = initiallyExpanded;',
\r
2664 ' this.elementContainers = [];',
\r
2665 ' this.children = [];',
\r
2666 ' this.expanded = initiallyExpanded;',
\r
2667 ' this.rendered = false;',
\r
2668 ' this.expandable = false;',
\r
2671 ' Group.prototype = new LogItem();',
\r
2673 ' copyProperties(Group.prototype, {',
\r
2674 ' addChild: function(logItem) {',
\r
2675 ' this.children.push(logItem);',
\r
2676 ' logItem.group = this;',
\r
2679 ' render: function() {',
\r
2681 ' var unwrappedDomContainer, wrappedDomContainer;',
\r
2682 ' if (this.isRoot) {',
\r
2683 ' unwrappedDomContainer = logMainContainer;',
\r
2684 ' wrappedDomContainer = logMainContainer;',
\r
2686 ' unwrappedDomContainer = this.getUnwrappedDomContainer();',
\r
2687 ' wrappedDomContainer = this.getWrappedDomContainer();',
\r
2689 ' this.unwrappedElementContainer = new GroupElementContainer(this, unwrappedDomContainer, this.isRoot, false);',
\r
2690 ' this.wrappedElementContainer = new GroupElementContainer(this, wrappedDomContainer, this.isRoot, true);',
\r
2691 ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
\r
2693 ' var mainDomContainer = this.isRoot ? logMainContainer : this.getMainDomContainer();',
\r
2694 ' this.mainElementContainer = new GroupElementContainer(this, mainDomContainer, this.isRoot, false);',
\r
2695 ' this.elementContainers = [this.mainElementContainer];',
\r
2697 ' this.rendered = true;',
\r
2700 ' toggleExpanded: function() {',
\r
2701 ' this.expanded = !this.expanded;',
\r
2702 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2703 ' this.elementContainers[i].toggleExpanded();',
\r
2707 ' expand: function() {',
\r
2708 ' if (!this.expanded) {',
\r
2709 ' this.toggleExpanded();',
\r
2713 ' accept: function(visitor) {',
\r
2714 ' visitor.visitGroup(this);',
\r
2717 ' reverseChildren: function() {',
\r
2718 ' if (this.rendered) {',
\r
2719 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2720 ' this.elementContainers[i].reverseChildren();',
\r
2725 ' update: function() {',
\r
2726 ' var previouslyExpandable = this.expandable;',
\r
2727 ' this.expandable = (this.children.length !== 0);',
\r
2728 ' if (this.expandable !== previouslyExpandable) {',
\r
2729 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2730 ' this.elementContainers[i].update();',
\r
2735 ' flatten: function() {',
\r
2736 ' var visitor = new GroupFlattener();',
\r
2737 ' this.accept(visitor);',
\r
2738 ' return visitor.logEntriesAndSeparators;',
\r
2741 ' removeChild: function(child, doUpdate) {',
\r
2742 ' array_remove(this.children, child);',
\r
2743 ' child.group = null;',
\r
2744 ' if (doUpdate) {',
\r
2745 ' this.update();',
\r
2749 ' remove: function(doUpdate, removeFromGroup) {',
\r
2750 ' for (var i = 0, len = this.children.length; i < len; i++) {',
\r
2751 ' this.children[i].remove(false, false);',
\r
2753 ' this.children = [];',
\r
2754 ' this.update();',
\r
2755 ' if (this === currentGroup) {',
\r
2756 ' currentGroup = this.group;',
\r
2758 ' this.doRemove(doUpdate, removeFromGroup);',
\r
2761 ' serialize: function(items) {',
\r
2762 ' items.push([LogItem.serializedItemKeys.GROUP_START, this.name]);',
\r
2763 ' for (var i = 0, len = this.children.length; i < len; i++) {',
\r
2764 ' this.children[i].serialize(items);',
\r
2766 ' if (this !== currentGroup) {',
\r
2767 ' items.push([LogItem.serializedItemKeys.GROUP_END]);',
\r
2771 ' clear: function() {',
\r
2772 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2773 ' this.elementContainers[i].clear();',
\r
2778 ' /*----------------------------------------------------------------*/',
\r
2780 ' function LogEntryElementContainer() {',
\r
2783 ' LogEntryElementContainer.prototype = new LogItemContainerElement();',
\r
2785 ' copyProperties(LogEntryElementContainer.prototype, {',
\r
2786 ' remove: function() {',
\r
2787 ' this.doRemove();',
\r
2790 ' doRemove: function() {',
\r
2791 ' this.mainDiv.parentNode.removeChild(this.mainDiv);',
\r
2792 ' this.mainDiv = null;',
\r
2793 ' this.contentElement = null;',
\r
2794 ' this.containerDomNode = null;',
\r
2797 ' setContent: function(content, wrappedContent) {',
\r
2798 ' if (content === this.formattedMessage) {',
\r
2799 ' this.contentElement.innerHTML = "";',
\r
2800 ' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));',
\r
2802 ' this.contentElement.innerHTML = content;',
\r
2806 ' setSearchMatch: function(isMatch) {',
\r
2807 ' var oldCssClass = isMatch ? "searchnonmatch" : "searchmatch";',
\r
2808 ' var newCssClass = isMatch ? "searchmatch" : "searchnonmatch";',
\r
2809 ' replaceClass(this.mainDiv, newCssClass, oldCssClass);',
\r
2812 ' clearSearch: function() {',
\r
2813 ' removeClass(this.mainDiv, "searchmatch");',
\r
2814 ' removeClass(this.mainDiv, "searchnonmatch");',
\r
2818 ' /*----------------------------------------------------------------*/',
\r
2820 ' function LogEntryWrappedElementContainer(logEntry, containerDomNode) {',
\r
2821 ' this.logEntry = logEntry;',
\r
2822 ' this.containerDomNode = containerDomNode;',
\r
2823 ' this.mainDiv = document.createElement("div");',
\r
2824 ' this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
\r
2825 ' this.mainDiv.className = "logentry wrapped " + this.logEntry.level;',
\r
2826 ' this.contentElement = this.mainDiv;',
\r
2829 ' LogEntryWrappedElementContainer.prototype = new LogEntryElementContainer();',
\r
2831 ' LogEntryWrappedElementContainer.prototype.setContent = function(content, wrappedContent) {',
\r
2832 ' if (content === this.formattedMessage) {',
\r
2833 ' this.contentElement.innerHTML = "";',
\r
2834 ' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));',
\r
2836 ' this.contentElement.innerHTML = wrappedContent;',
\r
2840 ' /*----------------------------------------------------------------*/',
\r
2842 ' function LogEntryUnwrappedElementContainer(logEntry, containerDomNode) {',
\r
2843 ' this.logEntry = logEntry;',
\r
2844 ' this.containerDomNode = containerDomNode;',
\r
2845 ' this.mainDiv = document.createElement("div");',
\r
2846 ' this.mainDiv.className = "logentry unwrapped " + this.logEntry.level;',
\r
2847 ' this.pre = this.mainDiv.appendChild(document.createElement("pre"));',
\r
2848 ' this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
\r
2849 ' this.pre.className = "unwrapped";',
\r
2850 ' this.contentElement = this.pre;',
\r
2853 ' LogEntryUnwrappedElementContainer.prototype = new LogEntryElementContainer();',
\r
2855 ' LogEntryUnwrappedElementContainer.prototype.remove = function() {',
\r
2856 ' this.doRemove();',
\r
2857 ' this.pre = null;',
\r
2860 ' /*----------------------------------------------------------------*/',
\r
2862 ' function LogEntryMainElementContainer(logEntry, containerDomNode) {',
\r
2863 ' this.logEntry = logEntry;',
\r
2864 ' this.containerDomNode = containerDomNode;',
\r
2865 ' this.mainDiv = document.createElement("div");',
\r
2866 ' this.mainDiv.className = "logentry nonielogentry " + this.logEntry.level;',
\r
2867 ' this.contentElement = this.mainDiv.appendChild(document.createElement("span"));',
\r
2868 ' this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
\r
2871 ' LogEntryMainElementContainer.prototype = new LogEntryElementContainer();',
\r
2873 ' /*----------------------------------------------------------------*/',
\r
2875 ' function LogEntry(level, formattedMessage) {',
\r
2876 ' this.level = level;',
\r
2877 ' this.formattedMessage = formattedMessage;',
\r
2878 ' this.rendered = false;',
\r
2881 ' LogEntry.prototype = new LogItem();',
\r
2883 ' copyProperties(LogEntry.prototype, {',
\r
2884 ' render: function() {',
\r
2885 ' var logEntry = this;',
\r
2886 ' var containerDomNode = this.group.contentDiv;',
\r
2888 ' // Support for the CSS attribute white-space in IE for Windows is',
\r
2889 ' // non-existent pre version 6 and slightly odd in 6, so instead',
\r
2890 ' // use two different HTML elements',
\r
2892 ' this.formattedMessage = this.formattedMessage.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space',
\r
2893 ' this.unwrappedElementContainer = new LogEntryUnwrappedElementContainer(this, this.getUnwrappedDomContainer());',
\r
2894 ' this.wrappedElementContainer = new LogEntryWrappedElementContainer(this, this.getWrappedDomContainer());',
\r
2895 ' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
\r
2897 ' this.mainElementContainer = new LogEntryMainElementContainer(this, this.getMainDomContainer());',
\r
2898 ' this.elementContainers = [this.mainElementContainer];',
\r
2900 ' this.content = this.formattedMessage;',
\r
2901 ' this.rendered = true;',
\r
2904 ' setContent: function(content, wrappedContent) {',
\r
2905 ' if (content != this.content) {',
\r
2906 ' if (isIe && (content !== this.formattedMessage)) {',
\r
2907 ' content = content.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space',
\r
2909 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2910 ' this.elementContainers[i].setContent(content, wrappedContent);',
\r
2912 ' this.content = content;',
\r
2916 ' getSearchMatches: function() {',
\r
2917 ' var matches = [];',
\r
2920 ' var unwrappedEls = getElementsByClass(this.unwrappedElementContainer.mainDiv, "searchterm", "span");',
\r
2921 ' var wrappedEls = getElementsByClass(this.wrappedElementContainer.mainDiv, "searchterm", "span");',
\r
2922 ' for (i = 0, len = unwrappedEls.length; i < len; i++) {',
\r
2923 ' matches[i] = new Match(this.level, null, unwrappedEls[i], wrappedEls[i]);',
\r
2926 ' var els = getElementsByClass(this.mainElementContainer.mainDiv, "searchterm", "span");',
\r
2927 ' for (i = 0, len = els.length; i < len; i++) {',
\r
2928 ' matches[i] = new Match(this.level, els[i]);',
\r
2931 ' return matches;',
\r
2934 ' setSearchMatch: function(isMatch) {',
\r
2935 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2936 ' this.elementContainers[i].setSearchMatch(isMatch);',
\r
2940 ' clearSearch: function() {',
\r
2941 ' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
\r
2942 ' this.elementContainers[i].clearSearch();',
\r
2946 ' accept: function(visitor) {',
\r
2947 ' visitor.visitLogEntry(this);',
\r
2950 ' serialize: function(items) {',
\r
2951 ' items.push([LogItem.serializedItemKeys.LOG_ENTRY, this.level, this.formattedMessage]);',
\r
2955 ' /*----------------------------------------------------------------*/',
\r
2957 ' function LogItemVisitor() {',
\r
2960 ' LogItemVisitor.prototype = {',
\r
2961 ' visit: function(logItem) {',
\r
2964 ' visitParent: function(logItem) {',
\r
2965 ' if (logItem.group) {',
\r
2966 ' logItem.group.accept(this);',
\r
2970 ' visitChildren: function(logItem) {',
\r
2971 ' for (var i = 0, len = logItem.children.length; i < len; i++) {',
\r
2972 ' logItem.children[i].accept(this);',
\r
2976 ' visitLogEntry: function(logEntry) {',
\r
2977 ' this.visit(logEntry);',
\r
2980 ' visitSeparator: function(separator) {',
\r
2981 ' this.visit(separator);',
\r
2984 ' visitGroup: function(group) {',
\r
2985 ' this.visit(group);',
\r
2989 ' /*----------------------------------------------------------------*/',
\r
2991 ' function GroupFlattener() {',
\r
2992 ' this.logEntriesAndSeparators = [];',
\r
2995 ' GroupFlattener.prototype = new LogItemVisitor();',
\r
2997 ' GroupFlattener.prototype.visitGroup = function(group) {',
\r
2998 ' this.visitChildren(group);',
\r
3001 ' GroupFlattener.prototype.visitLogEntry = function(logEntry) {',
\r
3002 ' this.logEntriesAndSeparators.push(logEntry);',
\r
3005 ' GroupFlattener.prototype.visitSeparator = function(separator) {',
\r
3006 ' this.logEntriesAndSeparators.push(separator);',
\r
3009 ' /*----------------------------------------------------------------*/',
\r
3011 ' window.onload = function() {',
\r
3012 ' // Sort out document.domain',
\r
3013 ' if (location.search) {',
\r
3014 ' var queryBits = unescape(location.search).substr(1).split("&"), nameValueBits;',
\r
3015 ' for (var i = 0, len = queryBits.length; i < len; i++) {',
\r
3016 ' nameValueBits = queryBits[i].split("=");',
\r
3017 ' if (nameValueBits[0] == "log4javascript_domain") {',
\r
3018 ' document.domain = nameValueBits[1];',
\r
3024 ' // Create DOM objects',
\r
3025 ' logMainContainer = $("log");',
\r
3026 ' if (isIePre7) {',
\r
3027 ' addClass(logMainContainer, "oldIe");',
\r
3030 ' rootGroup = new Group("root", true);',
\r
3031 ' rootGroup.render();',
\r
3032 ' currentGroup = rootGroup;',
\r
3034 ' setCommandInputWidth();',
\r
3035 ' setLogContainerHeight();',
\r
3036 ' toggleLoggingEnabled();',
\r
3037 ' toggleSearchEnabled();',
\r
3038 ' toggleSearchFilter();',
\r
3039 ' toggleSearchHighlight();',
\r
3040 ' applyFilters();',
\r
3041 ' checkAllLevels();',
\r
3043 ' toggleNewestAtTop();',
\r
3044 ' toggleScrollToLatest();',
\r
3045 ' renderQueuedLogItems();',
\r
3046 ' loaded = true;',
\r
3047 ' $("command").value = "";',
\r
3048 ' $("command").autocomplete = "off";',
\r
3049 ' $("command").onkeydown = function(evt) {',
\r
3050 ' evt = getEvent(evt);',
\r
3051 ' if (evt.keyCode == 10 || evt.keyCode == 13) { // Return/Enter',
\r
3052 ' evalCommandLine();',
\r
3053 ' stopPropagation(evt);',
\r
3054 ' } else if (evt.keyCode == 27) { // Escape',
\r
3055 ' this.value = "";',
\r
3057 ' } else if (evt.keyCode == 38 && commandHistory.length > 0) { // Up',
\r
3058 ' currentCommandIndex = Math.max(0, currentCommandIndex - 1);',
\r
3059 ' this.value = commandHistory[currentCommandIndex];',
\r
3060 ' moveCaretToEnd(this);',
\r
3061 ' } else if (evt.keyCode == 40 && commandHistory.length > 0) { // Down',
\r
3062 ' currentCommandIndex = Math.min(commandHistory.length - 1, currentCommandIndex + 1);',
\r
3063 ' this.value = commandHistory[currentCommandIndex];',
\r
3064 ' moveCaretToEnd(this);',
\r
3068 ' // Prevent the keypress moving the caret in Firefox',
\r
3069 ' $("command").onkeypress = function(evt) {',
\r
3070 ' evt = getEvent(evt);',
\r
3071 ' if (evt.keyCode == 38 && commandHistory.length > 0 && evt.preventDefault) { // Up',
\r
3072 ' evt.preventDefault();',
\r
3076 ' // Prevent the keyup event blurring the input in Opera',
\r
3077 ' $("command").onkeyup = function(evt) {',
\r
3078 ' evt = getEvent(evt);',
\r
3079 ' if (evt.keyCode == 27 && evt.preventDefault) { // Up',
\r
3080 ' evt.preventDefault();',
\r
3085 ' // Add document keyboard shortcuts',
\r
3086 ' document.onkeydown = function keyEventHandler(evt) {',
\r
3087 ' evt = getEvent(evt);',
\r
3088 ' switch (evt.keyCode) {',
\r
3089 ' case 69: // Ctrl + shift + E: re-execute last command',
\r
3090 ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
\r
3091 ' evalLastCommand();',
\r
3092 ' cancelKeyEvent(evt);',
\r
3096 ' case 75: // Ctrl + shift + K: focus search',
\r
3097 ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
\r
3098 ' focusSearch();',
\r
3099 ' cancelKeyEvent(evt);',
\r
3103 ' case 40: // Ctrl + shift + down arrow: focus command line',
\r
3104 ' case 76: // Ctrl + shift + L: focus command line',
\r
3105 ' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
\r
3106 ' focusCommandLine();',
\r
3107 ' cancelKeyEvent(evt);',
\r
3114 ' // Workaround to make sure log div starts at the correct size',
\r
3115 ' setTimeout(setLogContainerHeight, 20);',
\r
3117 ' setShowCommandLine(showCommandLine);',
\r
3121 ' window.onunload = function() {',
\r
3122 ' if (mainWindowExists()) {',
\r
3123 ' appender.unload();',
\r
3125 ' appender = null;',
\r
3128 ' /*----------------------------------------------------------------*/',
\r
3130 ' function toggleLoggingEnabled() {',
\r
3131 ' setLoggingEnabled($("enableLogging").checked);',
\r
3134 ' function setLoggingEnabled(enable) {',
\r
3135 ' loggingEnabled = enable;',
\r
3138 ' var appender = null;',
\r
3140 ' function setAppender(appenderParam) {',
\r
3141 ' appender = appenderParam;',
\r
3144 ' function setShowCloseButton(showCloseButton) {',
\r
3145 ' $("closeButton").style.display = showCloseButton ? "inline" : "none";',
\r
3148 ' function setShowHideButton(showHideButton) {',
\r
3149 ' $("hideButton").style.display = showHideButton ? "inline" : "none";',
\r
3152 ' var newestAtTop = false;',
\r
3154 ' /*----------------------------------------------------------------*/',
\r
3156 ' function LogItemContentReverser() {',
\r
3159 ' LogItemContentReverser.prototype = new LogItemVisitor();',
\r
3161 ' LogItemContentReverser.prototype.visitGroup = function(group) {',
\r
3162 ' group.reverseChildren();',
\r
3163 ' this.visitChildren(group);',
\r
3166 ' /*----------------------------------------------------------------*/',
\r
3168 ' function setNewestAtTop(isNewestAtTop) {',
\r
3169 ' var oldNewestAtTop = newestAtTop;',
\r
3170 ' var i, iLen, j, jLen;',
\r
3171 ' newestAtTop = Boolean(isNewestAtTop);',
\r
3172 ' if (oldNewestAtTop != newestAtTop) {',
\r
3173 ' var visitor = new LogItemContentReverser();',
\r
3174 ' rootGroup.accept(visitor);',
\r
3176 ' // Reassemble the matches array',
\r
3177 ' if (currentSearch) {',
\r
3178 ' var currentMatch = currentSearch.matches[currentMatchIndex];',
\r
3179 ' var matchIndex = 0;',
\r
3180 ' var matches = [];',
\r
3181 ' var actOnLogEntry = function(logEntry) {',
\r
3182 ' var logEntryMatches = logEntry.getSearchMatches();',
\r
3183 ' for (j = 0, jLen = logEntryMatches.length; j < jLen; j++) {',
\r
3184 ' matches[matchIndex] = logEntryMatches[j];',
\r
3185 ' if (currentMatch && logEntryMatches[j].equals(currentMatch)) {',
\r
3186 ' currentMatchIndex = matchIndex;',
\r
3191 ' if (newestAtTop) {',
\r
3192 ' for (i = logEntries.length - 1; i >= 0; i--) {',
\r
3193 ' actOnLogEntry(logEntries[i]);',
\r
3196 ' for (i = 0, iLen = logEntries.length; i < iLen; i++) {',
\r
3197 ' actOnLogEntry(logEntries[i]);',
\r
3200 ' currentSearch.matches = matches;',
\r
3201 ' if (currentMatch) {',
\r
3202 ' currentMatch.setCurrent();',
\r
3204 ' } else if (scrollToLatest) {',
\r
3205 ' doScrollToLatest();',
\r
3208 ' $("newestAtTop").checked = isNewestAtTop;',
\r
3211 ' function toggleNewestAtTop() {',
\r
3212 ' var isNewestAtTop = $("newestAtTop").checked;',
\r
3213 ' setNewestAtTop(isNewestAtTop);',
\r
3216 ' var scrollToLatest = true;',
\r
3218 ' function setScrollToLatest(isScrollToLatest) {',
\r
3219 ' scrollToLatest = isScrollToLatest;',
\r
3220 ' if (scrollToLatest) {',
\r
3221 ' doScrollToLatest();',
\r
3223 ' $("scrollToLatest").checked = isScrollToLatest;',
\r
3226 ' function toggleScrollToLatest() {',
\r
3227 ' var isScrollToLatest = $("scrollToLatest").checked;',
\r
3228 ' setScrollToLatest(isScrollToLatest);',
\r
3231 ' function doScrollToLatest() {',
\r
3232 ' var l = logMainContainer;',
\r
3233 ' if (typeof l.scrollTop != "undefined") {',
\r
3234 ' if (newestAtTop) {',
\r
3235 ' l.scrollTop = 0;',
\r
3237 ' var latestLogEntry = l.lastChild;',
\r
3238 ' if (latestLogEntry) {',
\r
3239 ' l.scrollTop = l.scrollHeight;',
\r
3245 ' var closeIfOpenerCloses = true;',
\r
3247 ' function setCloseIfOpenerCloses(isCloseIfOpenerCloses) {',
\r
3248 ' closeIfOpenerCloses = isCloseIfOpenerCloses;',
\r
3251 ' var maxMessages = null;',
\r
3253 ' function setMaxMessages(max) {',
\r
3254 ' maxMessages = max;',
\r
3255 ' pruneLogEntries();',
\r
3258 ' var showCommandLine = false;',
\r
3260 ' function setShowCommandLine(isShowCommandLine) {',
\r
3261 ' showCommandLine = isShowCommandLine;',
\r
3263 ' $("commandLine").style.display = showCommandLine ? "block" : "none";',
\r
3264 ' setCommandInputWidth();',
\r
3265 ' setLogContainerHeight();',
\r
3269 ' function focusCommandLine() {',
\r
3271 ' $("command").focus();',
\r
3275 ' function focusSearch() {',
\r
3277 ' $("searchBox").focus();',
\r
3281 ' function getLogItems() {',
\r
3282 ' var items = [];',
\r
3283 ' for (var i = 0, len = logItems.length; i < len; i++) {',
\r
3284 ' logItems[i].serialize(items);',
\r
3289 ' function setLogItems(items) {',
\r
3290 ' var loggingReallyEnabled = loggingEnabled;',
\r
3291 ' // Temporarily turn logging on',
\r
3292 ' loggingEnabled = true;',
\r
3293 ' for (var i = 0, len = items.length; i < len; i++) {',
\r
3294 ' switch (items[i][0]) {',
\r
3295 ' case LogItem.serializedItemKeys.LOG_ENTRY:',
\r
3296 ' log(items[i][1], items[i][2]);',
\r
3298 ' case LogItem.serializedItemKeys.GROUP_START:',
\r
3299 ' group(items[i][1]);',
\r
3301 ' case LogItem.serializedItemKeys.GROUP_END:',
\r
3306 ' loggingEnabled = loggingReallyEnabled;',
\r
3309 ' function log(logLevel, formattedMessage) {',
\r
3310 ' if (loggingEnabled) {',
\r
3311 ' var logEntry = new LogEntry(logLevel, formattedMessage);',
\r
3312 ' logEntries.push(logEntry);',
\r
3313 ' logEntriesAndSeparators.push(logEntry);',
\r
3314 ' logItems.push(logEntry);',
\r
3315 ' currentGroup.addChild(logEntry);',
\r
3317 ' if (logQueuedEventsTimer !== null) {',
\r
3318 ' clearTimeout(logQueuedEventsTimer);',
\r
3320 ' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);',
\r
3321 ' unrenderedLogItemsExist = true;',
\r
3326 ' function renderQueuedLogItems() {',
\r
3327 ' logQueuedEventsTimer = null;',
\r
3328 ' var pruned = pruneLogEntries();',
\r
3330 ' // Render any unrendered log entries and apply the current search to them',
\r
3331 ' var initiallyHasMatches = currentSearch ? currentSearch.hasMatches() : false;',
\r
3332 ' for (var i = 0, len = logItems.length; i < len; i++) {',
\r
3333 ' if (!logItems[i].rendered) {',
\r
3334 ' logItems[i].render();',
\r
3335 ' logItems[i].appendToLog();',
\r
3336 ' if (currentSearch && (logItems[i] instanceof LogEntry)) {',
\r
3337 ' currentSearch.applyTo(logItems[i]);',
\r
3341 ' if (currentSearch) {',
\r
3343 ' if (currentSearch.hasVisibleMatches()) {',
\r
3344 ' if (currentMatchIndex === null) {',
\r
3345 ' setCurrentMatchIndex(0);',
\r
3347 ' displayMatches();',
\r
3349 ' displayNoMatches();',
\r
3351 ' } else if (!initiallyHasMatches && currentSearch.hasVisibleMatches()) {',
\r
3352 ' setCurrentMatchIndex(0);',
\r
3353 ' displayMatches();',
\r
3356 ' if (scrollToLatest) {',
\r
3357 ' doScrollToLatest();',
\r
3359 ' unrenderedLogItemsExist = false;',
\r
3362 ' function pruneLogEntries() {',
\r
3363 ' if ((maxMessages !== null) && (logEntriesAndSeparators.length > maxMessages)) {',
\r
3364 ' var numberToDelete = logEntriesAndSeparators.length - maxMessages;',
\r
3365 ' var prunedLogEntries = logEntriesAndSeparators.slice(0, numberToDelete);',
\r
3366 ' if (currentSearch) {',
\r
3367 ' currentSearch.removeMatches(prunedLogEntries);',
\r
3370 ' for (var i = 0; i < numberToDelete; i++) {',
\r
3371 ' group = logEntriesAndSeparators[i].group;',
\r
3372 ' array_remove(logItems, logEntriesAndSeparators[i]);',
\r
3373 ' array_remove(logEntries, logEntriesAndSeparators[i]);',
\r
3374 ' logEntriesAndSeparators[i].remove(true, true);',
\r
3375 ' if (group.children.length === 0 && group !== currentGroup && group !== rootGroup) {',
\r
3376 ' array_remove(logItems, group);',
\r
3377 ' group.remove(true, true);',
\r
3380 ' logEntriesAndSeparators = array_removeFromStart(logEntriesAndSeparators, numberToDelete);',
\r
3386 ' function group(name, startExpanded) {',
\r
3387 ' if (loggingEnabled) {',
\r
3388 ' initiallyExpanded = (typeof startExpanded === "undefined") ? true : Boolean(startExpanded);',
\r
3389 ' var newGroup = new Group(name, false, initiallyExpanded);',
\r
3390 ' currentGroup.addChild(newGroup);',
\r
3391 ' currentGroup = newGroup;',
\r
3392 ' logItems.push(newGroup);',
\r
3394 ' if (logQueuedEventsTimer !== null) {',
\r
3395 ' clearTimeout(logQueuedEventsTimer);',
\r
3397 ' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);',
\r
3398 ' unrenderedLogItemsExist = true;',
\r
3403 ' function groupEnd() {',
\r
3404 ' currentGroup = (currentGroup === rootGroup) ? rootGroup : currentGroup.group;',
\r
3407 ' function mainPageReloaded() {',
\r
3408 ' currentGroup = rootGroup;',
\r
3409 ' var separator = new Separator();',
\r
3410 ' logEntriesAndSeparators.push(separator);',
\r
3411 ' logItems.push(separator);',
\r
3412 ' currentGroup.addChild(separator);',
\r
3415 ' function closeWindow() {',
\r
3416 ' if (appender && mainWindowExists()) {',
\r
3417 ' appender.close(true);',
\r
3419 ' window.close();',
\r
3423 ' function hide() {',
\r
3424 ' if (appender && mainWindowExists()) {',
\r
3425 ' appender.hide();',
\r
3429 ' var mainWindow = window;',
\r
3430 ' var windowId = "log4javascriptConsoleWindow_" + new Date().getTime() + "_" + ("" + Math.random()).substr(2);',
\r
3432 ' function setMainWindow(win) {',
\r
3433 ' mainWindow = win;',
\r
3434 ' mainWindow[windowId] = window;',
\r
3435 ' // If this is a pop-up, poll the opener to see if it\'s closed',
\r
3436 ' if (opener && closeIfOpenerCloses) {',
\r
3441 ' function pollOpener() {',
\r
3442 ' if (closeIfOpenerCloses) {',
\r
3443 ' if (mainWindowExists()) {',
\r
3444 ' setTimeout(pollOpener, 500);',
\r
3446 ' closeWindow();',
\r
3451 ' function mainWindowExists() {',
\r
3453 ' return (mainWindow && !mainWindow.closed &&',
\r
3454 ' mainWindow[windowId] == window);',
\r
3455 ' } catch (ex) {}',
\r
3459 ' var logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];',
\r
3461 ' function getCheckBox(logLevel) {',
\r
3462 ' return $("switch_" + logLevel);',
\r
3465 ' function getIeWrappedLogContainer() {',
\r
3466 ' return $("log_wrapped");',
\r
3469 ' function getIeUnwrappedLogContainer() {',
\r
3470 ' return $("log_unwrapped");',
\r
3473 ' function applyFilters() {',
\r
3474 ' for (var i = 0; i < logLevels.length; i++) {',
\r
3475 ' if (getCheckBox(logLevels[i]).checked) {',
\r
3476 ' addClass(logMainContainer, logLevels[i]);',
\r
3478 ' removeClass(logMainContainer, logLevels[i]);',
\r
3481 ' updateSearchFromFilters();',
\r
3484 ' function toggleAllLevels() {',
\r
3485 ' var turnOn = $("switch_ALL").checked;',
\r
3486 ' for (var i = 0; i < logLevels.length; i++) {',
\r
3487 ' getCheckBox(logLevels[i]).checked = turnOn;',
\r
3489 ' addClass(logMainContainer, logLevels[i]);',
\r
3491 ' removeClass(logMainContainer, logLevels[i]);',
\r
3496 ' function checkAllLevels() {',
\r
3497 ' for (var i = 0; i < logLevels.length; i++) {',
\r
3498 ' if (!getCheckBox(logLevels[i]).checked) {',
\r
3499 ' getCheckBox("ALL").checked = false;',
\r
3503 ' getCheckBox("ALL").checked = true;',
\r
3506 ' function clearLog() {',
\r
3507 ' rootGroup.clear();',
\r
3508 ' currentGroup = rootGroup;',
\r
3509 ' logEntries = [];',
\r
3510 ' logItems = [];',
\r
3511 ' logEntriesAndSeparators = [];',
\r
3515 ' function toggleWrap() {',
\r
3516 ' var enable = $("wrap").checked;',
\r
3518 ' addClass(logMainContainer, "wrap");',
\r
3520 ' removeClass(logMainContainer, "wrap");',
\r
3522 ' refreshCurrentMatch();',
\r
3525 ' /* ------------------------------------------------------------------- */',
\r
3529 ' var searchTimer = null;',
\r
3531 ' function scheduleSearch() {',
\r
3533 ' clearTimeout(searchTimer);',
\r
3534 ' } catch (ex) {',
\r
3537 ' searchTimer = setTimeout(doSearch, 500);',
\r
3540 ' function Search(searchTerm, isRegex, searchRegex, isCaseSensitive) {',
\r
3541 ' this.searchTerm = searchTerm;',
\r
3542 ' this.isRegex = isRegex;',
\r
3543 ' this.searchRegex = searchRegex;',
\r
3544 ' this.isCaseSensitive = isCaseSensitive;',
\r
3545 ' this.matches = [];',
\r
3548 ' Search.prototype = {',
\r
3549 ' hasMatches: function() {',
\r
3550 ' return this.matches.length > 0;',
\r
3553 ' hasVisibleMatches: function() {',
\r
3554 ' if (this.hasMatches()) {',
\r
3555 ' for (var i = 0; i < this.matches.length; i++) {',
\r
3556 ' if (this.matches[i].isVisible()) {',
\r
3564 ' match: function(logEntry) {',
\r
3565 ' var entryText = String(logEntry.formattedMessage);',
\r
3566 ' var matchesSearch = false;',
\r
3567 ' if (this.isRegex) {',
\r
3568 ' matchesSearch = this.searchRegex.test(entryText);',
\r
3569 ' } else if (this.isCaseSensitive) {',
\r
3570 ' matchesSearch = (entryText.indexOf(this.searchTerm) > -1);',
\r
3572 ' matchesSearch = (entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1);',
\r
3574 ' return matchesSearch;',
\r
3577 ' getNextVisibleMatchIndex: function() {',
\r
3578 ' for (var i = currentMatchIndex + 1; i < this.matches.length; i++) {',
\r
3579 ' if (this.matches[i].isVisible()) {',
\r
3583 ' // Start again from the first match',
\r
3584 ' for (i = 0; i <= currentMatchIndex; i++) {',
\r
3585 ' if (this.matches[i].isVisible()) {',
\r
3592 ' getPreviousVisibleMatchIndex: function() {',
\r
3593 ' for (var i = currentMatchIndex - 1; i >= 0; i--) {',
\r
3594 ' if (this.matches[i].isVisible()) {',
\r
3598 ' // Start again from the last match',
\r
3599 ' for (var i = this.matches.length - 1; i >= currentMatchIndex; i--) {',
\r
3600 ' if (this.matches[i].isVisible()) {',
\r
3607 ' applyTo: function(logEntry) {',
\r
3608 ' var doesMatch = this.match(logEntry);',
\r
3609 ' if (doesMatch) {',
\r
3610 ' logEntry.group.expand();',
\r
3611 ' logEntry.setSearchMatch(true);',
\r
3612 ' var logEntryContent;',
\r
3613 ' var wrappedLogEntryContent;',
\r
3614 ' var searchTermReplacementStartTag = "<span class=\\\"searchterm\\\">";',
\r
3615 ' var searchTermReplacementEndTag = "<" + "/span>";',
\r
3616 ' var preTagName = isIe ? "pre" : "span";',
\r
3617 ' var preStartTag = "<" + preTagName + " class=\\\"pre\\\">";',
\r
3618 ' var preEndTag = "<" + "/" + preTagName + ">";',
\r
3619 ' var startIndex = 0;',
\r
3620 ' var searchIndex, matchedText, textBeforeMatch;',
\r
3621 ' if (this.isRegex) {',
\r
3622 ' var flags = this.isCaseSensitive ? "g" : "gi";',
\r
3623 ' var capturingRegex = new RegExp("(" + this.searchRegex.source + ")", flags);',
\r
3625 ' // Replace the search term with temporary tokens for the start and end tags',
\r
3626 ' var rnd = ("" + Math.random()).substr(2);',
\r
3627 ' var startToken = "%%s" + rnd + "%%";',
\r
3628 ' var endToken = "%%e" + rnd + "%%";',
\r
3629 ' logEntryContent = logEntry.formattedMessage.replace(capturingRegex, startToken + "$1" + endToken);',
\r
3631 ' // Escape the HTML to get rid of angle brackets',
\r
3632 ' logEntryContent = escapeHtml(logEntryContent);',
\r
3634 ' // Substitute the proper HTML back in for the search match',
\r
3636 ' var searchString = logEntryContent;',
\r
3637 ' logEntryContent = "";',
\r
3638 ' wrappedLogEntryContent = "";',
\r
3639 ' while ((searchIndex = searchString.indexOf(startToken, startIndex)) > -1) {',
\r
3640 ' var endTokenIndex = searchString.indexOf(endToken, searchIndex);',
\r
3641 ' matchedText = searchString.substring(searchIndex + startToken.length, endTokenIndex);',
\r
3642 ' textBeforeMatch = searchString.substring(startIndex, searchIndex);',
\r
3643 ' logEntryContent += preStartTag + textBeforeMatch + preEndTag;',
\r
3644 ' logEntryContent += searchTermReplacementStartTag + preStartTag + matchedText +',
\r
3645 ' preEndTag + searchTermReplacementEndTag;',
\r
3647 ' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +',
\r
3648 ' matchedText + searchTermReplacementEndTag;',
\r
3650 ' startIndex = endTokenIndex + endToken.length;',
\r
3652 ' logEntryContent += preStartTag + searchString.substr(startIndex) + preEndTag;',
\r
3654 ' wrappedLogEntryContent += searchString.substr(startIndex);',
\r
3657 ' logEntryContent = "";',
\r
3658 ' wrappedLogEntryContent = "";',
\r
3659 ' var searchTermReplacementLength = searchTermReplacementStartTag.length +',
\r
3660 ' this.searchTerm.length + searchTermReplacementEndTag.length;',
\r
3661 ' var searchTermLength = this.searchTerm.length;',
\r
3662 ' var searchTermLowerCase = this.searchTerm.toLowerCase();',
\r
3663 ' var logTextLowerCase = logEntry.formattedMessage.toLowerCase();',
\r
3664 ' while ((searchIndex = logTextLowerCase.indexOf(searchTermLowerCase, startIndex)) > -1) {',
\r
3665 ' matchedText = escapeHtml(logEntry.formattedMessage.substr(searchIndex, this.searchTerm.length));',
\r
3666 ' textBeforeMatch = escapeHtml(logEntry.formattedMessage.substring(startIndex, searchIndex));',
\r
3667 ' var searchTermReplacement = searchTermReplacementStartTag +',
\r
3668 ' preStartTag + matchedText + preEndTag + searchTermReplacementEndTag;',
\r
3669 ' logEntryContent += preStartTag + textBeforeMatch + preEndTag + searchTermReplacement;',
\r
3671 ' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +',
\r
3672 ' matchedText + searchTermReplacementEndTag;',
\r
3674 ' startIndex = searchIndex + searchTermLength;',
\r
3676 ' var textAfterLastMatch = escapeHtml(logEntry.formattedMessage.substr(startIndex));',
\r
3677 ' logEntryContent += preStartTag + textAfterLastMatch + preEndTag;',
\r
3679 ' wrappedLogEntryContent += textAfterLastMatch;',
\r
3682 ' logEntry.setContent(logEntryContent, wrappedLogEntryContent);',
\r
3683 ' var logEntryMatches = logEntry.getSearchMatches();',
\r
3684 ' this.matches = this.matches.concat(logEntryMatches);',
\r
3686 ' logEntry.setSearchMatch(false);',
\r
3687 ' logEntry.setContent(logEntry.formattedMessage, logEntry.formattedMessage);',
\r
3689 ' return doesMatch;',
\r
3692 ' removeMatches: function(logEntries) {',
\r
3693 ' var matchesToRemoveCount = 0;',
\r
3694 ' var currentMatchRemoved = false;',
\r
3695 ' var matchesToRemove = [];',
\r
3696 ' var i, iLen, j, jLen;',
\r
3698 ' // Establish the list of matches to be removed',
\r
3699 ' for (i = 0, iLen = this.matches.length; i < iLen; i++) {',
\r
3700 ' for (j = 0, jLen = logEntries.length; j < jLen; j++) {',
\r
3701 ' if (this.matches[i].belongsTo(logEntries[j])) {',
\r
3702 ' matchesToRemove.push(this.matches[i]);',
\r
3703 ' if (i === currentMatchIndex) {',
\r
3704 ' currentMatchRemoved = true;',
\r
3710 ' // Set the new current match index if the current match has been deleted',
\r
3711 ' // This will be the first match that appears after the first log entry being',
\r
3712 ' // deleted, if one exists; otherwise, it\'s the first match overall',
\r
3713 ' var newMatch = currentMatchRemoved ? null : this.matches[currentMatchIndex];',
\r
3714 ' if (currentMatchRemoved) {',
\r
3715 ' for (i = currentMatchIndex, iLen = this.matches.length; i < iLen; i++) {',
\r
3716 ' if (this.matches[i].isVisible() && !array_contains(matchesToRemove, this.matches[i])) {',
\r
3717 ' newMatch = this.matches[i];',
\r
3723 ' // Remove the matches',
\r
3724 ' for (i = 0, iLen = matchesToRemove.length; i < iLen; i++) {',
\r
3725 ' array_remove(this.matches, matchesToRemove[i]);',
\r
3726 ' matchesToRemove[i].remove();',
\r
3729 ' // Set the new match, if one exists',
\r
3730 ' if (this.hasVisibleMatches()) {',
\r
3731 ' if (newMatch === null) {',
\r
3732 ' setCurrentMatchIndex(0);',
\r
3734 ' // Get the index of the new match',
\r
3735 ' var newMatchIndex = 0;',
\r
3736 ' for (i = 0, iLen = this.matches.length; i < iLen; i++) {',
\r
3737 ' if (newMatch === this.matches[i]) {',
\r
3738 ' newMatchIndex = i;',
\r
3742 ' setCurrentMatchIndex(newMatchIndex);',
\r
3745 ' currentMatchIndex = null;',
\r
3746 ' displayNoMatches();',
\r
3751 ' function getPageOffsetTop(el, container) {',
\r
3752 ' var currentEl = el;',
\r
3754 ' while (currentEl && currentEl != container) {',
\r
3755 ' y += currentEl.offsetTop;',
\r
3756 ' currentEl = currentEl.offsetParent;',
\r
3761 ' function scrollIntoView(el) {',
\r
3762 ' var logContainer = logMainContainer;',
\r
3763 ' // Check if the whole width of the element is visible and centre if not',
\r
3764 ' if (!$("wrap").checked) {',
\r
3765 ' var logContainerLeft = logContainer.scrollLeft;',
\r
3766 ' var logContainerRight = logContainerLeft + logContainer.offsetWidth;',
\r
3767 ' var elLeft = el.offsetLeft;',
\r
3768 ' var elRight = elLeft + el.offsetWidth;',
\r
3769 ' if (elLeft < logContainerLeft || elRight > logContainerRight) {',
\r
3770 ' logContainer.scrollLeft = elLeft - (logContainer.offsetWidth - el.offsetWidth) / 2;',
\r
3773 ' // Check if the whole height of the element is visible and centre if not',
\r
3774 ' var logContainerTop = logContainer.scrollTop;',
\r
3775 ' var logContainerBottom = logContainerTop + logContainer.offsetHeight;',
\r
3776 ' var elTop = getPageOffsetTop(el) - getToolBarsHeight();',
\r
3777 ' var elBottom = elTop + el.offsetHeight;',
\r
3778 ' if (elTop < logContainerTop || elBottom > logContainerBottom) {',
\r
3779 ' logContainer.scrollTop = elTop - (logContainer.offsetHeight - el.offsetHeight) / 2;',
\r
3783 ' function Match(logEntryLevel, spanInMainDiv, spanInUnwrappedPre, spanInWrappedDiv) {',
\r
3784 ' this.logEntryLevel = logEntryLevel;',
\r
3785 ' this.spanInMainDiv = spanInMainDiv;',
\r
3787 ' this.spanInUnwrappedPre = spanInUnwrappedPre;',
\r
3788 ' this.spanInWrappedDiv = spanInWrappedDiv;',
\r
3790 ' this.mainSpan = isIe ? spanInUnwrappedPre : spanInMainDiv;',
\r
3793 ' Match.prototype = {',
\r
3794 ' equals: function(match) {',
\r
3795 ' return this.mainSpan === match.mainSpan;',
\r
3798 ' setCurrent: function() {',
\r
3800 ' addClass(this.spanInUnwrappedPre, "currentmatch");',
\r
3801 ' addClass(this.spanInWrappedDiv, "currentmatch");',
\r
3802 ' // Scroll the visible one into view',
\r
3803 ' var elementToScroll = $("wrap").checked ? this.spanInWrappedDiv : this.spanInUnwrappedPre;',
\r
3804 ' scrollIntoView(elementToScroll);',
\r
3806 ' addClass(this.spanInMainDiv, "currentmatch");',
\r
3807 ' scrollIntoView(this.spanInMainDiv);',
\r
3811 ' belongsTo: function(logEntry) {',
\r
3813 ' return isDescendant(this.spanInUnwrappedPre, logEntry.unwrappedPre);',
\r
3815 ' return isDescendant(this.spanInMainDiv, logEntry.mainDiv);',
\r
3819 ' setNotCurrent: function() {',
\r
3821 ' removeClass(this.spanInUnwrappedPre, "currentmatch");',
\r
3822 ' removeClass(this.spanInWrappedDiv, "currentmatch");',
\r
3824 ' removeClass(this.spanInMainDiv, "currentmatch");',
\r
3828 ' isOrphan: function() {',
\r
3829 ' return isOrphan(this.mainSpan);',
\r
3832 ' isVisible: function() {',
\r
3833 ' return getCheckBox(this.logEntryLevel).checked;',
\r
3836 ' remove: function() {',
\r
3838 ' this.spanInUnwrappedPre = null;',
\r
3839 ' this.spanInWrappedDiv = null;',
\r
3841 ' this.spanInMainDiv = null;',
\r
3846 ' var currentSearch = null;',
\r
3847 ' var currentMatchIndex = null;',
\r
3849 ' function doSearch() {',
\r
3850 ' var searchBox = $("searchBox");',
\r
3851 ' var searchTerm = searchBox.value;',
\r
3852 ' var isRegex = $("searchRegex").checked;',
\r
3853 ' var isCaseSensitive = $("searchCaseSensitive").checked;',
\r
3856 ' if (searchTerm === "") {',
\r
3857 ' $("searchReset").disabled = true;',
\r
3858 ' $("searchNav").style.display = "none";',
\r
3859 ' removeClass(document.body, "searching");',
\r
3860 ' removeClass(searchBox, "hasmatches");',
\r
3861 ' removeClass(searchBox, "nomatches");',
\r
3862 ' for (i = 0; i < logEntries.length; i++) {',
\r
3863 ' logEntries[i].clearSearch();',
\r
3864 ' logEntries[i].setContent(logEntries[i].formattedMessage, logEntries[i].formattedMessage);',
\r
3866 ' currentSearch = null;',
\r
3867 ' setLogContainerHeight();',
\r
3869 ' $("searchReset").disabled = false;',
\r
3870 ' $("searchNav").style.display = "block";',
\r
3871 ' var searchRegex;',
\r
3872 ' var regexValid;',
\r
3873 ' if (isRegex) {',
\r
3875 ' searchRegex = isCaseSensitive ? new RegExp(searchTerm, "g") : new RegExp(searchTerm, "gi");',
\r
3876 ' regexValid = true;',
\r
3877 ' replaceClass(searchBox, "validregex", "invalidregex");',
\r
3878 ' searchBox.title = "Valid regex";',
\r
3879 ' } catch (ex) {',
\r
3880 ' regexValid = false;',
\r
3881 ' replaceClass(searchBox, "invalidregex", "validregex");',
\r
3882 ' searchBox.title = "Invalid regex: " + (ex.message ? ex.message : (ex.description ? ex.description : "unknown error"));',
\r
3886 ' searchBox.title = "";',
\r
3887 ' removeClass(searchBox, "validregex");',
\r
3888 ' removeClass(searchBox, "invalidregex");',
\r
3890 ' addClass(document.body, "searching");',
\r
3891 ' currentSearch = new Search(searchTerm, isRegex, searchRegex, isCaseSensitive);',
\r
3892 ' for (i = 0; i < logEntries.length; i++) {',
\r
3893 ' currentSearch.applyTo(logEntries[i]);',
\r
3895 ' setLogContainerHeight();',
\r
3897 ' // Highlight the first search match',
\r
3898 ' if (currentSearch.hasVisibleMatches()) {',
\r
3899 ' setCurrentMatchIndex(0);',
\r
3900 ' displayMatches();',
\r
3902 ' displayNoMatches();',
\r
3907 ' function updateSearchFromFilters() {',
\r
3908 ' if (currentSearch) {',
\r
3909 ' if (currentSearch.hasMatches()) {',
\r
3910 ' if (currentMatchIndex === null) {',
\r
3911 ' currentMatchIndex = 0;',
\r
3913 ' var currentMatch = currentSearch.matches[currentMatchIndex];',
\r
3914 ' if (currentMatch.isVisible()) {',
\r
3915 ' displayMatches();',
\r
3916 ' setCurrentMatchIndex(currentMatchIndex);',
\r
3918 ' currentMatch.setNotCurrent();',
\r
3919 ' // Find the next visible match, if one exists',
\r
3920 ' var nextVisibleMatchIndex = currentSearch.getNextVisibleMatchIndex();',
\r
3921 ' if (nextVisibleMatchIndex > -1) {',
\r
3922 ' setCurrentMatchIndex(nextVisibleMatchIndex);',
\r
3923 ' displayMatches();',
\r
3925 ' displayNoMatches();',
\r
3929 ' displayNoMatches();',
\r
3934 ' function refreshCurrentMatch() {',
\r
3935 ' if (currentSearch && currentSearch.hasVisibleMatches()) {',
\r
3936 ' setCurrentMatchIndex(currentMatchIndex);',
\r
3940 ' function displayMatches() {',
\r
3941 ' replaceClass($("searchBox"), "hasmatches", "nomatches");',
\r
3942 ' $("searchBox").title = "" + currentSearch.matches.length + " matches found";',
\r
3943 ' $("searchNav").style.display = "block";',
\r
3944 ' setLogContainerHeight();',
\r
3947 ' function displayNoMatches() {',
\r
3948 ' replaceClass($("searchBox"), "nomatches", "hasmatches");',
\r
3949 ' $("searchBox").title = "No matches found";',
\r
3950 ' $("searchNav").style.display = "none";',
\r
3951 ' setLogContainerHeight();',
\r
3954 ' function toggleSearchEnabled(enable) {',
\r
3955 ' enable = (typeof enable == "undefined") ? !$("searchDisable").checked : enable;',
\r
3956 ' $("searchBox").disabled = !enable;',
\r
3957 ' $("searchReset").disabled = !enable;',
\r
3958 ' $("searchRegex").disabled = !enable;',
\r
3959 ' $("searchNext").disabled = !enable;',
\r
3960 ' $("searchPrevious").disabled = !enable;',
\r
3961 ' $("searchCaseSensitive").disabled = !enable;',
\r
3962 ' $("searchNav").style.display = (enable && ($("searchBox").value !== "") &&',
\r
3963 ' currentSearch && currentSearch.hasVisibleMatches()) ?',
\r
3964 ' "block" : "none";',
\r
3966 ' removeClass($("search"), "greyedout");',
\r
3967 ' addClass(document.body, "searching");',
\r
3968 ' if ($("searchHighlight").checked) {',
\r
3969 ' addClass(logMainContainer, "searchhighlight");',
\r
3971 ' removeClass(logMainContainer, "searchhighlight");',
\r
3973 ' if ($("searchFilter").checked) {',
\r
3974 ' addClass(logMainContainer, "searchfilter");',
\r
3976 ' removeClass(logMainContainer, "searchfilter");',
\r
3978 ' $("searchDisable").checked = !enable;',
\r
3980 ' addClass($("search"), "greyedout");',
\r
3981 ' removeClass(document.body, "searching");',
\r
3982 ' removeClass(logMainContainer, "searchhighlight");',
\r
3983 ' removeClass(logMainContainer, "searchfilter");',
\r
3985 ' setLogContainerHeight();',
\r
3988 ' function toggleSearchFilter() {',
\r
3989 ' var enable = $("searchFilter").checked;',
\r
3991 ' addClass(logMainContainer, "searchfilter");',
\r
3993 ' removeClass(logMainContainer, "searchfilter");',
\r
3995 ' refreshCurrentMatch();',
\r
3998 ' function toggleSearchHighlight() {',
\r
3999 ' var enable = $("searchHighlight").checked;',
\r
4001 ' addClass(logMainContainer, "searchhighlight");',
\r
4003 ' removeClass(logMainContainer, "searchhighlight");',
\r
4007 ' function clearSearch() {',
\r
4008 ' $("searchBox").value = "";',
\r
4012 ' function searchNext() {',
\r
4013 ' if (currentSearch !== null && currentMatchIndex !== null) {',
\r
4014 ' currentSearch.matches[currentMatchIndex].setNotCurrent();',
\r
4015 ' var nextMatchIndex = currentSearch.getNextVisibleMatchIndex();',
\r
4016 ' if (nextMatchIndex > currentMatchIndex || confirm("Reached the end of the page. Start from the top?")) {',
\r
4017 ' setCurrentMatchIndex(nextMatchIndex);',
\r
4022 ' function searchPrevious() {',
\r
4023 ' if (currentSearch !== null && currentMatchIndex !== null) {',
\r
4024 ' currentSearch.matches[currentMatchIndex].setNotCurrent();',
\r
4025 ' var previousMatchIndex = currentSearch.getPreviousVisibleMatchIndex();',
\r
4026 ' if (previousMatchIndex < currentMatchIndex || confirm("Reached the start of the page. Continue from the bottom?")) {',
\r
4027 ' setCurrentMatchIndex(previousMatchIndex);',
\r
4032 ' function setCurrentMatchIndex(index) {',
\r
4033 ' currentMatchIndex = index;',
\r
4034 ' currentSearch.matches[currentMatchIndex].setCurrent();',
\r
4037 ' /* ------------------------------------------------------------------------- */',
\r
4039 ' // CSS Utilities',
\r
4041 ' function addClass(el, cssClass) {',
\r
4042 ' if (!hasClass(el, cssClass)) {',
\r
4043 ' if (el.className) {',
\r
4044 ' el.className += " " + cssClass;',
\r
4046 ' el.className = cssClass;',
\r
4051 ' function hasClass(el, cssClass) {',
\r
4052 ' if (el.className) {',
\r
4053 ' var classNames = el.className.split(" ");',
\r
4054 ' return array_contains(classNames, cssClass);',
\r
4059 ' function removeClass(el, cssClass) {',
\r
4060 ' if (hasClass(el, cssClass)) {',
\r
4061 ' // Rebuild the className property',
\r
4062 ' var existingClasses = el.className.split(" ");',
\r
4063 ' var newClasses = [];',
\r
4064 ' for (var i = 0, len = existingClasses.length; i < len; i++) {',
\r
4065 ' if (existingClasses[i] != cssClass) {',
\r
4066 ' newClasses[newClasses.length] = existingClasses[i];',
\r
4069 ' el.className = newClasses.join(" ");',
\r
4073 ' function replaceClass(el, newCssClass, oldCssClass) {',
\r
4074 ' removeClass(el, oldCssClass);',
\r
4075 ' addClass(el, newCssClass);',
\r
4078 ' /* ------------------------------------------------------------------------- */',
\r
4080 ' // Other utility functions',
\r
4082 ' function getElementsByClass(el, cssClass, tagName) {',
\r
4083 ' var elements = el.getElementsByTagName(tagName);',
\r
4084 ' var matches = [];',
\r
4085 ' for (var i = 0, len = elements.length; i < len; i++) {',
\r
4086 ' if (hasClass(elements[i], cssClass)) {',
\r
4087 ' matches.push(elements[i]);',
\r
4090 ' return matches;',
\r
4093 ' // Syntax borrowed from Prototype library',
\r
4094 ' function $(id) {',
\r
4095 ' return document.getElementById(id);',
\r
4098 ' function isDescendant(node, ancestorNode) {',
\r
4099 ' while (node != null) {',
\r
4100 ' if (node === ancestorNode) {',
\r
4103 ' node = node.parentNode;',
\r
4108 ' function isOrphan(node) {',
\r
4109 ' var currentNode = node;',
\r
4110 ' while (currentNode) {',
\r
4111 ' if (currentNode == document.body) {',
\r
4114 ' currentNode = currentNode.parentNode;',
\r
4119 ' function escapeHtml(str) {',
\r
4120 ' return str.replace(/&/g, "&").replace(/[<]/g, "<").replace(/>/g, ">");',
\r
4123 ' function getWindowWidth() {',
\r
4124 ' if (window.innerWidth) {',
\r
4125 ' return window.innerWidth;',
\r
4126 ' } else if (document.documentElement && document.documentElement.clientWidth) {',
\r
4127 ' return document.documentElement.clientWidth;',
\r
4128 ' } else if (document.body) {',
\r
4129 ' return document.body.clientWidth;',
\r
4134 ' function getWindowHeight() {',
\r
4135 ' if (window.innerHeight) {',
\r
4136 ' return window.innerHeight;',
\r
4137 ' } else if (document.documentElement && document.documentElement.clientHeight) {',
\r
4138 ' return document.documentElement.clientHeight;',
\r
4139 ' } else if (document.body) {',
\r
4140 ' return document.body.clientHeight;',
\r
4145 ' function getToolBarsHeight() {',
\r
4146 ' return $("switches").offsetHeight;',
\r
4149 ' function getChromeHeight() {',
\r
4150 ' var height = getToolBarsHeight();',
\r
4151 ' if (showCommandLine) {',
\r
4152 ' height += $("commandLine").offsetHeight;',
\r
4154 ' return height;',
\r
4157 ' function setLogContainerHeight() {',
\r
4158 ' if (logMainContainer) {',
\r
4159 ' var windowHeight = getWindowHeight();',
\r
4160 ' $("body").style.height = getWindowHeight() + "px";',
\r
4161 ' logMainContainer.style.height = "" +',
\r
4162 ' Math.max(0, windowHeight - getChromeHeight()) + "px";',
\r
4166 ' function setCommandInputWidth() {',
\r
4167 ' if (showCommandLine) {',
\r
4168 ' $("command").style.width = "" + Math.max(0, $("commandLineContainer").offsetWidth -',
\r
4169 ' ($("evaluateButton").offsetWidth + 13)) + "px";',
\r
4173 ' window.onresize = function() {',
\r
4174 ' setCommandInputWidth();',
\r
4175 ' setLogContainerHeight();',
\r
4178 ' if (!Array.prototype.push) {',
\r
4179 ' Array.prototype.push = function() {',
\r
4180 ' for (var i = 0, len = arguments.length; i < len; i++){',
\r
4181 ' this[this.length] = arguments[i];',
\r
4183 ' return this.length;',
\r
4187 ' if (!Array.prototype.pop) {',
\r
4188 ' Array.prototype.pop = function() {',
\r
4189 ' if (this.length > 0) {',
\r
4190 ' var val = this[this.length - 1];',
\r
4191 ' this.length = this.length - 1;',
\r
4197 ' if (!Array.prototype.shift) {',
\r
4198 ' Array.prototype.shift = function() {',
\r
4199 ' if (this.length > 0) {',
\r
4200 ' var firstItem = this[0];',
\r
4201 ' for (var i = 0, len = this.length - 1; i < len; i++) {',
\r
4202 ' this[i] = this[i + 1];',
\r
4204 ' this.length = this.length - 1;',
\r
4205 ' return firstItem;',
\r
4210 ' if (!Array.prototype.splice) {',
\r
4211 ' Array.prototype.splice = function(startIndex, deleteCount) {',
\r
4212 ' var itemsAfterDeleted = this.slice(startIndex + deleteCount);',
\r
4213 ' var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);',
\r
4214 ' this.length = startIndex;',
\r
4215 ' // Copy the arguments into a proper Array object',
\r
4216 ' var argumentsArray = [];',
\r
4217 ' for (var i = 0, len = arguments.length; i < len; i++) {',
\r
4218 ' argumentsArray[i] = arguments[i];',
\r
4220 ' var itemsToAppend = (argumentsArray.length > 2) ?',
\r
4221 ' itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;',
\r
4222 ' for (i = 0, len = itemsToAppend.length; i < len; i++) {',
\r
4223 ' this.push(itemsToAppend[i]);',
\r
4225 ' return itemsDeleted;',
\r
4229 ' function array_remove(arr, val) {',
\r
4230 ' var index = -1;',
\r
4231 ' for (var i = 0, len = arr.length; i < len; i++) {',
\r
4232 ' if (arr[i] === val) {',
\r
4237 ' if (index >= 0) {',
\r
4238 ' arr.splice(index, 1);',
\r
4245 ' function array_removeFromStart(array, numberToRemove) {',
\r
4246 ' if (Array.prototype.splice) {',
\r
4247 ' array.splice(0, numberToRemove);',
\r
4249 ' for (var i = numberToRemove, len = array.length; i < len; i++) {',
\r
4250 ' array[i - numberToRemove] = array[i];',
\r
4252 ' array.length = array.length - numberToRemove;',
\r
4257 ' function array_contains(arr, val) {',
\r
4258 ' for (var i = 0, len = arr.length; i < len; i++) {',
\r
4259 ' if (arr[i] == val) {',
\r
4266 ' function getErrorMessage(ex) {',
\r
4267 ' if (ex.message) {',
\r
4268 ' return ex.message;',
\r
4269 ' } else if (ex.description) {',
\r
4270 ' return ex.description;',
\r
4272 ' return "" + ex;',
\r
4275 ' function moveCaretToEnd(input) {',
\r
4276 ' if (input.setSelectionRange) {',
\r
4277 ' input.focus();',
\r
4278 ' var length = input.value.length;',
\r
4279 ' input.setSelectionRange(length, length);',
\r
4280 ' } else if (input.createTextRange) {',
\r
4281 ' var range = input.createTextRange();',
\r
4282 ' range.collapse(false);',
\r
4283 ' range.select();',
\r
4285 ' input.focus();',
\r
4288 ' function stopPropagation(evt) {',
\r
4289 ' if (evt.stopPropagation) {',
\r
4290 ' evt.stopPropagation();',
\r
4291 ' } else if (typeof evt.cancelBubble != "undefined") {',
\r
4292 ' evt.cancelBubble = true;',
\r
4296 ' function getEvent(evt) {',
\r
4297 ' return evt ? evt : event;',
\r
4300 ' function getTarget(evt) {',
\r
4301 ' return evt.target ? evt.target : evt.srcElement;',
\r
4304 ' function getRelatedTarget(evt) {',
\r
4305 ' if (evt.relatedTarget) {',
\r
4306 ' return evt.relatedTarget;',
\r
4307 ' } else if (evt.srcElement) {',
\r
4308 ' switch(evt.type) {',
\r
4309 ' case "mouseover":',
\r
4310 ' return evt.fromElement;',
\r
4311 ' case "mouseout":',
\r
4312 ' return evt.toElement;',
\r
4314 ' return evt.srcElement;',
\r
4319 ' function cancelKeyEvent(evt) {',
\r
4320 ' evt.returnValue = false;',
\r
4321 ' stopPropagation(evt);',
\r
4324 ' function evalCommandLine() {',
\r
4325 ' var expr = $("command").value;',
\r
4326 ' evalCommand(expr);',
\r
4327 ' $("command").value = "";',
\r
4330 ' function evalLastCommand() {',
\r
4331 ' if (lastCommand != null) {',
\r
4332 ' evalCommand(lastCommand);',
\r
4336 ' var lastCommand = null;',
\r
4337 ' var commandHistory = [];',
\r
4338 ' var currentCommandIndex = 0;',
\r
4340 ' function evalCommand(expr) {',
\r
4341 ' if (appender) {',
\r
4342 ' appender.evalCommandAndAppend(expr);',
\r
4344 ' var prefix = ">>> " + expr + "\\r\\n";',
\r
4346 ' log("INFO", prefix + eval(expr));',
\r
4347 ' } catch (ex) {',
\r
4348 ' log("ERROR", prefix + "Error: " + getErrorMessage(ex));',
\r
4351 ' // Update command history',
\r
4352 ' if (expr != commandHistory[commandHistory.length - 1]) {',
\r
4353 ' commandHistory.push(expr);',
\r
4354 ' // Update the appender',
\r
4355 ' if (appender) {',
\r
4356 ' appender.storeCommandHistory(commandHistory);',
\r
4359 ' currentCommandIndex = (expr == commandHistory[currentCommandIndex]) ? currentCommandIndex + 1 : commandHistory.length;',
\r
4360 ' lastCommand = expr;',
\r
4364 ' <style type="text/css">',
\r
4366 ' background-color: white;',
\r
4370 ' font-family: tahoma, verdana, arial, helvetica, sans-serif;',
\r
4371 ' overflow: hidden;',
\r
4374 ' div#switchesContainer input {',
\r
4375 ' margin-bottom: 0;',
\r
4379 ' border-top: solid #ffffff 1px;',
\r
4380 ' border-bottom: solid #aca899 1px;',
\r
4381 ' background-color: #f1efe7;',
\r
4382 ' padding: 3px 5px;',
\r
4383 ' font-size: 68.75%;',
\r
4386 ' div.toolbar, div#search input {',
\r
4387 ' font-family: tahoma, verdana, arial, helvetica, sans-serif;',
\r
4390 ' div.toolbar input.button {',
\r
4391 ' padding: 0 5px;',
\r
4392 ' font-size: 100%;',
\r
4395 ' div.toolbar input.hidden {',
\r
4396 ' display: none;',
\r
4399 ' div#switches input#clearButton {',
\r
4400 ' margin-left: 20px;',
\r
4403 ' div#levels label {',
\r
4404 ' font-weight: bold;',
\r
4407 ' div#levels label, div#options label {',
\r
4408 ' margin-right: 5px;',
\r
4411 ' div#levels label#wrapLabel {',
\r
4412 ' font-weight: normal;',
\r
4415 ' div#search label {',
\r
4416 ' margin-right: 10px;',
\r
4419 ' div#search label.searchboxlabel {',
\r
4420 ' margin-right: 0;',
\r
4423 ' div#search input {',
\r
4424 ' font-size: 100%;',
\r
4427 ' div#search input.validregex {',
\r
4431 ' div#search input.invalidregex {',
\r
4435 ' div#search input.nomatches {',
\r
4437 ' background-color: #ff6666;',
\r
4440 ' div#search input.nomatches {',
\r
4442 ' background-color: #ff6666;',
\r
4445 ' div#searchNav {',
\r
4446 ' display: none;',
\r
4449 ' div#commandLine {',
\r
4450 ' display: none;',
\r
4453 ' div#commandLine input#command {',
\r
4454 ' font-size: 100%;',
\r
4455 ' font-family: Courier New, Courier;',
\r
4458 ' div#commandLine input#evaluateButton {',
\r
4462 ' color: gray !important;',
\r
4463 ' border-color: gray !important;',
\r
4466 ' *.greyedout *.alwaysenabled { color: black; }',
\r
4468 ' *.unselectable {',
\r
4469 ' -khtml-user-select: none;',
\r
4470 ' -moz-user-select: none;',
\r
4471 ' user-select: none;',
\r
4475 ' font-family: Courier New, Courier;',
\r
4476 ' font-size: 75%;',
\r
4478 ' overflow: auto;',
\r
4480 ' position: relative;',
\r
4484 ' border-color: #cccccc;',
\r
4485 ' border-style: solid;',
\r
4486 ' border-width: 1px 0 1px 1px;',
\r
4487 ' overflow: visible;',
\r
4490 ' div.oldIe div.group, div.oldIe div.group *, div.oldIe *.logentry {',
\r
4494 ' div.group div.groupheading span.expander {',
\r
4495 ' border: solid black 1px;',
\r
4496 ' font-family: Courier New, Courier;',
\r
4497 ' font-size: 0.833em;',
\r
4498 ' background-color: #eeeeee;',
\r
4499 ' position: relative;',
\r
4502 ' padding: 0 2px;',
\r
4503 ' cursor: pointer;',
\r
4508 ' div.group div.groupcontent {',
\r
4509 ' margin-left: 10px;',
\r
4510 ' padding-bottom: 2px;',
\r
4511 ' overflow: visible;',
\r
4514 ' div.group div.expanded {',
\r
4515 ' display: block;',
\r
4518 ' div.group div.collapsed {',
\r
4519 ' display: none;',
\r
4523 ' overflow: visible;',
\r
4524 ' display: none;',
\r
4525 ' white-space: pre;',
\r
4529 ' white-space: pre;',
\r
4532 ' pre.unwrapped {',
\r
4533 ' display: inline !important;',
\r
4536 ' pre.unwrapped pre.pre, div.wrapped pre.pre {',
\r
4537 ' display: inline;',
\r
4540 ' div.wrapped pre.pre {',
\r
4541 ' white-space: normal;',
\r
4545 ' display: none;',
\r
4548 ' body.searching *.logentry span.currentmatch {',
\r
4549 ' color: white !important;',
\r
4550 ' background-color: green !important;',
\r
4553 ' body.searching div.searchhighlight *.logentry span.searchterm {',
\r
4555 ' background-color: yellow;',
\r
4558 ' div.wrap *.logentry {',
\r
4559 ' white-space: normal !important;',
\r
4560 ' border-width: 0 0 1px 0;',
\r
4561 ' border-color: #dddddd;',
\r
4562 ' border-style: dotted;',
\r
4565 ' div.wrap #log_wrapped, #log_unwrapped {',
\r
4566 ' display: block;',
\r
4569 ' div.wrap #log_unwrapped, #log_wrapped {',
\r
4570 ' display: none;',
\r
4573 ' div.wrap *.logentry span.pre {',
\r
4574 ' overflow: visible;',
\r
4575 ' white-space: normal;',
\r
4578 ' div.wrap *.logentry pre.unwrapped {',
\r
4579 ' display: none;',
\r
4582 ' div.wrap *.logentry span.wrapped {',
\r
4583 ' display: inline;',
\r
4586 ' div.searchfilter *.searchnonmatch {',
\r
4587 ' display: none !important;',
\r
4590 ' div#log *.TRACE, label#label_TRACE {',
\r
4591 ' color: #666666;',
\r
4594 ' div#log *.DEBUG, label#label_DEBUG {',
\r
4598 ' div#log *.INFO, label#label_INFO {',
\r
4599 ' color: #000099;',
\r
4602 ' div#log *.WARN, label#label_WARN {',
\r
4603 ' color: #999900;',
\r
4606 ' div#log *.ERROR, label#label_ERROR {',
\r
4610 ' div#log *.FATAL, label#label_FATAL {',
\r
4611 ' color: #660066;',
\r
4614 ' div.TRACE#log *.TRACE,',
\r
4615 ' div.DEBUG#log *.DEBUG,',
\r
4616 ' div.INFO#log *.INFO,',
\r
4617 ' div.WARN#log *.WARN,',
\r
4618 ' div.ERROR#log *.ERROR,',
\r
4619 ' div.FATAL#log *.FATAL {',
\r
4620 ' display: block;',
\r
4623 ' div#log div.separator {',
\r
4624 ' background-color: #cccccc;',
\r
4625 ' margin: 5px 0;',
\r
4626 ' line-height: 1px;',
\r
4631 ' <body id="body">',
\r
4632 ' <div id="switchesContainer">',
\r
4633 ' <div id="switches">',
\r
4634 ' <div id="levels" class="toolbar">',
\r
4636 ' <input type="checkbox" id="switch_TRACE" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide trace messages" /><label for="switch_TRACE" id="label_TRACE">trace</label>',
\r
4637 ' <input type="checkbox" id="switch_DEBUG" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide debug messages" /><label for="switch_DEBUG" id="label_DEBUG">debug</label>',
\r
4638 ' <input type="checkbox" id="switch_INFO" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide info messages" /><label for="switch_INFO" id="label_INFO">info</label>',
\r
4639 ' <input type="checkbox" id="switch_WARN" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide warn messages" /><label for="switch_WARN" id="label_WARN">warn</label>',
\r
4640 ' <input type="checkbox" id="switch_ERROR" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide error messages" /><label for="switch_ERROR" id="label_ERROR">error</label>',
\r
4641 ' <input type="checkbox" id="switch_FATAL" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide fatal messages" /><label for="switch_FATAL" id="label_FATAL">fatal</label>',
\r
4642 ' <input type="checkbox" id="switch_ALL" onclick="toggleAllLevels(); applyFilters()" checked="checked" title="Show/hide all messages" /><label for="switch_ALL" id="label_ALL">all</label>',
\r
4644 ' <div id="search" class="toolbar">',
\r
4645 ' <label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" />',
\r
4646 ' <input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" />',
\r
4647 ' <input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label>',
\r
4648 ' <input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label>',
\r
4649 ' <input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label>',
\r
4650 ' <div id="searchNav">',
\r
4651 ' <input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext()" class="button" title="Go to the next matching log entry" />',
\r
4652 ' <input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" />',
\r
4653 ' <input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label>',
\r
4654 ' <input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label>',
\r
4657 ' <div id="options" class="toolbar">',
\r
4659 ' <input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label>',
\r
4660 ' <input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label>',
\r
4661 ' <input type="checkbox" id="newestAtTop" onclick="toggleNewestAtTop()" title="If checked, causes newest messages to appear at the top" /><label for="newestAtTop" id="newestAtTopLabel">Newest at the top</label>',
\r
4662 ' <input type="checkbox" id="scrollToLatest" onclick="toggleScrollToLatest()" checked="checked" title="If checked, window automatically scrolls to a new message when it is added" /><label for="scrollToLatest" id="scrollToLatestLabel">Scroll to latest</label>',
\r
4663 ' <input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages" />',
\r
4664 ' <input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" />',
\r
4665 ' <input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" />',
\r
4669 ' <div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>',
\r
4670 ' <div id="commandLine" class="toolbar">',
\r
4671 ' <div id="commandLineContainer">',
\r
4672 ' <input type="text" id="command" title="Enter a JavaScript command here and hit return or press \'Evaluate\'" />',
\r
4673 ' <input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" />',
\r
4682 var defaultCommandLineFunctions = [];
\r
4684 ConsoleAppender = function() {};
\r
4686 var consoleAppenderIdCounter = 1;
\r
4687 ConsoleAppender.prototype = new Appender();
\r
4689 ConsoleAppender.prototype.create = function(inPage, container,
\r
4690 lazyInit, initiallyMinimized, useDocumentWrite, width, height, focusConsoleWindow) {
\r
4691 var appender = this;
\r
4693 // Common properties
\r
4694 var initialized = false;
\r
4695 var consoleWindowCreated = false;
\r
4696 var consoleWindowLoaded = false;
\r
4697 var consoleClosed = false;
\r
4699 var queuedLoggingEvents = [];
\r
4700 var isSupported = true;
\r
4701 var consoleAppenderId = consoleAppenderIdCounter++;
\r
4703 // Local variables
\r
4704 initiallyMinimized = extractBooleanFromParam(initiallyMinimized, this.defaults.initiallyMinimized);
\r
4705 lazyInit = extractBooleanFromParam(lazyInit, this.defaults.lazyInit);
\r
4706 useDocumentWrite = extractBooleanFromParam(useDocumentWrite, this.defaults.useDocumentWrite);
\r
4707 var newestMessageAtTop = this.defaults.newestMessageAtTop;
\r
4708 var scrollToLatestMessage = this.defaults.scrollToLatestMessage;
\r
4709 width = width ? width : this.defaults.width;
\r
4710 height = height ? height : this.defaults.height;
\r
4711 var maxMessages = this.defaults.maxMessages;
\r
4712 var showCommandLine = this.defaults.showCommandLine;
\r
4713 var commandLineObjectExpansionDepth = this.defaults.commandLineObjectExpansionDepth;
\r
4714 var showHideButton = this.defaults.showHideButton;
\r
4715 var showCloseButton = this.defaults.showCloseButton;
\r
4716 var showLogEntryDeleteButtons = this.defaults.showLogEntryDeleteButtons;
\r
4718 this.setLayout(this.defaults.layout);
\r
4720 // Functions whose implementations vary between subclasses
\r
4721 var init, createWindow, safeToAppend, getConsoleWindow, open;
\r
4723 // Configuration methods. The function scope is used to prevent
\r
4724 // direct alteration to the appender configuration properties.
\r
4725 var appenderName = inPage ? "InPageAppender" : "PopUpAppender";
\r
4726 var checkCanConfigure = function(configOptionName) {
\r
4727 if (consoleWindowCreated) {
\r
4728 handleError(appenderName + ": configuration option '" + configOptionName + "' may not be set after the appender has been initialized");
\r
4734 var consoleWindowExists = function() {
\r
4735 return (consoleWindowLoaded && isSupported && !consoleClosed);
\r
4738 this.isNewestMessageAtTop = function() { return newestMessageAtTop; };
\r
4739 this.setNewestMessageAtTop = function(newestMessageAtTopParam) {
\r
4740 newestMessageAtTop = bool(newestMessageAtTopParam);
\r
4741 if (consoleWindowExists()) {
\r
4742 getConsoleWindow().setNewestAtTop(newestMessageAtTop);
\r
4746 this.isScrollToLatestMessage = function() { return scrollToLatestMessage; };
\r
4747 this.setScrollToLatestMessage = function(scrollToLatestMessageParam) {
\r
4748 scrollToLatestMessage = bool(scrollToLatestMessageParam);
\r
4749 if (consoleWindowExists()) {
\r
4750 getConsoleWindow().setScrollToLatest(scrollToLatestMessage);
\r
4754 this.getWidth = function() { return width; };
\r
4755 this.setWidth = function(widthParam) {
\r
4756 if (checkCanConfigure("width")) {
\r
4757 width = extractStringFromParam(widthParam, width);
\r
4761 this.getHeight = function() { return height; };
\r
4762 this.setHeight = function(heightParam) {
\r
4763 if (checkCanConfigure("height")) {
\r
4764 height = extractStringFromParam(heightParam, height);
\r
4768 this.getMaxMessages = function() { return maxMessages; };
\r
4769 this.setMaxMessages = function(maxMessagesParam) {
\r
4770 maxMessages = extractIntFromParam(maxMessagesParam, maxMessages);
\r
4771 if (consoleWindowExists()) {
\r
4772 getConsoleWindow().setMaxMessages(maxMessages);
\r
4776 this.isShowCommandLine = function() { return showCommandLine; };
\r
4777 this.setShowCommandLine = function(showCommandLineParam) {
\r
4778 showCommandLine = bool(showCommandLineParam);
\r
4779 if (consoleWindowExists()) {
\r
4780 getConsoleWindow().setShowCommandLine(showCommandLine);
\r
4784 this.isShowHideButton = function() { return showHideButton; };
\r
4785 this.setShowHideButton = function(showHideButtonParam) {
\r
4786 showHideButton = bool(showHideButtonParam);
\r
4787 if (consoleWindowExists()) {
\r
4788 getConsoleWindow().setShowHideButton(showHideButton);
\r
4792 this.isShowCloseButton = function() { return showCloseButton; };
\r
4793 this.setShowCloseButton = function(showCloseButtonParam) {
\r
4794 showCloseButton = bool(showCloseButtonParam);
\r
4795 if (consoleWindowExists()) {
\r
4796 getConsoleWindow().setShowCloseButton(showCloseButton);
\r
4800 this.getCommandLineObjectExpansionDepth = function() { return commandLineObjectExpansionDepth; };
\r
4801 this.setCommandLineObjectExpansionDepth = function(commandLineObjectExpansionDepthParam) {
\r
4802 commandLineObjectExpansionDepth = extractIntFromParam(commandLineObjectExpansionDepthParam, commandLineObjectExpansionDepth);
\r
4805 var minimized = initiallyMinimized;
\r
4806 this.isInitiallyMinimized = function() { return initiallyMinimized; };
\r
4807 this.setInitiallyMinimized = function(initiallyMinimizedParam) {
\r
4808 if (checkCanConfigure("initiallyMinimized")) {
\r
4809 initiallyMinimized = bool(initiallyMinimizedParam);
\r
4810 minimized = initiallyMinimized;
\r
4814 this.isUseDocumentWrite = function() { return useDocumentWrite; };
\r
4815 this.setUseDocumentWrite = function(useDocumentWriteParam) {
\r
4816 if (checkCanConfigure("useDocumentWrite")) {
\r
4817 useDocumentWrite = bool(useDocumentWriteParam);
\r
4822 function QueuedLoggingEvent(loggingEvent, formattedMessage) {
\r
4823 this.loggingEvent = loggingEvent;
\r
4824 this.levelName = loggingEvent.level.name;
\r
4825 this.formattedMessage = formattedMessage;
\r
4828 QueuedLoggingEvent.prototype.append = function() {
\r
4829 getConsoleWindow().log(this.levelName, this.formattedMessage);
\r
4832 function QueuedGroup(name, initiallyExpanded) {
\r
4834 this.initiallyExpanded = initiallyExpanded;
\r
4837 QueuedGroup.prototype.append = function() {
\r
4838 getConsoleWindow().group(this.name, this.initiallyExpanded);
\r
4841 function QueuedGroupEnd() {}
\r
4843 QueuedGroupEnd.prototype.append = function() {
\r
4844 getConsoleWindow().groupEnd();
\r
4847 var checkAndAppend = function() {
\r
4848 // Next line forces a check of whether the window has been closed
\r
4850 if (!initialized) {
\r
4852 } else if (consoleClosed && reopenWhenClosed) {
\r
4855 if (safeToAppend()) {
\r
4856 appendQueuedLoggingEvents();
\r
4860 this.append = function(loggingEvent) {
\r
4861 if (isSupported) {
\r
4862 // Format the message
\r
4863 var formattedMessage = appender.getLayout().format(loggingEvent);
\r
4864 if (this.getLayout().ignoresThrowable()) {
\r
4865 formattedMessage += loggingEvent.getThrowableStrRep();
\r
4867 queuedLoggingEvents.push(new QueuedLoggingEvent(loggingEvent, formattedMessage));
\r
4872 this.group = function(name, initiallyExpanded) {
\r
4873 if (isSupported) {
\r
4874 queuedLoggingEvents.push(new QueuedGroup(name, initiallyExpanded));
\r
4879 this.groupEnd = function() {
\r
4880 if (isSupported) {
\r
4881 queuedLoggingEvents.push(new QueuedGroupEnd());
\r
4886 var appendQueuedLoggingEvents = function() {
\r
4887 var currentLoggingEvent;
\r
4888 while (queuedLoggingEvents.length > 0) {
\r
4889 queuedLoggingEvents.shift().append();
\r
4891 if (focusConsoleWindow) {
\r
4892 getConsoleWindow().focus();
\r
4896 this.setAddedToLogger = function(logger) {
\r
4897 this.loggers.push(logger);
\r
4898 if (enabled && !lazyInit) {
\r
4903 this.clear = function() {
\r
4904 if (consoleWindowExists()) {
\r
4905 getConsoleWindow().clearLog();
\r
4907 queuedLoggingEvents.length = 0;
\r
4910 this.focus = function() {
\r
4911 if (consoleWindowExists()) {
\r
4912 getConsoleWindow().focus();
\r
4916 this.focusCommandLine = function() {
\r
4917 if (consoleWindowExists()) {
\r
4918 getConsoleWindow().focusCommandLine();
\r
4922 this.focusSearch = function() {
\r
4923 if (consoleWindowExists()) {
\r
4924 getConsoleWindow().focusSearch();
\r
4928 var commandWindow = window;
\r
4930 this.getCommandWindow = function() { return commandWindow; };
\r
4931 this.setCommandWindow = function(commandWindowParam) {
\r
4932 commandWindow = commandWindowParam;
\r
4935 this.executeLastCommand = function() {
\r
4936 if (consoleWindowExists()) {
\r
4937 getConsoleWindow().evalLastCommand();
\r
4941 var commandLayout = new PatternLayout("%m");
\r
4942 this.getCommandLayout = function() { return commandLayout; };
\r
4943 this.setCommandLayout = function(commandLayoutParam) {
\r
4944 commandLayout = commandLayoutParam;
\r
4947 this.evalCommandAndAppend = function(expr) {
\r
4948 var commandReturnValue = { appendResult: true, isError: false };
\r
4949 var commandOutput = "";
\r
4950 // Evaluate the command
\r
4953 // The next three lines constitute a workaround for IE. Bizarrely, iframes seem to have no
\r
4954 // eval method on the window object initially, but once execScript has been called on
\r
4955 // it once then the eval method magically appears. See http://www.thismuchiknow.co.uk/?p=25
\r
4956 if (!commandWindow.eval && commandWindow.execScript) {
\r
4957 commandWindow.execScript("null");
\r
4960 var commandLineFunctionsHash = {};
\r
4961 for (i = 0, len = commandLineFunctions.length; i < len; i++) {
\r
4962 commandLineFunctionsHash[commandLineFunctions[i][0]] = commandLineFunctions[i][1];
\r
4965 // Keep an array of variables that are being changed in the command window so that they
\r
4966 // can be restored to their original values afterwards
\r
4967 var objectsToRestore = [];
\r
4968 var addObjectToRestore = function(name) {
\r
4969 objectsToRestore.push([name, commandWindow[name]]);
\r
4972 addObjectToRestore("appender");
\r
4973 commandWindow.appender = appender;
\r
4975 addObjectToRestore("commandReturnValue");
\r
4976 commandWindow.commandReturnValue = commandReturnValue;
\r
4978 addObjectToRestore("commandLineFunctionsHash");
\r
4979 commandWindow.commandLineFunctionsHash = commandLineFunctionsHash;
\r
4981 var addFunctionToWindow = function(name) {
\r
4982 addObjectToRestore(name);
\r
4983 commandWindow[name] = function() {
\r
4984 return this.commandLineFunctionsHash[name](appender, arguments, commandReturnValue);
\r
4988 for (i = 0, len = commandLineFunctions.length; i < len; i++) {
\r
4989 addFunctionToWindow(commandLineFunctions[i][0]);
\r
4992 // Another bizarre workaround to get IE to eval in the global scope
\r
4993 if (commandWindow === window && commandWindow.execScript) {
\r
4994 addObjectToRestore("evalExpr");
\r
4995 addObjectToRestore("result");
\r
4996 window.evalExpr = expr;
\r
4997 commandWindow.execScript("window.result=eval(window.evalExpr);");
\r
4998 result = window.result;
\r
5000 result = commandWindow.eval(expr);
\r
5002 commandOutput = isUndefined(result) ? result : formatObjectExpansion(result, commandLineObjectExpansionDepth);
\r
5004 // Restore variables in the command window to their original state
\r
5005 for (i = 0, len = objectsToRestore.length; i < len; i++) {
\r
5006 commandWindow[objectsToRestore[i][0]] = objectsToRestore[i][1];
\r
5009 commandOutput = "Error evaluating command: " + getExceptionStringRep(ex);
\r
5010 commandReturnValue.isError = true;
\r
5012 // Append command output
\r
5013 if (commandReturnValue.appendResult) {
\r
5014 var message = ">>> " + expr;
\r
5015 if (!isUndefined(commandOutput)) {
\r
5016 message += newLine + commandOutput;
\r
5018 var level = commandReturnValue.isError ? Level.ERROR : Level.INFO;
\r
5019 var loggingEvent = new LoggingEvent(null, new Date(), level, [message], null);
\r
5020 var mainLayout = this.getLayout();
\r
5021 this.setLayout(commandLayout);
\r
5022 this.append(loggingEvent);
\r
5023 this.setLayout(mainLayout);
\r
5027 var commandLineFunctions = defaultCommandLineFunctions.concat([]);
\r
5029 this.addCommandLineFunction = function(functionName, commandLineFunction) {
\r
5030 commandLineFunctions.push([functionName, commandLineFunction]);
\r
5033 var commandHistoryCookieName = "log4javascriptCommandHistory";
\r
5034 this.storeCommandHistory = function(commandHistory) {
\r
5035 setCookie(commandHistoryCookieName, commandHistory.join(","));
\r
5038 var writeHtml = function(doc) {
\r
5039 var lines = getConsoleHtmlLines();
\r
5041 for (var i = 0, len = lines.length; i < len; i++) {
\r
5042 doc.writeln(lines[i]);
\r
5047 // Set up event listeners
\r
5048 this.setEventTypes(["load", "unload"]);
\r
5050 var consoleWindowLoadHandler = function() {
\r
5051 var win = getConsoleWindow();
\r
5052 win.setAppender(appender);
\r
5053 win.setNewestAtTop(newestMessageAtTop);
\r
5054 win.setScrollToLatest(scrollToLatestMessage);
\r
5055 win.setMaxMessages(maxMessages);
\r
5056 win.setShowCommandLine(showCommandLine);
\r
5057 win.setShowHideButton(showHideButton);
\r
5058 win.setShowCloseButton(showCloseButton);
\r
5059 win.setMainWindow(window);
\r
5061 // Restore command history stored in cookie
\r
5062 var storedValue = getCookie(commandHistoryCookieName);
\r
5063 if (storedValue) {
\r
5064 win.commandHistory = storedValue.split(",");
\r
5065 win.currentCommandIndex = win.commandHistory.length;
\r
5068 appender.dispatchEvent("load", { "win" : win });
\r
5071 this.unload = function() {
\r
5072 logLog.debug("unload " + this + ", caller: " + this.unload.caller);
\r
5073 if (!consoleClosed) {
\r
5074 logLog.debug("really doing unload " + this);
\r
5075 consoleClosed = true;
\r
5076 consoleWindowLoaded = false;
\r
5077 consoleWindowCreated = false;
\r
5078 appender.dispatchEvent("unload", {});
\r
5082 var pollConsoleWindow = function(windowTest, interval, successCallback, errorMessage) {
\r
5083 function doPoll() {
\r
5085 // Test if the console has been closed while polling
\r
5086 if (consoleClosed) {
\r
5087 clearInterval(poll);
\r
5089 if (windowTest(getConsoleWindow())) {
\r
5090 clearInterval(poll);
\r
5091 successCallback();
\r
5094 clearInterval(poll);
\r
5095 isSupported = false;
\r
5096 handleError(errorMessage, ex);
\r
5100 // Poll the pop-up since the onload event is not reliable
\r
5101 var poll = setInterval(doPoll, interval);
\r
5104 var getConsoleUrl = function() {
\r
5105 var documentDomainSet = (document.domain != location.hostname);
\r
5106 return useDocumentWrite ? "" : getBaseUrl() + "console_uncompressed.html" +
\r
5107 (documentDomainSet ? "?log4javascript_domain=" + escape(document.domain) : "");
\r
5110 // Define methods and properties that vary between subclasses
\r
5114 var containerElement = null;
\r
5116 // Configuration methods. The function scope is used to prevent
\r
5117 // direct alteration to the appender configuration properties.
\r
5118 var cssProperties = [];
\r
5119 this.addCssProperty = function(name, value) {
\r
5120 if (checkCanConfigure("cssProperties")) {
\r
5121 cssProperties.push([name, value]);
\r
5125 // Define useful variables
\r
5126 var windowCreationStarted = false;
\r
5127 var iframeContainerDiv;
\r
5128 var iframeId = uniqueId + "_InPageAppender_" + consoleAppenderId;
\r
5130 this.hide = function() {
\r
5131 if (initialized && consoleWindowCreated) {
\r
5132 if (consoleWindowExists()) {
\r
5133 getConsoleWindow().$("command").blur();
\r
5135 iframeContainerDiv.style.display = "none";
\r
5140 this.show = function() {
\r
5141 if (initialized) {
\r
5142 if (consoleWindowCreated) {
\r
5143 iframeContainerDiv.style.display = "block";
\r
5144 this.setShowCommandLine(showCommandLine); // Force IE to update
\r
5145 minimized = false;
\r
5146 } else if (!windowCreationStarted) {
\r
5147 createWindow(true);
\r
5152 this.isVisible = function() {
\r
5153 return !minimized && !consoleClosed;
\r
5156 this.close = function(fromButton) {
\r
5157 if (!consoleClosed && (!fromButton || confirm("This will permanently remove the console from the page. No more messages will be logged. Do you wish to continue?"))) {
\r
5158 iframeContainerDiv.parentNode.removeChild(iframeContainerDiv);
\r
5163 // Create open, init, getConsoleWindow and safeToAppend functions
\r
5164 open = function() {
\r
5165 var initErrorMessage = "InPageAppender.open: unable to create console iframe";
\r
5167 function finalInit() {
\r
5169 if (!initiallyMinimized) {
\r
5172 consoleWindowLoadHandler();
\r
5173 consoleWindowLoaded = true;
\r
5174 appendQueuedLoggingEvents();
\r
5176 isSupported = false;
\r
5177 handleError(initErrorMessage, ex);
\r
5181 function writeToDocument() {
\r
5183 var windowTest = function(win) { return isLoaded(win); };
\r
5184 if (useDocumentWrite) {
\r
5185 writeHtml(getConsoleWindow().document);
\r
5187 if (windowTest(getConsoleWindow())) {
\r
5190 pollConsoleWindow(windowTest, 100, finalInit, initErrorMessage);
\r
5193 isSupported = false;
\r
5194 handleError(initErrorMessage, ex);
\r
5198 minimized = false;
\r
5199 iframeContainerDiv = containerElement.appendChild(document.createElement("div"));
\r
5201 iframeContainerDiv.style.width = width;
\r
5202 iframeContainerDiv.style.height = height;
\r
5203 iframeContainerDiv.style.border = "solid gray 1px";
\r
5205 for (var i = 0, len = cssProperties.length; i < len; i++) {
\r
5206 iframeContainerDiv.style[cssProperties[i][0]] = cssProperties[i][1];
\r
5209 var iframeSrc = useDocumentWrite ? "" : " src='" + getConsoleUrl() + "'";
\r
5211 // Adding an iframe using the DOM would be preferable, but it doesn't work
\r
5212 // in IE5 on Windows, or in Konqueror prior to version 3.5 - in Konqueror
\r
5213 // it creates the iframe fine but I haven't been able to find a way to obtain
\r
5214 // the iframe's window object
\r
5215 iframeContainerDiv.innerHTML = "<iframe id='" + iframeId + "' name='" + iframeId +
\r
5216 "' width='100%' height='100%' frameborder='0'" + iframeSrc +
\r
5217 " scrolling='no'></iframe>";
\r
5218 consoleClosed = false;
\r
5220 // Write the console HTML to the iframe
\r
5221 var iframeDocumentExistsTest = function(win) {
\r
5223 return bool(win) && bool(win.document);
\r
5228 if (iframeDocumentExistsTest(getConsoleWindow())) {
\r
5229 writeToDocument();
\r
5231 pollConsoleWindow(iframeDocumentExistsTest, 100, writeToDocument, initErrorMessage);
\r
5233 consoleWindowCreated = true;
\r
5236 createWindow = function(show) {
\r
5237 if (show || !initiallyMinimized) {
\r
5238 var pageLoadHandler = function() {
\r
5240 // Set up default container element
\r
5241 containerElement = document.createElement("div");
\r
5242 containerElement.style.position = "fixed";
\r
5243 containerElement.style.left = "0";
\r
5244 containerElement.style.right = "0";
\r
5245 containerElement.style.bottom = "0";
\r
5246 document.body.appendChild(containerElement);
\r
5247 appender.addCssProperty("borderWidth", "1px 0 0 0");
\r
5248 appender.addCssProperty("zIndex", 1000000); // Can't find anything authoritative that says how big z-index can be
\r
5252 var el = document.getElementById(container);
\r
5253 if (el.nodeType == 1) {
\r
5254 containerElement = el;
\r
5258 handleError("InPageAppender.init: invalid container element '" + container + "' supplied", ex);
\r
5263 // Test the type of the container supplied. First, check if it's an element
\r
5264 if (pageLoaded && container && container.appendChild) {
\r
5265 containerElement = container;
\r
5267 } else if (pageLoaded) {
\r
5268 pageLoadHandler();
\r
5270 log4javascript.addEventListener("load", pageLoadHandler);
\r
5272 windowCreationStarted = true;
\r
5276 init = function() {
\r
5278 initialized = true;
\r
5281 getConsoleWindow = function() {
\r
5282 var iframe = window.frames[iframeId];
\r
5288 safeToAppend = function() {
\r
5289 if (isSupported && !consoleClosed) {
\r
5290 if (consoleWindowCreated && !consoleWindowLoaded && getConsoleWindow() && isLoaded(getConsoleWindow())) {
\r
5291 consoleWindowLoaded = true;
\r
5293 return consoleWindowLoaded;
\r
5301 var useOldPopUp = appender.defaults.useOldPopUp;
\r
5302 var complainAboutPopUpBlocking = appender.defaults.complainAboutPopUpBlocking;
\r
5303 var reopenWhenClosed = this.defaults.reopenWhenClosed;
\r
5305 // Configuration methods. The function scope is used to prevent
\r
5306 // direct alteration to the appender configuration properties.
\r
5307 this.isUseOldPopUp = function() { return useOldPopUp; };
\r
5308 this.setUseOldPopUp = function(useOldPopUpParam) {
\r
5309 if (checkCanConfigure("useOldPopUp")) {
\r
5310 useOldPopUp = bool(useOldPopUpParam);
\r
5314 this.isComplainAboutPopUpBlocking = function() { return complainAboutPopUpBlocking; };
\r
5315 this.setComplainAboutPopUpBlocking = function(complainAboutPopUpBlockingParam) {
\r
5316 if (checkCanConfigure("complainAboutPopUpBlocking")) {
\r
5317 complainAboutPopUpBlocking = bool(complainAboutPopUpBlockingParam);
\r
5321 this.isFocusPopUp = function() { return focusConsoleWindow; };
\r
5322 this.setFocusPopUp = function(focusPopUpParam) {
\r
5323 // This property can be safely altered after logging has started
\r
5324 focusConsoleWindow = bool(focusPopUpParam);
\r
5327 this.isReopenWhenClosed = function() { return reopenWhenClosed; };
\r
5328 this.setReopenWhenClosed = function(reopenWhenClosedParam) {
\r
5329 // This property can be safely altered after logging has started
\r
5330 reopenWhenClosed = bool(reopenWhenClosedParam);
\r
5333 this.close = function() {
\r
5334 logLog.debug("close " + this);
\r
5343 this.hide = function() {
\r
5344 logLog.debug("hide " + this);
\r
5345 if (consoleWindowExists()) {
\r
5350 this.show = function() {
\r
5351 logLog.debug("show " + this);
\r
5352 if (!consoleWindowCreated) {
\r
5357 this.isVisible = function() {
\r
5358 return safeToAppend();
\r
5361 // Define useful variables
\r
5364 // Create open, init, getConsoleWindow and safeToAppend functions
\r
5365 open = function() {
\r
5366 var windowProperties = "width=" + width + ",height=" + height + ",status,resizable";
\r
5367 var frameInfo = "";
\r
5369 var frameEl = window.frameElement;
\r
5371 frameInfo = "_" + frameEl.tagName + "_" + (frameEl.name || frameEl.id || "");
\r
5374 frameInfo = "_inaccessibleParentFrame";
\r
5376 var windowName = "PopUp_" + location.host.replace(/[^a-z0-9]/gi, "_") + "_" + consoleAppenderId + frameInfo;
\r
5377 if (!useOldPopUp || !useDocumentWrite) {
\r
5378 // Ensure a previous window isn't used by using a unique name
\r
5379 windowName = windowName + "_" + uniqueId;
\r
5382 var checkPopUpClosed = function(win) {
\r
5383 if (consoleClosed) {
\r
5387 return bool(win) && win.closed;
\r
5393 var popUpClosedCallback = function() {
\r
5394 if (!consoleClosed) {
\r
5395 appender.unload();
\r
5399 function finalInit() {
\r
5400 getConsoleWindow().setCloseIfOpenerCloses(!useOldPopUp || !useDocumentWrite);
\r
5401 consoleWindowLoadHandler();
\r
5402 consoleWindowLoaded = true;
\r
5403 appendQueuedLoggingEvents();
\r
5404 pollConsoleWindow(checkPopUpClosed, 500, popUpClosedCallback,
\r
5405 "PopUpAppender.checkPopUpClosed: error checking pop-up window");
\r
5409 popUp = window.open(getConsoleUrl(), windowName, windowProperties);
\r
5410 consoleClosed = false;
\r
5411 consoleWindowCreated = true;
\r
5412 if (popUp && popUp.document) {
\r
5413 if (useDocumentWrite && useOldPopUp && isLoaded(popUp)) {
\r
5414 popUp.mainPageReloaded();
\r
5417 if (useDocumentWrite) {
\r
5418 writeHtml(popUp.document);
\r
5420 // Check if the pop-up window object is available
\r
5421 var popUpLoadedTest = function(win) { return bool(win) && isLoaded(win); };
\r
5422 if (isLoaded(popUp)) {
\r
5425 pollConsoleWindow(popUpLoadedTest, 100, finalInit,
\r
5426 "PopUpAppender.init: unable to create console window");
\r
5430 isSupported = false;
\r
5431 logLog.warn("PopUpAppender.init: pop-ups blocked, please unblock to use PopUpAppender");
\r
5432 if (complainAboutPopUpBlocking) {
\r
5433 handleError("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging.");
\r
5437 handleError("PopUpAppender.init: error creating pop-up", ex);
\r
5441 createWindow = function() {
\r
5442 if (!initiallyMinimized) {
\r
5447 init = function() {
\r
5449 initialized = true;
\r
5452 getConsoleWindow = function() {
\r
5456 safeToAppend = function() {
\r
5457 if (isSupported && !isUndefined(popUp) && !consoleClosed) {
\r
5458 if (popUp.closed ||
\r
5459 (consoleWindowLoaded && isUndefined(popUp.closed))) { // Extra check for Opera
\r
5460 appender.unload();
\r
5461 logLog.debug("PopUpAppender: pop-up closed");
\r
5464 if (!consoleWindowLoaded && isLoaded(popUp)) {
\r
5465 consoleWindowLoaded = true;
\r
5468 return isSupported && consoleWindowLoaded && !consoleClosed;
\r
5472 // Expose getConsoleWindow so that automated tests can check the DOM
\r
5473 this.getConsoleWindow = getConsoleWindow;
\r
5476 ConsoleAppender.addGlobalCommandLineFunction = function(functionName, commandLineFunction) {
\r
5477 defaultCommandLineFunctions.push([functionName, commandLineFunction]);
\r
5480 /* ------------------------------------------------------------------ */
\r
5482 function PopUpAppender(lazyInit, initiallyMinimized, useDocumentWrite,
\r
5484 this.create(false, null, lazyInit, initiallyMinimized,
\r
5485 useDocumentWrite, width, height, this.defaults.focusPopUp);
\r
5488 PopUpAppender.prototype = new ConsoleAppender();
\r
5490 PopUpAppender.prototype.defaults = {
\r
5491 layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),
\r
5492 initiallyMinimized: false,
\r
5493 focusPopUp: false,
\r
5495 useOldPopUp: true,
\r
5496 complainAboutPopUpBlocking: true,
\r
5497 newestMessageAtTop: false,
\r
5498 scrollToLatestMessage: true,
\r
5501 reopenWhenClosed: false,
\r
5502 maxMessages: null,
\r
5503 showCommandLine: true,
\r
5504 commandLineObjectExpansionDepth: 1,
\r
5505 showHideButton: false,
\r
5506 showCloseButton: true,
\r
5507 showLogEntryDeleteButtons: true,
\r
5508 useDocumentWrite: true
\r
5511 PopUpAppender.prototype.toString = function() {
\r
5512 return "PopUpAppender";
\r
5515 log4javascript.PopUpAppender = PopUpAppender;
\r
5517 /* ------------------------------------------------------------------ */
\r
5519 function InPageAppender(container, lazyInit, initiallyMinimized,
\r
5520 useDocumentWrite, width, height) {
\r
5521 this.create(true, container, lazyInit, initiallyMinimized,
\r
5522 useDocumentWrite, width, height, false);
\r
5525 InPageAppender.prototype = new ConsoleAppender();
\r
5527 InPageAppender.prototype.defaults = {
\r
5528 layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),
\r
5529 initiallyMinimized: false,
\r
5531 newestMessageAtTop: false,
\r
5532 scrollToLatestMessage: true,
\r
5535 maxMessages: null,
\r
5536 showCommandLine: true,
\r
5537 commandLineObjectExpansionDepth: 1,
\r
5538 showHideButton: false,
\r
5539 showCloseButton: false,
\r
5540 showLogEntryDeleteButtons: true,
\r
5541 useDocumentWrite: true
\r
5544 InPageAppender.prototype.toString = function() {
\r
5545 return "InPageAppender";
\r
5548 log4javascript.InPageAppender = InPageAppender;
\r
5550 // Next line for backwards compatibility
\r
5551 log4javascript.InlineAppender = InPageAppender;
\r
5553 /* ---------------------------------------------------------------------- */
\r
5554 // Console extension functions
\r
5556 function padWithSpaces(str, len) {
\r
5557 if (str.length < len) {
\r
5559 var numberOfSpaces = Math.max(0, len - str.length);
\r
5560 for (var i = 0; i < numberOfSpaces; i++) {
\r
5563 str += spaces.join("");
\r
5569 function dir(obj) {
\r
5571 // Obtain the length of the longest property name
\r
5572 for (var p in obj) {
\r
5573 maxLen = Math.max(toStr(p).length, maxLen);
\r
5575 // Create the nicely formatted property list
\r
5576 var propList = [];
\r
5578 var propNameStr = " " + padWithSpaces(toStr(p), maxLen + 2);
\r
5581 propVal = splitIntoLines(toStr(obj[p])).join(padWithSpaces(newLine, maxLen + 6));
\r
5583 propVal = "[Error obtaining property. Details: " + getExceptionMessage(ex) + "]";
\r
5585 propList.push(propNameStr + propVal);
\r
5587 return propList.join(newLine);
\r
5592 ATTRIBUTE_NODE: 2,
\r
5594 CDATA_SECTION_NODE: 4,
\r
5595 ENTITY_REFERENCE_NODE: 5,
\r
5597 PROCESSING_INSTRUCTION_NODE: 7,
\r
5600 DOCUMENT_TYPE_NODE: 10,
\r
5601 DOCUMENT_FRAGMENT_NODE: 11,
\r
5605 var preFormattedElements = ["script", "pre"];
\r
5607 // This should be the definitive list, as specified by the XHTML 1.0 Transitional DTD
\r
5608 var emptyElements = ["br", "img", "hr", "param", "link", "area", "input", "col", "base", "meta"];
\r
5609 var indentationUnit = " ";
\r
5611 // Create and return an XHTML string from the node specified
\r
5612 function getXhtml(rootNode, includeRootNode, indentation, startNewLine, preformatted) {
\r
5613 includeRootNode = (typeof includeRootNode == "undefined") ? true : !!includeRootNode;
\r
5614 if (typeof indentation != "string") {
\r
5617 startNewLine = !!startNewLine;
\r
5618 preformatted = !!preformatted;
\r
5621 function isWhitespace(node) {
\r
5622 return ((node.nodeType == nodeTypes.TEXT_NODE) && /^[ \t\r\n]*$/.test(node.nodeValue));
\r
5625 function fixAttributeValue(attrValue) {
\r
5626 return attrValue.toString().replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
\r
5629 function getStyleAttributeValue(el) {
\r
5630 var stylePairs = el.style.cssText.split(";");
\r
5631 var styleValue = "";
\r
5632 var isFirst = true;
\r
5633 for (var j = 0, len = stylePairs.length; j < len; j++) {
\r
5634 var nameValueBits = stylePairs[j].split(":");
\r
5636 if (!/^\s*$/.test(nameValueBits[0])) {
\r
5637 props.push(trim(nameValueBits[0]).toLowerCase() + ":" + trim(nameValueBits[1]));
\r
5639 styleValue = props.join(";");
\r
5641 return styleValue;
\r
5644 function getNamespace(el) {
\r
5647 } else if (el.outerHTML) {
\r
5648 var regex = new RegExp("<([^:]+):" + el.tagName + "[^>]*>", "i");
\r
5649 if (regex.test(el.outerHTML)) {
\r
5650 return RegExp.$1.toLowerCase();
\r
5659 if (includeRootNode && rootNode.nodeType != nodeTypes.DOCUMENT_FRAGMENT_NODE) {
\r
5660 switch (rootNode.nodeType) {
\r
5661 case nodeTypes.ELEMENT_NODE:
\r
5662 var tagName = rootNode.tagName.toLowerCase();
\r
5663 xhtml = startNewLine ? newLine + indentation : "";
\r
5665 // Allow for namespaces, where present
\r
5666 var prefix = getNamespace(rootNode);
\r
5667 var hasPrefix = !!prefix;
\r
5669 xhtml += prefix + ":";
\r
5672 for (i = 0, len = rootNode.attributes.length; i < len; i++) {
\r
5673 var currentAttr = rootNode.attributes[i];
\r
5674 // Check the attribute is valid.
\r
5675 if (! currentAttr.specified ||
\r
5676 currentAttr.nodeValue === null ||
\r
5677 currentAttr.nodeName.toLowerCase() === "style" ||
\r
5678 typeof currentAttr.nodeValue !== "string" ||
\r
5679 currentAttr.nodeName.indexOf("_moz") === 0) {
\r
5682 xhtml += " " + currentAttr.nodeName.toLowerCase() + "=\"";
\r
5683 xhtml += fixAttributeValue(currentAttr.nodeValue);
\r
5686 // Style needs to be done separately as it is not reported as an
\r
5687 // attribute in IE
\r
5688 if (rootNode.style.cssText) {
\r
5689 var styleValue = getStyleAttributeValue(rootNode);
\r
5690 if (styleValue !== "") {
\r
5691 xhtml += " style=\"" + getStyleAttributeValue(rootNode) + "\"";
\r
5694 if (array_contains(emptyElements, tagName) ||
\r
5695 (hasPrefix && !rootNode.hasChildNodes())) {
\r
5696 xhtml += "/" + gt;
\r
5699 // Add output for childNodes collection (which doesn't include attribute nodes)
\r
5700 var childStartNewLine = !(rootNode.childNodes.length === 1 &&
\r
5701 rootNode.childNodes[0].nodeType === nodeTypes.TEXT_NODE);
\r
5702 var childPreformatted = array_contains(preFormattedElements, tagName);
\r
5703 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
\r
5704 xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit,
\r
5705 childStartNewLine, childPreformatted);
\r
5707 // Add the end tag
\r
5708 var endTag = lt + "/" + tagName + gt;
\r
5709 xhtml += childStartNewLine ? newLine + indentation + endTag : endTag;
\r
5712 case nodeTypes.TEXT_NODE:
\r
5713 if (isWhitespace(rootNode)) {
\r
5716 if (preformatted) {
\r
5717 xhtml = rootNode.nodeValue;
\r
5719 // Trim whitespace from each line of the text node
\r
5720 var lines = splitIntoLines(trim(rootNode.nodeValue));
\r
5721 var trimmedLines = [];
\r
5722 for (var i = 0, len = lines.length; i < len; i++) {
\r
5723 trimmedLines[i] = trim(lines[i]);
\r
5725 xhtml = trimmedLines.join(newLine + indentation);
\r
5727 if (startNewLine) {
\r
5728 xhtml = newLine + indentation + xhtml;
\r
5732 case nodeTypes.CDATA_SECTION_NODE:
\r
5733 return "<![CDA" + "TA[" + rootNode.nodeValue + "]" + "]>" + newLine;
\r
5734 case nodeTypes.DOCUMENT_NODE:
\r
5736 // Add output for childNodes collection (which doesn't include attribute nodes)
\r
5737 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
\r
5738 xhtml += getXhtml(rootNode.childNodes[i], true, indentation);
\r
5746 // Add output for childNodes collection (which doesn't include attribute nodes)
\r
5747 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
\r
5748 xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit);
\r
5754 function createCommandLineFunctions() {
\r
5755 ConsoleAppender.addGlobalCommandLineFunction("$", function(appender, args, returnValue) {
\r
5756 return document.getElementById(args[0]);
\r
5759 ConsoleAppender.addGlobalCommandLineFunction("dir", function(appender, args, returnValue) {
\r
5761 for (var i = 0, len = args.length; i < len; i++) {
\r
5762 lines[i] = dir(args[i]);
\r
5764 return lines.join(newLine + newLine);
\r
5767 ConsoleAppender.addGlobalCommandLineFunction("dirxml", function(appender, args, returnValue) {
\r
5769 for (var i = 0, len = args.length; i < len; i++) {
\r
5770 var win = appender.getCommandWindow();
\r
5771 lines[i] = getXhtml(args[i]);
\r
5773 return lines.join(newLine + newLine);
\r
5776 ConsoleAppender.addGlobalCommandLineFunction("cd", function(appender, args, returnValue) {
\r
5778 if (args.length === 0 || args[0] === "") {
\r
5780 message = "Command line set to run in main window";
\r
5782 if (args[0].window == args[0]) {
\r
5784 message = "Command line set to run in frame '" + args[0].name + "'";
\r
5786 win = window.frames[args[0]];
\r
5788 message = "Command line set to run in frame '" + args[0] + "'";
\r
5790 returnValue.isError = true;
\r
5791 message = "Frame '" + args[0] + "' does not exist";
\r
5792 win = appender.getCommandWindow();
\r
5796 appender.setCommandWindow(win);
\r
5800 ConsoleAppender.addGlobalCommandLineFunction("clear", function(appender, args, returnValue) {
\r
5801 returnValue.appendResult = false;
\r
5805 ConsoleAppender.addGlobalCommandLineFunction("keys", function(appender, args, returnValue) {
\r
5807 for (var k in args[0]) {
\r
5813 ConsoleAppender.addGlobalCommandLineFunction("values", function(appender, args, returnValue) {
\r
5815 for (var k in args[0]) {
\r
5817 values.push(args[0][k]);
\r
5819 logLog.warn("values(): Unable to obtain value for key " + k + ". Details: " + getExceptionMessage(ex));
\r
5825 ConsoleAppender.addGlobalCommandLineFunction("expansionDepth", function(appender, args, returnValue) {
\r
5826 var expansionDepth = parseInt(args[0], 10);
\r
5827 if (isNaN(expansionDepth) || expansionDepth < 0) {
\r
5828 returnValue.isError = true;
\r
5829 return "" + args[0] + " is not a valid expansion depth";
\r
5831 appender.setCommandLineObjectExpansionDepth(expansionDepth);
\r
5832 return "Object expansion depth set to " + expansionDepth;
\r
5838 // Add command line functions
\r
5839 createCommandLineFunctions();
\r
5842 /* ------------------------------------------------------------------ */
\r
5847 /* ---------------------------------------------------------------------- */
\r
5850 log4javascript.setDocumentReady = function() {
\r
5851 pageLoaded = true;
\r
5852 log4javascript.dispatchEvent("load", {});
\r
5855 if (window.addEventListener) {
\r
5856 window.addEventListener("load", log4javascript.setDocumentReady, false);
\r
5857 } else if (window.attachEvent) {
\r
5858 window.attachEvent("onload", log4javascript.setDocumentReady);
\r
5860 var oldOnload = window.onload;
\r
5861 if (typeof window.onload != "function") {
\r
5862 window.onload = log4javascript.setDocumentReady;
\r
5864 window.onload = function(evt) {
\r
5868 log4javascript.setDocumentReady();
\r
5873 // Ensure that the log4javascript object is available in the window. This
\r
5874 // is necessary for log4javascript to be available in IE if loaded using
\r
5875 // Dojo's module system
\r
5876 window.log4javascript = log4javascript;
\r
5878 return log4javascript;
\r