blob: a644e3b7694e1731d7793e2d34543150607e7988 [file] [log] [blame]
Siobhan Tullye18b3442014-02-23 14:23:34 -05001/**
2 * Copyright 2013 Tim Down.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * log4javascript
19 *
20 * log4javascript is a logging framework for JavaScript based on log4j
21 * for Java. This file contains all core log4javascript code and is the only
22 * file required to use log4javascript, unless you require support for
23 * document.domain, in which case you will also need console.html, which must be
24 * stored in the same directory as the main log4javascript.js file.
25 *
26 * Author: Tim Down <tim@log4javascript.org>
27 * Version: 1.4.6
28 * Edition: log4javascript
29 * Build date: 19 March 2013
30 * Website: http://log4javascript.org
31 */
32
33/* -------------------------------------------------------------------------- */
34// Array-related stuff
35
36// Next three methods are solely for IE5, which is missing them
37if (!Array.prototype.push) {
38 Array.prototype.push = function() {
39 for (var i = 0, len = arguments.length; i < len; i++){
40 this[this.length] = arguments[i];
41 }
42 return this.length;
43 };
44}
45
46if (!Array.prototype.shift) {
47 Array.prototype.shift = function() {
48 if (this.length > 0) {
49 var firstItem = this[0];
50 for (var i = 0, len = this.length - 1; i < len; i++) {
51 this[i] = this[i + 1];
52 }
53 this.length = this.length - 1;
54 return firstItem;
55 }
56 };
57}
58
59if (!Array.prototype.splice) {
60 Array.prototype.splice = function(startIndex, deleteCount) {
61 var itemsAfterDeleted = this.slice(startIndex + deleteCount);
62 var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);
63 this.length = startIndex;
64 // Copy the arguments into a proper Array object
65 var argumentsArray = [];
66 for (var i = 0, len = arguments.length; i < len; i++) {
67 argumentsArray[i] = arguments[i];
68 }
69 var itemsToAppend = (argumentsArray.length > 2) ?
70 itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;
71 for (i = 0, len = itemsToAppend.length; i < len; i++) {
72 this.push(itemsToAppend[i]);
73 }
74 return itemsDeleted;
75 };
76}
77
78/* -------------------------------------------------------------------------- */
79
80var log4javascript = (function() {
81
82 function isUndefined(obj) {
83 return typeof obj == "undefined";
84 }
85
86 /* ---------------------------------------------------------------------- */
87 // Custom event support
88
89 function EventSupport() {}
90
91 EventSupport.prototype = {
92 eventTypes: [],
93 eventListeners: {},
94 setEventTypes: function(eventTypesParam) {
95 if (eventTypesParam instanceof Array) {
96 this.eventTypes = eventTypesParam;
97 this.eventListeners = {};
98 for (var i = 0, len = this.eventTypes.length; i < len; i++) {
99 this.eventListeners[this.eventTypes[i]] = [];
100 }
101 } else {
102 handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array");
103 }
104 },
105
106 addEventListener: function(eventType, listener) {
107 if (typeof listener == "function") {
108 if (!array_contains(this.eventTypes, eventType)) {
109 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'");
110 }
111 this.eventListeners[eventType].push(listener);
112 } else {
113 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function");
114 }
115 },
116
117 removeEventListener: function(eventType, listener) {
118 if (typeof listener == "function") {
119 if (!array_contains(this.eventTypes, eventType)) {
120 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'");
121 }
122 array_remove(this.eventListeners[eventType], listener);
123 } else {
124 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function");
125 }
126 },
127
128 dispatchEvent: function(eventType, eventArgs) {
129 if (array_contains(this.eventTypes, eventType)) {
130 var listeners = this.eventListeners[eventType];
131 for (var i = 0, len = listeners.length; i < len; i++) {
132 listeners[i](this, eventType, eventArgs);
133 }
134 } else {
135 handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'");
136 }
137 }
138 };
139
140 /* -------------------------------------------------------------------------- */
141
142 var applicationStartDate = new Date();
143 var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" +
144 Math.floor(Math.random() * 100000000);
145 var emptyFunction = function() {};
146 var newLine = "\r\n";
147 var pageLoaded = false;
148
149 // Create main log4javascript object; this will be assigned public properties
150 function Log4JavaScript() {}
151 Log4JavaScript.prototype = new EventSupport();
152
153 log4javascript = new Log4JavaScript();
154 log4javascript.version = "1.4.6";
155 log4javascript.edition = "log4javascript";
156
157 /* -------------------------------------------------------------------------- */
158 // Utility functions
159
160 function toStr(obj) {
161 if (obj && obj.toString) {
162 return obj.toString();
163 } else {
164 return String(obj);
165 }
166 }
167
168 function getExceptionMessage(ex) {
169 if (ex.message) {
170 return ex.message;
171 } else if (ex.description) {
172 return ex.description;
173 } else {
174 return toStr(ex);
175 }
176 }
177
178 // Gets the portion of the URL after the last slash
179 function getUrlFileName(url) {
180 var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
181 return url.substr(lastSlashIndex + 1);
182 }
183
184 // Returns a nicely formatted representation of an error
185 function getExceptionStringRep(ex) {
186 if (ex) {
187 var exStr = "Exception: " + getExceptionMessage(ex);
188 try {
189 if (ex.lineNumber) {
190 exStr += " on line number " + ex.lineNumber;
191 }
192 if (ex.fileName) {
193 exStr += " in file " + getUrlFileName(ex.fileName);
194 }
195 } catch (localEx) {
196 logLog.warn("Unable to obtain file and line information for error");
197 }
198 if (showStackTraces && ex.stack) {
199 exStr += newLine + "Stack trace:" + newLine + ex.stack;
200 }
201 return exStr;
202 }
203 return null;
204 }
205
206 function bool(obj) {
207 return Boolean(obj);
208 }
209
210 function trim(str) {
211 return str.replace(/^\s+/, "").replace(/\s+$/, "");
212 }
213
214 function splitIntoLines(text) {
215 // Ensure all line breaks are \n only
216 var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
217 return text2.split("\n");
218 }
219
220 var urlEncode = (typeof window.encodeURIComponent != "undefined") ?
221 function(str) {
222 return encodeURIComponent(str);
223 }:
224 function(str) {
225 return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D");
226 };
227
228 var urlDecode = (typeof window.decodeURIComponent != "undefined") ?
229 function(str) {
230 return decodeURIComponent(str);
231 }:
232 function(str) {
233 return unescape(str).replace(/%2B/g, "+").replace(/%22/g, "\"").replace(/%27/g, "'").replace(/%2F/g, "/").replace(/%3D/g, "=");
234 };
235
236 function array_remove(arr, val) {
237 var index = -1;
238 for (var i = 0, len = arr.length; i < len; i++) {
239 if (arr[i] === val) {
240 index = i;
241 break;
242 }
243 }
244 if (index >= 0) {
245 arr.splice(index, 1);
246 return true;
247 } else {
248 return false;
249 }
250 }
251
252 function array_contains(arr, val) {
253 for(var i = 0, len = arr.length; i < len; i++) {
254 if (arr[i] == val) {
255 return true;
256 }
257 }
258 return false;
259 }
260
261 function extractBooleanFromParam(param, defaultValue) {
262 if (isUndefined(param)) {
263 return defaultValue;
264 } else {
265 return bool(param);
266 }
267 }
268
269 function extractStringFromParam(param, defaultValue) {
270 if (isUndefined(param)) {
271 return defaultValue;
272 } else {
273 return String(param);
274 }
275 }
276
277 function extractIntFromParam(param, defaultValue) {
278 if (isUndefined(param)) {
279 return defaultValue;
280 } else {
281 try {
282 var value = parseInt(param, 10);
283 return isNaN(value) ? defaultValue : value;
284 } catch (ex) {
285 logLog.warn("Invalid int param " + param, ex);
286 return defaultValue;
287 }
288 }
289 }
290
291 function extractFunctionFromParam(param, defaultValue) {
292 if (typeof param == "function") {
293 return param;
294 } else {
295 return defaultValue;
296 }
297 }
298
299 function isError(err) {
300 return (err instanceof Error);
301 }
302
303 if (!Function.prototype.apply){
304 Function.prototype.apply = function(obj, args) {
305 var methodName = "__apply__";
306 if (typeof obj[methodName] != "undefined") {
307 methodName += String(Math.random()).substr(2);
308 }
309 obj[methodName] = this;
310
311 var argsStrings = [];
312 for (var i = 0, len = args.length; i < len; i++) {
313 argsStrings[i] = "args[" + i + "]";
314 }
315 var script = "obj." + methodName + "(" + argsStrings.join(",") + ")";
316 var returnValue = eval(script);
317 delete obj[methodName];
318 return returnValue;
319 };
320 }
321
322 if (!Function.prototype.call){
323 Function.prototype.call = function(obj) {
324 var args = [];
325 for (var i = 1, len = arguments.length; i < len; i++) {
326 args[i - 1] = arguments[i];
327 }
328 return this.apply(obj, args);
329 };
330 }
331
332 function getListenersPropertyName(eventName) {
333 return "__log4javascript_listeners__" + eventName;
334 }
335
336 function addEvent(node, eventName, listener, useCapture, win) {
337 win = win ? win : window;
338 if (node.addEventListener) {
339 node.addEventListener(eventName, listener, useCapture);
340 } else if (node.attachEvent) {
341 node.attachEvent("on" + eventName, listener);
342 } else {
343 var propertyName = getListenersPropertyName(eventName);
344 if (!node[propertyName]) {
345 node[propertyName] = [];
346 // Set event handler
347 node["on" + eventName] = function(evt) {
348 evt = getEvent(evt, win);
349 var listenersPropertyName = getListenersPropertyName(eventName);
350
351 // Clone the array of listeners to leave the original untouched
352 var listeners = this[listenersPropertyName].concat([]);
353 var currentListener;
354
355 // Call each listener in turn
356 while ((currentListener = listeners.shift())) {
357 currentListener.call(this, evt);
358 }
359 };
360 }
361 node[propertyName].push(listener);
362 }
363 }
364
365 function removeEvent(node, eventName, listener, useCapture) {
366 if (node.removeEventListener) {
367 node.removeEventListener(eventName, listener, useCapture);
368 } else if (node.detachEvent) {
369 node.detachEvent("on" + eventName, listener);
370 } else {
371 var propertyName = getListenersPropertyName(eventName);
372 if (node[propertyName]) {
373 array_remove(node[propertyName], listener);
374 }
375 }
376 }
377
378 function getEvent(evt, win) {
379 win = win ? win : window;
380 return evt ? evt : win.event;
381 }
382
383 function stopEventPropagation(evt) {
384 if (evt.stopPropagation) {
385 evt.stopPropagation();
386 } else if (typeof evt.cancelBubble != "undefined") {
387 evt.cancelBubble = true;
388 }
389 evt.returnValue = false;
390 }
391
392 /* ---------------------------------------------------------------------- */
393 // Simple logging for log4javascript itself
394
395 var logLog = {
396 quietMode: false,
397
398 debugMessages: [],
399
400 setQuietMode: function(quietMode) {
401 this.quietMode = bool(quietMode);
402 },
403
404 numberOfErrors: 0,
405
406 alertAllErrors: false,
407
408 setAlertAllErrors: function(alertAllErrors) {
409 this.alertAllErrors = alertAllErrors;
410 },
411
412 debug: function(message) {
413 this.debugMessages.push(message);
414 },
415
416 displayDebug: function() {
417 alert(this.debugMessages.join(newLine));
418 },
419
420 warn: function(message, exception) {
421 },
422
423 error: function(message, exception) {
424 if (++this.numberOfErrors == 1 || this.alertAllErrors) {
425 if (!this.quietMode) {
426 var alertMessage = "log4javascript error: " + message;
427 if (exception) {
428 alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception);
429 }
430 alert(alertMessage);
431 }
432 }
433 }
434 };
435 log4javascript.logLog = logLog;
436
437 log4javascript.setEventTypes(["load", "error"]);
438
439 function handleError(message, exception) {
440 logLog.error(message, exception);
441 log4javascript.dispatchEvent("error", { "message": message, "exception": exception });
442 }
443
444 log4javascript.handleError = handleError;
445
446 /* ---------------------------------------------------------------------- */
447
448 var enabled = !((typeof log4javascript_disabled != "undefined") &&
449 log4javascript_disabled);
450
451 log4javascript.setEnabled = function(enable) {
452 enabled = bool(enable);
453 };
454
455 log4javascript.isEnabled = function() {
456 return enabled;
457 };
458
459 var useTimeStampsInMilliseconds = true;
460
461 log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) {
462 useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
463 };
464
465 log4javascript.isTimeStampsInMilliseconds = function() {
466 return useTimeStampsInMilliseconds;
467 };
468
469
470 // This evaluates the given expression in the current scope, thus allowing
471 // scripts to access private variables. Particularly useful for testing
472 log4javascript.evalInScope = function(expr) {
473 return eval(expr);
474 };
475
476 var showStackTraces = false;
477
478 log4javascript.setShowStackTraces = function(show) {
479 showStackTraces = bool(show);
480 };
481
482 /* ---------------------------------------------------------------------- */
483 // Levels
484
485 var Level = function(level, name) {
486 this.level = level;
487 this.name = name;
488 };
489
490 Level.prototype = {
491 toString: function() {
492 return this.name;
493 },
494 equals: function(level) {
495 return this.level == level.level;
496 },
497 isGreaterOrEqual: function(level) {
498 return this.level >= level.level;
499 }
500 };
501
502 Level.ALL = new Level(Number.MIN_VALUE, "ALL");
503 Level.TRACE = new Level(10000, "TRACE");
504 Level.DEBUG = new Level(20000, "DEBUG");
505 Level.INFO = new Level(30000, "INFO");
506 Level.WARN = new Level(40000, "WARN");
507 Level.ERROR = new Level(50000, "ERROR");
508 Level.FATAL = new Level(60000, "FATAL");
509 Level.OFF = new Level(Number.MAX_VALUE, "OFF");
510
511 log4javascript.Level = Level;
512
513 /* ---------------------------------------------------------------------- */
514 // Timers
515
516 function Timer(name, level) {
517 this.name = name;
518 this.level = isUndefined(level) ? Level.INFO : level;
519 this.start = new Date();
520 }
521
522 Timer.prototype.getElapsedTime = function() {
523 return new Date().getTime() - this.start.getTime();
524 };
525
526 /* ---------------------------------------------------------------------- */
527 // Loggers
528
529 var anonymousLoggerName = "[anonymous]";
530 var defaultLoggerName = "[default]";
531 var nullLoggerName = "[null]";
532 var rootLoggerName = "root";
533
534 function Logger(name) {
535 this.name = name;
536 this.parent = null;
537 this.children = [];
538
539 var appenders = [];
540 var loggerLevel = null;
541 var isRoot = (this.name === rootLoggerName);
542 var isNull = (this.name === nullLoggerName);
543
544 var appenderCache = null;
545 var appenderCacheInvalidated = false;
546
547 this.addChild = function(childLogger) {
548 this.children.push(childLogger);
549 childLogger.parent = this;
550 childLogger.invalidateAppenderCache();
551 };
552
553 // Additivity
554 var additive = true;
555 this.getAdditivity = function() {
556 return additive;
557 };
558
559 this.setAdditivity = function(additivity) {
560 var valueChanged = (additive != additivity);
561 additive = additivity;
562 if (valueChanged) {
563 this.invalidateAppenderCache();
564 }
565 };
566
567 // Create methods that use the appenders variable in this scope
568 this.addAppender = function(appender) {
569 if (isNull) {
570 handleError("Logger.addAppender: you may not add an appender to the null logger");
571 } else {
572 if (appender instanceof log4javascript.Appender) {
573 if (!array_contains(appenders, appender)) {
574 appenders.push(appender);
575 appender.setAddedToLogger(this);
576 this.invalidateAppenderCache();
577 }
578 } else {
579 handleError("Logger.addAppender: appender supplied ('" +
580 toStr(appender) + "') is not a subclass of Appender");
581 }
582 }
583 };
584
585 this.removeAppender = function(appender) {
586 array_remove(appenders, appender);
587 appender.setRemovedFromLogger(this);
588 this.invalidateAppenderCache();
589 };
590
591 this.removeAllAppenders = function() {
592 var appenderCount = appenders.length;
593 if (appenderCount > 0) {
594 for (var i = 0; i < appenderCount; i++) {
595 appenders[i].setRemovedFromLogger(this);
596 }
597 appenders.length = 0;
598 this.invalidateAppenderCache();
599 }
600 };
601
602 this.getEffectiveAppenders = function() {
603 if (appenderCache === null || appenderCacheInvalidated) {
604 // Build appender cache
605 var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ?
606 [] : this.parent.getEffectiveAppenders();
607 appenderCache = parentEffectiveAppenders.concat(appenders);
608 appenderCacheInvalidated = false;
609 }
610 return appenderCache;
611 };
612
613 this.invalidateAppenderCache = function() {
614 appenderCacheInvalidated = true;
615 for (var i = 0, len = this.children.length; i < len; i++) {
616 this.children[i].invalidateAppenderCache();
617 }
618 };
619
620 this.log = function(level, params) {
621 if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) {
622 // Check whether last param is an exception
623 var exception;
624 var finalParamIndex = params.length - 1;
625 var lastParam = params[finalParamIndex];
626 if (params.length > 1 && isError(lastParam)) {
627 exception = lastParam;
628 finalParamIndex--;
629 }
630
631 // Construct genuine array for the params
632 var messages = [];
633 for (var i = 0; i <= finalParamIndex; i++) {
634 messages[i] = params[i];
635 }
636
637 var loggingEvent = new LoggingEvent(
638 this, new Date(), level, messages, exception);
639
640 this.callAppenders(loggingEvent);
641 }
642 };
643
644 this.callAppenders = function(loggingEvent) {
645 var effectiveAppenders = this.getEffectiveAppenders();
646 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
647 effectiveAppenders[i].doAppend(loggingEvent);
648 }
649 };
650
651 this.setLevel = function(level) {
652 // Having a level of null on the root logger would be very bad.
653 if (isRoot && level === null) {
654 handleError("Logger.setLevel: you cannot set the level of the root logger to null");
655 } else if (level instanceof Level) {
656 loggerLevel = level;
657 } else {
658 handleError("Logger.setLevel: level supplied to logger " +
659 this.name + " is not an instance of log4javascript.Level");
660 }
661 };
662
663 this.getLevel = function() {
664 return loggerLevel;
665 };
666
667 this.getEffectiveLevel = function() {
668 for (var logger = this; logger !== null; logger = logger.parent) {
669 var level = logger.getLevel();
670 if (level !== null) {
671 return level;
672 }
673 }
674 };
675
676 this.group = function(name, initiallyExpanded) {
677 if (enabled) {
678 var effectiveAppenders = this.getEffectiveAppenders();
679 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
680 effectiveAppenders[i].group(name, initiallyExpanded);
681 }
682 }
683 };
684
685 this.groupEnd = function() {
686 if (enabled) {
687 var effectiveAppenders = this.getEffectiveAppenders();
688 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
689 effectiveAppenders[i].groupEnd();
690 }
691 }
692 };
693
694 var timers = {};
695
696 this.time = function(name, level) {
697 if (enabled) {
698 if (isUndefined(name)) {
699 handleError("Logger.time: a name for the timer must be supplied");
700 } else if (level && !(level instanceof Level)) {
701 handleError("Logger.time: level supplied to timer " +
702 name + " is not an instance of log4javascript.Level");
703 } else {
704 timers[name] = new Timer(name, level);
705 }
706 }
707 };
708
709 this.timeEnd = function(name) {
710 if (enabled) {
711 if (isUndefined(name)) {
712 handleError("Logger.timeEnd: a name for the timer must be supplied");
713 } else if (timers[name]) {
714 var timer = timers[name];
715 var milliseconds = timer.getElapsedTime();
716 this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]);
717 delete timers[name];
718 } else {
719 logLog.warn("Logger.timeEnd: no timer found with name " + name);
720 }
721 }
722 };
723
724 this.assert = function(expr) {
725 if (enabled && !expr) {
726 var args = [];
727 for (var i = 1, len = arguments.length; i < len; i++) {
728 args.push(arguments[i]);
729 }
730 args = (args.length > 0) ? args : ["Assertion Failure"];
731 args.push(newLine);
732 args.push(expr);
733 this.log(Level.ERROR, args);
734 }
735 };
736
737 this.toString = function() {
738 return "Logger[" + this.name + "]";
739 };
740 }
741
742 Logger.prototype = {
743 trace: function() {
744 this.log(Level.TRACE, arguments);
745 },
746
747 debug: function() {
748 this.log(Level.DEBUG, arguments);
749 },
750
751 info: function() {
752 this.log(Level.INFO, arguments);
753 },
754
755 warn: function() {
756 this.log(Level.WARN, arguments);
757 },
758
759 error: function() {
760 this.log(Level.ERROR, arguments);
761 },
762
763 fatal: function() {
764 this.log(Level.FATAL, arguments);
765 },
766
767 isEnabledFor: function(level) {
768 return level.isGreaterOrEqual(this.getEffectiveLevel());
769 },
770
771 isTraceEnabled: function() {
772 return this.isEnabledFor(Level.TRACE);
773 },
774
775 isDebugEnabled: function() {
776 return this.isEnabledFor(Level.DEBUG);
777 },
778
779 isInfoEnabled: function() {
780 return this.isEnabledFor(Level.INFO);
781 },
782
783 isWarnEnabled: function() {
784 return this.isEnabledFor(Level.WARN);
785 },
786
787 isErrorEnabled: function() {
788 return this.isEnabledFor(Level.ERROR);
789 },
790
791 isFatalEnabled: function() {
792 return this.isEnabledFor(Level.FATAL);
793 }
794 };
795
796 Logger.prototype.trace.isEntryPoint = true;
797 Logger.prototype.debug.isEntryPoint = true;
798 Logger.prototype.info.isEntryPoint = true;
799 Logger.prototype.warn.isEntryPoint = true;
800 Logger.prototype.error.isEntryPoint = true;
801 Logger.prototype.fatal.isEntryPoint = true;
802
803 /* ---------------------------------------------------------------------- */
804 // Logger access methods
805
806 // Hashtable of loggers keyed by logger name
807 var loggers = {};
808 var loggerNames = [];
809
810 var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG;
811 var rootLogger = new Logger(rootLoggerName);
812 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
813
814 log4javascript.getRootLogger = function() {
815 return rootLogger;
816 };
817
818 log4javascript.getLogger = function(loggerName) {
819 // Use default logger if loggerName is not specified or invalid
820 if (!(typeof loggerName == "string")) {
821 loggerName = anonymousLoggerName;
822 logLog.warn("log4javascript.getLogger: non-string logger name " +
823 toStr(loggerName) + " supplied, returning anonymous logger");
824 }
825
826 // Do not allow retrieval of the root logger by name
827 if (loggerName == rootLoggerName) {
828 handleError("log4javascript.getLogger: root logger may not be obtained by name");
829 }
830
831 // Create the logger for this name if it doesn't already exist
832 if (!loggers[loggerName]) {
833 var logger = new Logger(loggerName);
834 loggers[loggerName] = logger;
835 loggerNames.push(loggerName);
836
837 // Set up parent logger, if it doesn't exist
838 var lastDotIndex = loggerName.lastIndexOf(".");
839 var parentLogger;
840 if (lastDotIndex > -1) {
841 var parentLoggerName = loggerName.substring(0, lastDotIndex);
842 parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc.
843 } else {
844 parentLogger = rootLogger;
845 }
846 parentLogger.addChild(logger);
847 }
848 return loggers[loggerName];
849 };
850
851 var defaultLogger = null;
852 log4javascript.getDefaultLogger = function() {
853 if (!defaultLogger) {
854 defaultLogger = log4javascript.getLogger(defaultLoggerName);
855 var a = new log4javascript.PopUpAppender();
856 defaultLogger.addAppender(a);
857 }
858 return defaultLogger;
859 };
860
861 var nullLogger = null;
862 log4javascript.getNullLogger = function() {
863 if (!nullLogger) {
864 nullLogger = new Logger(nullLoggerName);
865 nullLogger.setLevel(Level.OFF);
866 }
867 return nullLogger;
868 };
869
870 // Destroys all loggers
871 log4javascript.resetConfiguration = function() {
872 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
873 loggers = {};
874 };
875
876 /* ---------------------------------------------------------------------- */
877 // Logging events
878
879 var LoggingEvent = function(logger, timeStamp, level, messages,
880 exception) {
881 this.logger = logger;
882 this.timeStamp = timeStamp;
883 this.timeStampInMilliseconds = timeStamp.getTime();
884 this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000);
885 this.milliseconds = this.timeStamp.getMilliseconds();
886 this.level = level;
887 this.messages = messages;
888 this.exception = exception;
889 };
890
891 LoggingEvent.prototype = {
892 getThrowableStrRep: function() {
893 return this.exception ?
894 getExceptionStringRep(this.exception) : "";
895 },
896 getCombinedMessages: function() {
897 return (this.messages.length == 1) ? this.messages[0] :
898 this.messages.join(newLine);
899 },
900 toString: function() {
901 return "LoggingEvent[" + this.level + "]";
902 }
903 };
904
905 log4javascript.LoggingEvent = LoggingEvent;
906
907 /* ---------------------------------------------------------------------- */
908 // Layout prototype
909
910 var Layout = function() {
911 };
912
913 Layout.prototype = {
914 defaults: {
915 loggerKey: "logger",
916 timeStampKey: "timestamp",
917 millisecondsKey: "milliseconds",
918 levelKey: "level",
919 messageKey: "message",
920 exceptionKey: "exception",
921 urlKey: "url"
922 },
923 loggerKey: "logger",
924 timeStampKey: "timestamp",
925 millisecondsKey: "milliseconds",
926 levelKey: "level",
927 messageKey: "message",
928 exceptionKey: "exception",
929 urlKey: "url",
930 batchHeader: "",
931 batchFooter: "",
932 batchSeparator: "",
933 returnsPostData: false,
934 overrideTimeStampsSetting: false,
935 useTimeStampsInMilliseconds: null,
936
937 format: function() {
938 handleError("Layout.format: layout supplied has no format() method");
939 },
940
941 ignoresThrowable: function() {
942 handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");
943 },
944
945 getContentType: function() {
946 return "text/plain";
947 },
948
949 allowBatching: function() {
950 return true;
951 },
952
953 setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) {
954 this.overrideTimeStampsSetting = true;
955 this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
956 },
957
958 isTimeStampsInMilliseconds: function() {
959 return this.overrideTimeStampsSetting ?
960 this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds;
961 },
962
963 getTimeStampValue: function(loggingEvent) {
964 return this.isTimeStampsInMilliseconds() ?
965 loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds;
966 },
967
968 getDataValues: function(loggingEvent, combineMessages) {
969 var dataValues = [
970 [this.loggerKey, loggingEvent.logger.name],
971 [this.timeStampKey, this.getTimeStampValue(loggingEvent)],
972 [this.levelKey, loggingEvent.level.name],
973 [this.urlKey, window.location.href],
974 [this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages]
975 ];
976 if (!this.isTimeStampsInMilliseconds()) {
977 dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]);
978 }
979 if (loggingEvent.exception) {
980 dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]);
981 }
982 if (this.hasCustomFields()) {
983 for (var i = 0, len = this.customFields.length; i < len; i++) {
984 var val = this.customFields[i].value;
985
986 // Check if the value is a function. If so, execute it, passing it the
987 // current layout and the logging event
988 if (typeof val === "function") {
989 val = val(this, loggingEvent);
990 }
991 dataValues.push([this.customFields[i].name, val]);
992 }
993 }
994 return dataValues;
995 },
996
997 setKeys: function(loggerKey, timeStampKey, levelKey, messageKey,
998 exceptionKey, urlKey, millisecondsKey) {
999 this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey);
1000 this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey);
1001 this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey);
1002 this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey);
1003 this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey);
1004 this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey);
1005 this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey);
1006 },
1007
1008 setCustomField: function(name, value) {
1009 var fieldUpdated = false;
1010 for (var i = 0, len = this.customFields.length; i < len; i++) {
1011 if (this.customFields[i].name === name) {
1012 this.customFields[i].value = value;
1013 fieldUpdated = true;
1014 }
1015 }
1016 if (!fieldUpdated) {
1017 this.customFields.push({"name": name, "value": value});
1018 }
1019 },
1020
1021 hasCustomFields: function() {
1022 return (this.customFields.length > 0);
1023 },
1024
1025 toString: function() {
1026 handleError("Layout.toString: all layouts must override this method");
1027 }
1028 };
1029
1030 log4javascript.Layout = Layout;
1031
1032 /* ---------------------------------------------------------------------- */
1033 // Appender prototype
1034
1035 var Appender = function() {};
1036
1037 Appender.prototype = new EventSupport();
1038
1039 Appender.prototype.layout = new PatternLayout();
1040 Appender.prototype.threshold = Level.ALL;
1041 Appender.prototype.loggers = [];
1042
1043 // Performs threshold checks before delegating actual logging to the
1044 // subclass's specific append method.
1045 Appender.prototype.doAppend = function(loggingEvent) {
1046 if (enabled && loggingEvent.level.level >= this.threshold.level) {
1047 this.append(loggingEvent);
1048 }
1049 };
1050
1051 Appender.prototype.append = function(loggingEvent) {};
1052
1053 Appender.prototype.setLayout = function(layout) {
1054 if (layout instanceof Layout) {
1055 this.layout = layout;
1056 } else {
1057 handleError("Appender.setLayout: layout supplied to " +
1058 this.toString() + " is not a subclass of Layout");
1059 }
1060 };
1061
1062 Appender.prototype.getLayout = function() {
1063 return this.layout;
1064 };
1065
1066 Appender.prototype.setThreshold = function(threshold) {
1067 if (threshold instanceof Level) {
1068 this.threshold = threshold;
1069 } else {
1070 handleError("Appender.setThreshold: threshold supplied to " +
1071 this.toString() + " is not a subclass of Level");
1072 }
1073 };
1074
1075 Appender.prototype.getThreshold = function() {
1076 return this.threshold;
1077 };
1078
1079 Appender.prototype.setAddedToLogger = function(logger) {
1080 this.loggers.push(logger);
1081 };
1082
1083 Appender.prototype.setRemovedFromLogger = function(logger) {
1084 array_remove(this.loggers, logger);
1085 };
1086
1087 Appender.prototype.group = emptyFunction;
1088 Appender.prototype.groupEnd = emptyFunction;
1089
1090 Appender.prototype.toString = function() {
1091 handleError("Appender.toString: all appenders must override this method");
1092 };
1093
1094 log4javascript.Appender = Appender;
1095
1096 /* ---------------------------------------------------------------------- */
1097 // SimpleLayout
1098
1099 function SimpleLayout() {
1100 this.customFields = [];
1101 }
1102
1103 SimpleLayout.prototype = new Layout();
1104
1105 SimpleLayout.prototype.format = function(loggingEvent) {
1106 return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages();
1107 };
1108
1109 SimpleLayout.prototype.ignoresThrowable = function() {
1110 return true;
1111 };
1112
1113 SimpleLayout.prototype.toString = function() {
1114 return "SimpleLayout";
1115 };
1116
1117 log4javascript.SimpleLayout = SimpleLayout;
1118 /* ----------------------------------------------------------------------- */
1119 // NullLayout
1120
1121 function NullLayout() {
1122 this.customFields = [];
1123 }
1124
1125 NullLayout.prototype = new Layout();
1126
1127 NullLayout.prototype.format = function(loggingEvent) {
1128 return loggingEvent.messages;
1129 };
1130
1131 NullLayout.prototype.ignoresThrowable = function() {
1132 return true;
1133 };
1134
1135 NullLayout.prototype.toString = function() {
1136 return "NullLayout";
1137 };
1138
1139 log4javascript.NullLayout = NullLayout;
1140/* ---------------------------------------------------------------------- */
1141 // XmlLayout
1142
1143 function XmlLayout(combineMessages) {
1144 this.combineMessages = extractBooleanFromParam(combineMessages, true);
1145 this.customFields = [];
1146 }
1147
1148 XmlLayout.prototype = new Layout();
1149
1150 XmlLayout.prototype.isCombinedMessages = function() {
1151 return this.combineMessages;
1152 };
1153
1154 XmlLayout.prototype.getContentType = function() {
1155 return "text/xml";
1156 };
1157
1158 XmlLayout.prototype.escapeCdata = function(str) {
1159 return str.replace(/\]\]>/, "]]>]]&gt;<![CDATA[");
1160 };
1161
1162 XmlLayout.prototype.format = function(loggingEvent) {
1163 var layout = this;
1164 var i, len;
1165 function formatMessage(message) {
1166 message = (typeof message === "string") ? message : toStr(message);
1167 return "<log4javascript:message><![CDATA[" +
1168 layout.escapeCdata(message) + "]]></log4javascript:message>";
1169 }
1170
1171 var str = "<log4javascript:event logger=\"" + loggingEvent.logger.name +
1172 "\" timestamp=\"" + this.getTimeStampValue(loggingEvent) + "\"";
1173 if (!this.isTimeStampsInMilliseconds()) {
1174 str += " milliseconds=\"" + loggingEvent.milliseconds + "\"";
1175 }
1176 str += " level=\"" + loggingEvent.level.name + "\">" + newLine;
1177 if (this.combineMessages) {
1178 str += formatMessage(loggingEvent.getCombinedMessages());
1179 } else {
1180 str += "<log4javascript:messages>" + newLine;
1181 for (i = 0, len = loggingEvent.messages.length; i < len; i++) {
1182 str += formatMessage(loggingEvent.messages[i]) + newLine;
1183 }
1184 str += "</log4javascript:messages>" + newLine;
1185 }
1186 if (this.hasCustomFields()) {
1187 for (i = 0, len = this.customFields.length; i < len; i++) {
1188 str += "<log4javascript:customfield name=\"" +
1189 this.customFields[i].name + "\"><![CDATA[" +
1190 this.customFields[i].value.toString() +
1191 "]]></log4javascript:customfield>" + newLine;
1192 }
1193 }
1194 if (loggingEvent.exception) {
1195 str += "<log4javascript:exception><![CDATA[" +
1196 getExceptionStringRep(loggingEvent.exception) +
1197 "]]></log4javascript:exception>" + newLine;
1198 }
1199 str += "</log4javascript:event>" + newLine + newLine;
1200 return str;
1201 };
1202
1203 XmlLayout.prototype.ignoresThrowable = function() {
1204 return false;
1205 };
1206
1207 XmlLayout.prototype.toString = function() {
1208 return "XmlLayout";
1209 };
1210
1211 log4javascript.XmlLayout = XmlLayout;
1212 /* ---------------------------------------------------------------------- */
1213 // JsonLayout related
1214
1215 function escapeNewLines(str) {
1216 return str.replace(/\r\n|\r|\n/g, "\\r\\n");
1217 }
1218
1219 function JsonLayout(readable, combineMessages) {
1220 this.readable = extractBooleanFromParam(readable, false);
1221 this.combineMessages = extractBooleanFromParam(combineMessages, true);
1222 this.batchHeader = this.readable ? "[" + newLine : "[";
1223 this.batchFooter = this.readable ? "]" + newLine : "]";
1224 this.batchSeparator = this.readable ? "," + newLine : ",";
1225 this.setKeys();
1226 this.colon = this.readable ? ": " : ":";
1227 this.tab = this.readable ? "\t" : "";
1228 this.lineBreak = this.readable ? newLine : "";
1229 this.customFields = [];
1230 }
1231
1232 /* ---------------------------------------------------------------------- */
1233 // JsonLayout
1234
1235 JsonLayout.prototype = new Layout();
1236
1237 JsonLayout.prototype.isReadable = function() {
1238 return this.readable;
1239 };
1240
1241 JsonLayout.prototype.isCombinedMessages = function() {
1242 return this.combineMessages;
1243 };
1244
1245 JsonLayout.prototype.format = function(loggingEvent) {
1246 var layout = this;
1247 var dataValues = this.getDataValues(loggingEvent, this.combineMessages);
1248 var str = "{" + this.lineBreak;
1249 var i, len;
1250
1251 function formatValue(val, prefix, expand) {
1252 // Check the type of the data value to decide whether quotation marks
1253 // or expansion are required
1254 var formattedValue;
1255 var valType = typeof val;
1256 if (val instanceof Date) {
1257 formattedValue = String(val.getTime());
1258 } else if (expand && (val instanceof Array)) {
1259 formattedValue = "[" + layout.lineBreak;
1260 for (var i = 0, len = val.length; i < len; i++) {
1261 var childPrefix = prefix + layout.tab;
1262 formattedValue += childPrefix + formatValue(val[i], childPrefix, false);
1263 if (i < val.length - 1) {
1264 formattedValue += ",";
1265 }
1266 formattedValue += layout.lineBreak;
1267 }
1268 formattedValue += prefix + "]";
1269 } else if (valType !== "number" && valType !== "boolean") {
1270 formattedValue = "\"" + escapeNewLines(toStr(val).replace(/\"/g, "\\\"")) + "\"";
1271 } else {
1272 formattedValue = val;
1273 }
1274 return formattedValue;
1275 }
1276
1277 for (i = 0, len = dataValues.length - 1; i <= len; i++) {
1278 str += this.tab + "\"" + dataValues[i][0] + "\"" + this.colon + formatValue(dataValues[i][1], this.tab, true);
1279 if (i < len) {
1280 str += ",";
1281 }
1282 str += this.lineBreak;
1283 }
1284
1285 str += "}" + this.lineBreak;
1286 return str;
1287 };
1288
1289 JsonLayout.prototype.ignoresThrowable = function() {
1290 return false;
1291 };
1292
1293 JsonLayout.prototype.toString = function() {
1294 return "JsonLayout";
1295 };
1296
1297 JsonLayout.prototype.getContentType = function() {
1298 return "application/json";
1299 };
1300
1301 log4javascript.JsonLayout = JsonLayout;
1302 /* ---------------------------------------------------------------------- */
1303 // HttpPostDataLayout
1304
1305 function HttpPostDataLayout() {
1306 this.setKeys();
1307 this.customFields = [];
1308 this.returnsPostData = true;
1309 }
1310
1311 HttpPostDataLayout.prototype = new Layout();
1312
1313 // Disable batching
1314 HttpPostDataLayout.prototype.allowBatching = function() {
1315 return false;
1316 };
1317
1318 HttpPostDataLayout.prototype.format = function(loggingEvent) {
1319 var dataValues = this.getDataValues(loggingEvent);
1320 var queryBits = [];
1321 for (var i = 0, len = dataValues.length; i < len; i++) {
1322 var val = (dataValues[i][1] instanceof Date) ?
1323 String(dataValues[i][1].getTime()) : dataValues[i][1];
1324 queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(val));
1325 }
1326 return queryBits.join("&");
1327 };
1328
1329 HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) {
1330 return false;
1331 };
1332
1333 HttpPostDataLayout.prototype.toString = function() {
1334 return "HttpPostDataLayout";
1335 };
1336
1337 log4javascript.HttpPostDataLayout = HttpPostDataLayout;
1338 /* ---------------------------------------------------------------------- */
1339 // formatObjectExpansion
1340
1341 function formatObjectExpansion(obj, depth, indentation) {
1342 var objectsExpanded = [];
1343
1344 function doFormat(obj, depth, indentation) {
1345 var i, j, len, childDepth, childIndentation, childLines, expansion,
1346 childExpansion;
1347
1348 if (!indentation) {
1349 indentation = "";
1350 }
1351
1352 function formatString(text) {
1353 var lines = splitIntoLines(text);
1354 for (var j = 1, jLen = lines.length; j < jLen; j++) {
1355 lines[j] = indentation + lines[j];
1356 }
1357 return lines.join(newLine);
1358 }
1359
1360 if (obj === null) {
1361 return "null";
1362 } else if (typeof obj == "undefined") {
1363 return "undefined";
1364 } else if (typeof obj == "string") {
1365 return formatString(obj);
1366 } else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) {
1367 try {
1368 expansion = toStr(obj);
1369 } catch (ex) {
1370 expansion = "Error formatting property. Details: " + getExceptionStringRep(ex);
1371 }
1372 return expansion + " [already expanded]";
1373 } else if ((obj instanceof Array) && depth > 0) {
1374 objectsExpanded.push(obj);
1375 expansion = "[" + newLine;
1376 childDepth = depth - 1;
1377 childIndentation = indentation + " ";
1378 childLines = [];
1379 for (i = 0, len = obj.length; i < len; i++) {
1380 try {
1381 childExpansion = doFormat(obj[i], childDepth, childIndentation);
1382 childLines.push(childIndentation + childExpansion);
1383 } catch (ex) {
1384 childLines.push(childIndentation + "Error formatting array member. Details: " +
1385 getExceptionStringRep(ex) + "");
1386 }
1387 }
1388 expansion += childLines.join("," + newLine) + newLine + indentation + "]";
1389 return expansion;
1390 } else if (Object.prototype.toString.call(obj) == "[object Date]") {
1391 return obj.toString();
1392 } else if (typeof obj == "object" && depth > 0) {
1393 objectsExpanded.push(obj);
1394 expansion = "{" + newLine;
1395 childDepth = depth - 1;
1396 childIndentation = indentation + " ";
1397 childLines = [];
1398 for (i in obj) {
1399 try {
1400 childExpansion = doFormat(obj[i], childDepth, childIndentation);
1401 childLines.push(childIndentation + i + ": " + childExpansion);
1402 } catch (ex) {
1403 childLines.push(childIndentation + i + ": Error formatting property. Details: " +
1404 getExceptionStringRep(ex));
1405 }
1406 }
1407 expansion += childLines.join("," + newLine) + newLine + indentation + "}";
1408 return expansion;
1409 } else {
1410 return formatString(toStr(obj));
1411 }
1412 }
1413 return doFormat(obj, depth, indentation);
1414 }
1415 /* ---------------------------------------------------------------------- */
1416 // Date-related stuff
1417
1418 var SimpleDateFormat;
1419
1420 (function() {
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']+)/;
1422 var monthNames = ["January", "February", "March", "April", "May", "June",
1423 "July", "August", "September", "October", "November", "December"];
1424 var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1425 var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
1426 var types = {
1427 G : TEXT2,
1428 y : YEAR,
1429 M : MONTH,
1430 w : NUMBER,
1431 W : NUMBER,
1432 D : NUMBER,
1433 d : NUMBER,
1434 F : NUMBER,
1435 E : TEXT3,
1436 a : TEXT2,
1437 H : NUMBER,
1438 k : NUMBER,
1439 K : NUMBER,
1440 h : NUMBER,
1441 m : NUMBER,
1442 s : NUMBER,
1443 S : NUMBER,
1444 Z : TIMEZONE
1445 };
1446 var ONE_DAY = 24 * 60 * 60 * 1000;
1447 var ONE_WEEK = 7 * ONE_DAY;
1448 var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;
1449
1450 var newDateAtMidnight = function(year, month, day) {
1451 var d = new Date(year, month, day, 0, 0, 0);
1452 d.setMilliseconds(0);
1453 return d;
1454 };
1455
1456 Date.prototype.getDifference = function(date) {
1457 return this.getTime() - date.getTime();
1458 };
1459
1460 Date.prototype.isBefore = function(d) {
1461 return this.getTime() < d.getTime();
1462 };
1463
1464 Date.prototype.getUTCTime = function() {
1465 return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
1466 this.getSeconds(), this.getMilliseconds());
1467 };
1468
1469 Date.prototype.getTimeSince = function(d) {
1470 return this.getUTCTime() - d.getUTCTime();
1471 };
1472
1473 Date.prototype.getPreviousSunday = function() {
1474 // Using midday avoids any possibility of DST messing things up
1475 var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
1476 var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
1477 return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
1478 previousSunday.getDate());
1479 };
1480
1481 Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
1482 if (isUndefined(this.minimalDaysInFirstWeek)) {
1483 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
1484 }
1485 var previousSunday = this.getPreviousSunday();
1486 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
1487 var numberOfSundays = previousSunday.isBefore(startOfYear) ?
1488 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
1489 var numberOfDaysInFirstWeek = 7 - startOfYear.getDay();
1490 var weekInYear = numberOfSundays;
1491 if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
1492 weekInYear--;
1493 }
1494 return weekInYear;
1495 };
1496
1497 Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
1498 if (isUndefined(this.minimalDaysInFirstWeek)) {
1499 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
1500 }
1501 var previousSunday = this.getPreviousSunday();
1502 var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
1503 var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
1504 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK);
1505 var numberOfDaysInFirstWeek = 7 - startOfMonth.getDay();
1506 var weekInMonth = numberOfSundays;
1507 if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
1508 weekInMonth++;
1509 }
1510 return weekInMonth;
1511 };
1512
1513 Date.prototype.getDayInYear = function() {
1514 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
1515 return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
1516 };
1517
1518 /* ------------------------------------------------------------------ */
1519
1520 SimpleDateFormat = function(formatString) {
1521 this.formatString = formatString;
1522 };
1523
1524 /**
1525 * Sets the minimum number of days in a week in order for that week to
1526 * be considered as belonging to a particular month or year
1527 */
1528 SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
1529 this.minimalDaysInFirstWeek = days;
1530 };
1531
1532 SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() {
1533 return isUndefined(this.minimalDaysInFirstWeek) ?
1534 DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
1535 };
1536
1537 var padWithZeroes = function(str, len) {
1538 while (str.length < len) {
1539 str = "0" + str;
1540 }
1541 return str;
1542 };
1543
1544 var formatText = function(data, numberOfLetters, minLength) {
1545 return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
1546 };
1547
1548 var formatNumber = function(data, numberOfLetters) {
1549 var dataString = "" + data;
1550 // Pad with 0s as necessary
1551 return padWithZeroes(dataString, numberOfLetters);
1552 };
1553
1554 SimpleDateFormat.prototype.format = function(date) {
1555 var formattedString = "";
1556 var result;
1557 var searchString = this.formatString;
1558 while ((result = regex.exec(searchString))) {
1559 var quotedString = result[1];
1560 var patternLetters = result[2];
1561 var otherLetters = result[3];
1562 var otherCharacters = result[4];
1563
1564 // If the pattern matched is quoted string, output the text between the quotes
1565 if (quotedString) {
1566 if (quotedString == "''") {
1567 formattedString += "'";
1568 } else {
1569 formattedString += quotedString.substring(1, quotedString.length - 1);
1570 }
1571 } else if (otherLetters) {
1572 // Swallow non-pattern letters by doing nothing here
1573 } else if (otherCharacters) {
1574 // Simply output other characters
1575 formattedString += otherCharacters;
1576 } else if (patternLetters) {
1577 // Replace pattern letters
1578 var patternLetter = patternLetters.charAt(0);
1579 var numberOfLetters = patternLetters.length;
1580 var rawData = "";
1581 switch(patternLetter) {
1582 case "G":
1583 rawData = "AD";
1584 break;
1585 case "y":
1586 rawData = date.getFullYear();
1587 break;
1588 case "M":
1589 rawData = date.getMonth();
1590 break;
1591 case "w":
1592 rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
1593 break;
1594 case "W":
1595 rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
1596 break;
1597 case "D":
1598 rawData = date.getDayInYear();
1599 break;
1600 case "d":
1601 rawData = date.getDate();
1602 break;
1603 case "F":
1604 rawData = 1 + Math.floor((date.getDate() - 1) / 7);
1605 break;
1606 case "E":
1607 rawData = dayNames[date.getDay()];
1608 break;
1609 case "a":
1610 rawData = (date.getHours() >= 12) ? "PM" : "AM";
1611 break;
1612 case "H":
1613 rawData = date.getHours();
1614 break;
1615 case "k":
1616 rawData = date.getHours() || 24;
1617 break;
1618 case "K":
1619 rawData = date.getHours() % 12;
1620 break;
1621 case "h":
1622 rawData = (date.getHours() % 12) || 12;
1623 break;
1624 case "m":
1625 rawData = date.getMinutes();
1626 break;
1627 case "s":
1628 rawData = date.getSeconds();
1629 break;
1630 case "S":
1631 rawData = date.getMilliseconds();
1632 break;
1633 case "Z":
1634 rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time.
1635 break;
1636 }
1637 // Format the raw data depending on the type
1638 switch(types[patternLetter]) {
1639 case TEXT2:
1640 formattedString += formatText(rawData, numberOfLetters, 2);
1641 break;
1642 case TEXT3:
1643 formattedString += formatText(rawData, numberOfLetters, 3);
1644 break;
1645 case NUMBER:
1646 formattedString += formatNumber(rawData, numberOfLetters);
1647 break;
1648 case YEAR:
1649 if (numberOfLetters <= 3) {
1650 // Output a 2-digit year
1651 var dataString = "" + rawData;
1652 formattedString += dataString.substr(2, 2);
1653 } else {
1654 formattedString += formatNumber(rawData, numberOfLetters);
1655 }
1656 break;
1657 case MONTH:
1658 if (numberOfLetters >= 3) {
1659 formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
1660 } else {
1661 // NB. Months returned by getMonth are zero-based
1662 formattedString += formatNumber(rawData + 1, numberOfLetters);
1663 }
1664 break;
1665 case TIMEZONE:
1666 var isPositive = (rawData > 0);
1667 // The following line looks like a mistake but isn't
1668 // because of the way getTimezoneOffset measures.
1669 var prefix = isPositive ? "-" : "+";
1670 var absData = Math.abs(rawData);
1671
1672 // Hours
1673 var hours = "" + Math.floor(absData / 60);
1674 hours = padWithZeroes(hours, 2);
1675 // Minutes
1676 var minutes = "" + (absData % 60);
1677 minutes = padWithZeroes(minutes, 2);
1678
1679 formattedString += prefix + hours + minutes;
1680 break;
1681 }
1682 }
1683 searchString = searchString.substr(result.index + result[0].length);
1684 }
1685 return formattedString;
1686 };
1687 })();
1688
1689 log4javascript.SimpleDateFormat = SimpleDateFormat;
1690
1691 /* ---------------------------------------------------------------------- */
1692 // PatternLayout
1693
1694 function PatternLayout(pattern) {
1695 if (pattern) {
1696 this.pattern = pattern;
1697 } else {
1698 this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
1699 }
1700 this.customFields = [];
1701 }
1702
1703 PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
1704 PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n";
1705 PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
1706 PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS";
1707 PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS";
1708
1709 PatternLayout.prototype = new Layout();
1710
1711 PatternLayout.prototype.format = function(loggingEvent) {
1712 var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;
1713 var formattedString = "";
1714 var result;
1715 var searchString = this.pattern;
1716
1717 // Cannot use regex global flag since it doesn't work with exec in IE5
1718 while ((result = regex.exec(searchString))) {
1719 var matchedString = result[0];
1720 var padding = result[1];
1721 var truncation = result[2];
1722 var conversionCharacter = result[3];
1723 var specifier = result[5];
1724 var text = result[6];
1725
1726 // Check if the pattern matched was just normal text
1727 if (text) {
1728 formattedString += "" + text;
1729 } else {
1730 // Create a raw replacement string based on the conversion
1731 // character and specifier
1732 var replacement = "";
1733 switch(conversionCharacter) {
1734 case "a": // Array of messages
1735 case "m": // Message
1736 var depth = 0;
1737 if (specifier) {
1738 depth = parseInt(specifier, 10);
1739 if (isNaN(depth)) {
1740 handleError("PatternLayout.format: invalid specifier '" +
1741 specifier + "' for conversion character '" + conversionCharacter +
1742 "' - should be a number");
1743 depth = 0;
1744 }
1745 }
1746 var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages;
1747 for (var i = 0, len = messages.length; i < len; i++) {
1748 if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) {
1749 replacement += " ";
1750 }
1751 if (depth === 0) {
1752 replacement += messages[i];
1753 } else {
1754 replacement += formatObjectExpansion(messages[i], depth);
1755 }
1756 }
1757 break;
1758 case "c": // Logger name
1759 var loggerName = loggingEvent.logger.name;
1760 if (specifier) {
1761 var precision = parseInt(specifier, 10);
1762 var loggerNameBits = loggingEvent.logger.name.split(".");
1763 if (precision >= loggerNameBits.length) {
1764 replacement = loggerName;
1765 } else {
1766 replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
1767 }
1768 } else {
1769 replacement = loggerName;
1770 }
1771 break;
1772 case "d": // Date
1773 var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
1774 if (specifier) {
1775 dateFormat = specifier;
1776 // Pick up special cases
1777 if (dateFormat == "ISO8601") {
1778 dateFormat = PatternLayout.ISO8601_DATEFORMAT;
1779 } else if (dateFormat == "ABSOLUTE") {
1780 dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
1781 } else if (dateFormat == "DATE") {
1782 dateFormat = PatternLayout.DATETIME_DATEFORMAT;
1783 }
1784 }
1785 // Format the date
1786 replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);
1787 break;
1788 case "f": // Custom field
1789 if (this.hasCustomFields()) {
1790 var fieldIndex = 0;
1791 if (specifier) {
1792 fieldIndex = parseInt(specifier, 10);
1793 if (isNaN(fieldIndex)) {
1794 handleError("PatternLayout.format: invalid specifier '" +
1795 specifier + "' for conversion character 'f' - should be a number");
1796 } else if (fieldIndex === 0) {
1797 handleError("PatternLayout.format: invalid specifier '" +
1798 specifier + "' for conversion character 'f' - must be greater than zero");
1799 } else if (fieldIndex > this.customFields.length) {
1800 handleError("PatternLayout.format: invalid specifier '" +
1801 specifier + "' for conversion character 'f' - there aren't that many custom fields");
1802 } else {
1803 fieldIndex = fieldIndex - 1;
1804 }
1805 }
1806 var val = this.customFields[fieldIndex].value;
1807 if (typeof val == "function") {
1808 val = val(this, loggingEvent);
1809 }
1810 replacement = val;
1811 }
1812 break;
1813 case "n": // New line
1814 replacement = newLine;
1815 break;
1816 case "p": // Level
1817 replacement = loggingEvent.level.name;
1818 break;
1819 case "r": // Milliseconds since log4javascript startup
1820 replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate);
1821 break;
1822 case "%": // Literal % sign
1823 replacement = "%";
1824 break;
1825 default:
1826 replacement = matchedString;
1827 break;
1828 }
1829 // Format the replacement according to any padding or
1830 // truncation specified
1831 var l;
1832
1833 // First, truncation
1834 if (truncation) {
1835 l = parseInt(truncation.substr(1), 10);
1836 var strLen = replacement.length;
1837 if (l < strLen) {
1838 replacement = replacement.substring(strLen - l, strLen);
1839 }
1840 }
1841 // Next, padding
1842 if (padding) {
1843 if (padding.charAt(0) == "-") {
1844 l = parseInt(padding.substr(1), 10);
1845 // Right pad with spaces
1846 while (replacement.length < l) {
1847 replacement += " ";
1848 }
1849 } else {
1850 l = parseInt(padding, 10);
1851 // Left pad with spaces
1852 while (replacement.length < l) {
1853 replacement = " " + replacement;
1854 }
1855 }
1856 }
1857 formattedString += replacement;
1858 }
1859 searchString = searchString.substr(result.index + result[0].length);
1860 }
1861 return formattedString;
1862 };
1863
1864 PatternLayout.prototype.ignoresThrowable = function() {
1865 return true;
1866 };
1867
1868 PatternLayout.prototype.toString = function() {
1869 return "PatternLayout";
1870 };
1871
1872 log4javascript.PatternLayout = PatternLayout;
1873 /* ---------------------------------------------------------------------- */
1874 // AlertAppender
1875
1876 function AlertAppender() {}
1877
1878 AlertAppender.prototype = new Appender();
1879
1880 AlertAppender.prototype.layout = new SimpleLayout();
1881
1882 AlertAppender.prototype.append = function(loggingEvent) {
1883 var formattedMessage = this.getLayout().format(loggingEvent);
1884 if (this.getLayout().ignoresThrowable()) {
1885 formattedMessage += loggingEvent.getThrowableStrRep();
1886 }
1887 alert(formattedMessage);
1888 };
1889
1890 AlertAppender.prototype.toString = function() {
1891 return "AlertAppender";
1892 };
1893
1894 log4javascript.AlertAppender = AlertAppender;
1895 /* ---------------------------------------------------------------------- */
1896 // BrowserConsoleAppender (only works in Opera and Safari and Firefox with
1897 // Firebug extension)
1898
1899 function BrowserConsoleAppender() {}
1900
1901 BrowserConsoleAppender.prototype = new log4javascript.Appender();
1902 BrowserConsoleAppender.prototype.layout = new NullLayout();
1903 BrowserConsoleAppender.prototype.threshold = Level.DEBUG;
1904
1905 BrowserConsoleAppender.prototype.append = function(loggingEvent) {
1906 var appender = this;
1907
1908 var getFormattedMessage = function() {
1909 var layout = appender.getLayout();
1910 var formattedMessage = layout.format(loggingEvent);
1911 if (layout.ignoresThrowable() && loggingEvent.exception) {
1912 formattedMessage += loggingEvent.getThrowableStrRep();
1913 }
1914 return formattedMessage;
1915 };
1916
1917 if ((typeof opera != "undefined") && opera.postError) { // Opera
1918 opera.postError(getFormattedMessage());
1919 } else if (window.console && window.console.log) { // Safari and Firebug
1920 var formattedMesage = getFormattedMessage();
1921 // Log to Firebug using its logging methods or revert to the console.log
1922 // method in Safari
1923 if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
1924 window.console.debug(formattedMesage);
1925 } else if (window.console.info && Level.INFO.equals(loggingEvent.level)) {
1926 window.console.info(formattedMesage);
1927 } else if (window.console.warn && Level.WARN.equals(loggingEvent.level)) {
1928 window.console.warn(formattedMesage);
1929 } else if (window.console.error && loggingEvent.level.isGreaterOrEqual(Level.ERROR)) {
1930 window.console.error(formattedMesage);
1931 } else {
1932 window.console.log(formattedMesage);
1933 }
1934 }
1935 };
1936
1937 BrowserConsoleAppender.prototype.group = function(name) {
1938 if (window.console && window.console.group) {
1939 window.console.group(name);
1940 }
1941 };
1942
1943 BrowserConsoleAppender.prototype.groupEnd = function() {
1944 if (window.console && window.console.groupEnd) {
1945 window.console.groupEnd();
1946 }
1947 };
1948
1949 BrowserConsoleAppender.prototype.toString = function() {
1950 return "BrowserConsoleAppender";
1951 };
1952
1953 log4javascript.BrowserConsoleAppender = BrowserConsoleAppender;
1954 /* ---------------------------------------------------------------------- */
1955 // AjaxAppender related
1956
1957 var xmlHttpFactories = [
1958 function() { return new XMLHttpRequest(); },
1959 function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
1960 function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
1961 ];
1962
1963 var getXmlHttp = function(errorHandler) {
1964 // This is only run the first time; the value of getXmlHttp gets
1965 // replaced with the factory that succeeds on the first run
1966 var xmlHttp = null, factory;
1967 for (var i = 0, len = xmlHttpFactories.length; i < len; i++) {
1968 factory = xmlHttpFactories[i];
1969 try {
1970 xmlHttp = factory();
1971 getXmlHttp = factory;
1972 return xmlHttp;
1973 } catch (e) {
1974 }
1975 }
1976 // If we're here, all factories have failed, so throw an error
1977 if (errorHandler) {
1978 errorHandler();
1979 } else {
1980 handleError("getXmlHttp: unable to obtain XMLHttpRequest object");
1981 }
1982 };
1983
1984 function isHttpRequestSuccessful(xmlHttp) {
1985 return isUndefined(xmlHttp.status) || xmlHttp.status === 0 ||
1986 (xmlHttp.status >= 200 && xmlHttp.status < 300) ||
1987 xmlHttp.status == 1223 /* Fix for IE */;
1988 }
1989
1990 /* ---------------------------------------------------------------------- */
1991 // AjaxAppender
1992
1993 function AjaxAppender(url) {
1994 var appender = this;
1995 var isSupported = true;
1996 if (!url) {
1997 handleError("AjaxAppender: URL must be specified in constructor");
1998 isSupported = false;
1999 }
2000
2001 var timed = this.defaults.timed;
2002 var waitForResponse = this.defaults.waitForResponse;
2003 var batchSize = this.defaults.batchSize;
2004 var timerInterval = this.defaults.timerInterval;
2005 var requestSuccessCallback = this.defaults.requestSuccessCallback;
2006 var failCallback = this.defaults.failCallback;
2007 var postVarName = this.defaults.postVarName;
2008 var sendAllOnUnload = this.defaults.sendAllOnUnload;
2009 var contentType = this.defaults.contentType;
2010 var sessionId = null;
2011
2012 var queuedLoggingEvents = [];
2013 var queuedRequests = [];
2014 var headers = [];
2015 var sending = false;
2016 var initialized = false;
2017
2018 // Configuration methods. The function scope is used to prevent
2019 // direct alteration to the appender configuration properties.
2020 function checkCanConfigure(configOptionName) {
2021 if (initialized) {
2022 handleError("AjaxAppender: configuration option '" +
2023 configOptionName +
2024 "' may not be set after the appender has been initialized");
2025 return false;
2026 }
2027 return true;
2028 }
2029
2030 this.getSessionId = function() { return sessionId; };
2031 this.setSessionId = function(sessionIdParam) {
2032 sessionId = extractStringFromParam(sessionIdParam, null);
2033 this.layout.setCustomField("sessionid", sessionId);
2034 };
2035
2036 this.setLayout = function(layoutParam) {
2037 if (checkCanConfigure("layout")) {
2038 this.layout = layoutParam;
2039 // Set the session id as a custom field on the layout, if not already present
2040 if (sessionId !== null) {
2041 this.setSessionId(sessionId);
2042 }
2043 }
2044 };
2045
2046 this.isTimed = function() { return timed; };
2047 this.setTimed = function(timedParam) {
2048 if (checkCanConfigure("timed")) {
2049 timed = bool(timedParam);
2050 }
2051 };
2052
2053 this.getTimerInterval = function() { return timerInterval; };
2054 this.setTimerInterval = function(timerIntervalParam) {
2055 if (checkCanConfigure("timerInterval")) {
2056 timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
2057 }
2058 };
2059
2060 this.isWaitForResponse = function() { return waitForResponse; };
2061 this.setWaitForResponse = function(waitForResponseParam) {
2062 if (checkCanConfigure("waitForResponse")) {
2063 waitForResponse = bool(waitForResponseParam);
2064 }
2065 };
2066
2067 this.getBatchSize = function() { return batchSize; };
2068 this.setBatchSize = function(batchSizeParam) {
2069 if (checkCanConfigure("batchSize")) {
2070 batchSize = extractIntFromParam(batchSizeParam, batchSize);
2071 }
2072 };
2073
2074 this.isSendAllOnUnload = function() { return sendAllOnUnload; };
2075 this.setSendAllOnUnload = function(sendAllOnUnloadParam) {
2076 if (checkCanConfigure("sendAllOnUnload")) {
2077 sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload);
2078 }
2079 };
2080
2081 this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
2082 requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
2083 };
2084
2085 this.setFailCallback = function(failCallbackParam) {
2086 failCallback = extractFunctionFromParam(failCallbackParam, failCallback);
2087 };
2088
2089 this.getPostVarName = function() { return postVarName; };
2090 this.setPostVarName = function(postVarNameParam) {
2091 if (checkCanConfigure("postVarName")) {
2092 postVarName = extractStringFromParam(postVarNameParam, postVarName);
2093 }
2094 };
2095
2096 this.getHeaders = function() { return headers; };
2097 this.addHeader = function(name, value) {
2098 if (name.toLowerCase() == "content-type") {
2099 contentType = value;
2100 } else {
2101 headers.push( { name: name, value: value } );
2102 }
2103 };
2104
2105 // Internal functions
2106 function sendAll() {
2107 if (isSupported && enabled) {
2108 sending = true;
2109 var currentRequestBatch;
2110 if (waitForResponse) {
2111 // Send the first request then use this function as the callback once
2112 // the response comes back
2113 if (queuedRequests.length > 0) {
2114 currentRequestBatch = queuedRequests.shift();
2115 sendRequest(preparePostData(currentRequestBatch), sendAll);
2116 } else {
2117 sending = false;
2118 if (timed) {
2119 scheduleSending();
2120 }
2121 }
2122 } else {
2123 // Rattle off all the requests without waiting to see the response
2124 while ((currentRequestBatch = queuedRequests.shift())) {
2125 sendRequest(preparePostData(currentRequestBatch));
2126 }
2127 sending = false;
2128 if (timed) {
2129 scheduleSending();
2130 }
2131 }
2132 }
2133 }
2134
2135 this.sendAll = sendAll;
2136
2137 // Called when the window unloads. At this point we're past caring about
2138 // waiting for responses or timers or incomplete batches - everything
2139 // must go, now
2140 function sendAllRemaining() {
2141 var sendingAnything = false;
2142 if (isSupported && enabled) {
2143 // Create requests for everything left over, batched as normal
2144 var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1;
2145 var currentLoggingEvent;
2146 var batchedLoggingEvents = [];
2147 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
2148 batchedLoggingEvents.push(currentLoggingEvent);
2149 if (queuedLoggingEvents.length >= actualBatchSize) {
2150 // Queue this batch of log entries
2151 queuedRequests.push(batchedLoggingEvents);
2152 batchedLoggingEvents = [];
2153 }
2154 }
2155 // If there's a partially completed batch, add it
2156 if (batchedLoggingEvents.length > 0) {
2157 queuedRequests.push(batchedLoggingEvents);
2158 }
2159 sendingAnything = (queuedRequests.length > 0);
2160 waitForResponse = false;
2161 timed = false;
2162 sendAll();
2163 }
2164 return sendingAnything;
2165 }
2166
2167 this.sendAllRemaining = sendAllRemaining;
2168
2169 function preparePostData(batchedLoggingEvents) {
2170 // Format the logging events
2171 var formattedMessages = [];
2172 var currentLoggingEvent;
2173 var postData = "";
2174 while ((currentLoggingEvent = batchedLoggingEvents.shift())) {
2175 var currentFormattedMessage = appender.getLayout().format(currentLoggingEvent);
2176 if (appender.getLayout().ignoresThrowable()) {
2177 currentFormattedMessage += currentLoggingEvent.getThrowableStrRep();
2178 }
2179 formattedMessages.push(currentFormattedMessage);
2180 }
2181 // Create the post data string
2182 if (batchedLoggingEvents.length == 1) {
2183 postData = formattedMessages.join("");
2184 } else {
2185 postData = appender.getLayout().batchHeader +
2186 formattedMessages.join(appender.getLayout().batchSeparator) +
2187 appender.getLayout().batchFooter;
2188 }
2189 if (contentType == appender.defaults.contentType) {
2190 postData = appender.getLayout().returnsPostData ? postData :
2191 urlEncode(postVarName) + "=" + urlEncode(postData);
2192 // Add the layout name to the post data
2193 if (postData.length > 0) {
2194 postData += "&";
2195 }
2196 postData += "layout=" + urlEncode(appender.getLayout().toString());
2197 }
2198 return postData;
2199 }
2200
2201 function scheduleSending() {
2202 window.setTimeout(sendAll, timerInterval);
2203 }
2204
2205 function xmlHttpErrorHandler() {
2206 var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";
2207 handleError(msg);
2208 isSupported = false;
2209 if (failCallback) {
2210 failCallback(msg);
2211 }
2212 }
2213
2214 function sendRequest(postData, successCallback) {
2215 try {
2216 var xmlHttp = getXmlHttp(xmlHttpErrorHandler);
2217 if (isSupported) {
2218 if (xmlHttp.overrideMimeType) {
2219 xmlHttp.overrideMimeType(appender.getLayout().getContentType());
2220 }
2221 xmlHttp.onreadystatechange = function() {
2222 if (xmlHttp.readyState == 4) {
2223 if (isHttpRequestSuccessful(xmlHttp)) {
2224 if (requestSuccessCallback) {
2225 requestSuccessCallback(xmlHttp);
2226 }
2227 if (successCallback) {
2228 successCallback(xmlHttp);
2229 }
2230 } else {
2231 var msg = "AjaxAppender.append: XMLHttpRequest request to URL " +
2232 url + " returned status code " + xmlHttp.status;
2233 handleError(msg);
2234 if (failCallback) {
2235 failCallback(msg);
2236 }
2237 }
2238 xmlHttp.onreadystatechange = emptyFunction;
2239 xmlHttp = null;
2240 }
2241 };
2242 xmlHttp.open("POST", url, true);
2243 try {
2244 for (var i = 0, header; header = headers[i++]; ) {
2245 xmlHttp.setRequestHeader(header.name, header.value);
2246 }
2247 xmlHttp.setRequestHeader("Content-Type", contentType);
2248 } catch (headerEx) {
2249 var msg = "AjaxAppender.append: your browser's XMLHttpRequest implementation" +
2250 " does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled";
2251 handleError(msg);
2252 isSupported = false;
2253 if (failCallback) {
2254 failCallback(msg);
2255 }
2256 return;
2257 }
2258 xmlHttp.send(postData);
2259 }
2260 } catch (ex) {
2261 var errMsg = "AjaxAppender.append: error sending log message to " + url;
2262 handleError(errMsg, ex);
2263 isSupported = false;
2264 if (failCallback) {
2265 failCallback(errMsg + ". Details: " + getExceptionStringRep(ex));
2266 }
2267 }
2268 }
2269
2270 this.append = function(loggingEvent) {
2271 if (isSupported) {
2272 if (!initialized) {
2273 init();
2274 }
2275 queuedLoggingEvents.push(loggingEvent);
2276 var actualBatchSize = this.getLayout().allowBatching() ? batchSize : 1;
2277
2278 if (queuedLoggingEvents.length >= actualBatchSize) {
2279 var currentLoggingEvent;
2280 var batchedLoggingEvents = [];
2281 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
2282 batchedLoggingEvents.push(currentLoggingEvent);
2283 }
2284 // Queue this batch of log entries
2285 queuedRequests.push(batchedLoggingEvents);
2286
2287 // If using a timer, the queue of requests will be processed by the
2288 // timer function, so nothing needs to be done here.
2289 if (!timed && (!waitForResponse || (waitForResponse && !sending))) {
2290 sendAll();
2291 }
2292 }
2293 }
2294 };
2295
2296 function init() {
2297 initialized = true;
2298 // Add unload event to send outstanding messages
2299 if (sendAllOnUnload) {
2300 var oldBeforeUnload = window.onbeforeunload;
2301 window.onbeforeunload = function() {
2302 if (oldBeforeUnload) {
2303 oldBeforeUnload();
2304 }
2305 if (sendAllRemaining()) {
2306 return "Sending log messages";
2307 }
2308 };
2309 }
2310 // Start timer
2311 if (timed) {
2312 scheduleSending();
2313 }
2314 }
2315 }
2316
2317 AjaxAppender.prototype = new Appender();
2318
2319 AjaxAppender.prototype.defaults = {
2320 waitForResponse: false,
2321 timed: false,
2322 timerInterval: 1000,
2323 batchSize: 1,
2324 sendAllOnUnload: false,
2325 requestSuccessCallback: null,
2326 failCallback: null,
2327 postVarName: "data",
2328 contentType: "application/x-www-form-urlencoded"
2329 };
2330
2331 AjaxAppender.prototype.layout = new HttpPostDataLayout();
2332
2333 AjaxAppender.prototype.toString = function() {
2334 return "AjaxAppender";
2335 };
2336
2337 log4javascript.AjaxAppender = AjaxAppender;
2338 /* ---------------------------------------------------------------------- */
2339 // PopUpAppender and InPageAppender related
2340
2341 function setCookie(name, value, days, path) {
2342 var expires;
2343 path = path ? "; path=" + path : "";
2344 if (days) {
2345 var date = new Date();
2346 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
2347 expires = "; expires=" + date.toGMTString();
2348 } else {
2349 expires = "";
2350 }
2351 document.cookie = escape(name) + "=" + escape(value) + expires + path;
2352 }
2353
2354 function getCookie(name) {
2355 var nameEquals = escape(name) + "=";
2356 var ca = document.cookie.split(";");
2357 for (var i = 0, len = ca.length; i < len; i++) {
2358 var c = ca[i];
2359 while (c.charAt(0) === " ") {
2360 c = c.substring(1, c.length);
2361 }
2362 if (c.indexOf(nameEquals) === 0) {
2363 return unescape(c.substring(nameEquals.length, c.length));
2364 }
2365 }
2366 return null;
2367 }
2368
2369 // Gets the base URL of the location of the log4javascript script.
2370 // This is far from infallible.
2371 function getBaseUrl() {
2372 var scripts = document.getElementsByTagName("script");
2373 for (var i = 0, len = scripts.length; i < len; ++i) {
2374 if (scripts[i].src.indexOf("log4javascript") != -1) {
2375 var lastSlash = scripts[i].src.lastIndexOf("/");
2376 return (lastSlash == -1) ? "" : scripts[i].src.substr(0, lastSlash + 1);
2377 }
2378 }
2379 return null;
2380 }
2381
2382 function isLoaded(win) {
2383 try {
2384 return bool(win.loaded);
2385 } catch (ex) {
2386 return false;
2387 }
2388 }
2389
2390 /* ---------------------------------------------------------------------- */
2391 // ConsoleAppender (prototype for PopUpAppender and InPageAppender)
2392
2393 var ConsoleAppender;
2394
2395 // Create an anonymous function to protect base console methods
2396 (function() {
2397 var getConsoleHtmlLines = function() {
2398 return [
2399'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
2400'<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">',
2401' <head>',
2402' <title>log4javascript</title>',
2403' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
2404' <!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work -->',
2405' <meta http-equiv="X-UA-Compatible" content="IE=7" />',
2406' <script type="text/javascript">var isIe = false, isIePre7 = false;</script>',
2407' <!--[if IE]><script type="text/javascript">isIe = true</script><![endif]-->',
2408' <!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]-->',
2409' <script type="text/javascript">',
2410' //<![CDATA[',
2411' var loggingEnabled = true;',
2412' var logQueuedEventsTimer = null;',
2413' var logEntries = [];',
2414' var logEntriesAndSeparators = [];',
2415' var logItems = [];',
2416' var renderDelay = 100;',
2417' var unrenderedLogItemsExist = false;',
2418' var rootGroup, currentGroup = null;',
2419' var loaded = false;',
2420' var currentLogItem = null;',
2421' var logMainContainer;',
2422'',
2423' function copyProperties(obj, props) {',
2424' for (var i in props) {',
2425' obj[i] = props[i];',
2426' }',
2427' }',
2428'',
2429' /*----------------------------------------------------------------*/',
2430'',
2431' function LogItem() {',
2432' }',
2433'',
2434' LogItem.prototype = {',
2435' mainContainer: null,',
2436' wrappedContainer: null,',
2437' unwrappedContainer: null,',
2438' group: null,',
2439'',
2440' appendToLog: function() {',
2441' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2442' this.elementContainers[i].appendToLog();',
2443' }',
2444' this.group.update();',
2445' },',
2446'',
2447' doRemove: function(doUpdate, removeFromGroup) {',
2448' if (this.rendered) {',
2449' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2450' this.elementContainers[i].remove();',
2451' }',
2452' this.unwrappedElementContainer = null;',
2453' this.wrappedElementContainer = null;',
2454' this.mainElementContainer = null;',
2455' }',
2456' if (this.group && removeFromGroup) {',
2457' this.group.removeChild(this, doUpdate);',
2458' }',
2459' if (this === currentLogItem) {',
2460' currentLogItem = null;',
2461' }',
2462' },',
2463'',
2464' remove: function(doUpdate, removeFromGroup) {',
2465' this.doRemove(doUpdate, removeFromGroup);',
2466' },',
2467'',
2468' render: function() {},',
2469'',
2470' accept: function(visitor) {',
2471' visitor.visit(this);',
2472' },',
2473'',
2474' getUnwrappedDomContainer: function() {',
2475' return this.group.unwrappedElementContainer.contentDiv;',
2476' },',
2477'',
2478' getWrappedDomContainer: function() {',
2479' return this.group.wrappedElementContainer.contentDiv;',
2480' },',
2481'',
2482' getMainDomContainer: function() {',
2483' return this.group.mainElementContainer.contentDiv;',
2484' }',
2485' };',
2486'',
2487' LogItem.serializedItemKeys = {LOG_ENTRY: 0, GROUP_START: 1, GROUP_END: 2};',
2488'',
2489' /*----------------------------------------------------------------*/',
2490'',
2491' function LogItemContainerElement() {',
2492' }',
2493'',
2494' LogItemContainerElement.prototype = {',
2495' appendToLog: function() {',
2496' var insertBeforeFirst = (newestAtTop && this.containerDomNode.hasChildNodes());',
2497' if (insertBeforeFirst) {',
2498' this.containerDomNode.insertBefore(this.mainDiv, this.containerDomNode.firstChild);',
2499' } else {',
2500' this.containerDomNode.appendChild(this.mainDiv);',
2501' }',
2502' }',
2503' };',
2504'',
2505' /*----------------------------------------------------------------*/',
2506'',
2507' function SeparatorElementContainer(containerDomNode) {',
2508' this.containerDomNode = containerDomNode;',
2509' this.mainDiv = document.createElement("div");',
2510' this.mainDiv.className = "separator";',
2511' this.mainDiv.innerHTML = "&nbsp;";',
2512' }',
2513'',
2514' SeparatorElementContainer.prototype = new LogItemContainerElement();',
2515'',
2516' SeparatorElementContainer.prototype.remove = function() {',
2517' this.mainDiv.parentNode.removeChild(this.mainDiv);',
2518' this.mainDiv = null;',
2519' };',
2520'',
2521' /*----------------------------------------------------------------*/',
2522'',
2523' function Separator() {',
2524' this.rendered = false;',
2525' }',
2526'',
2527' Separator.prototype = new LogItem();',
2528'',
2529' copyProperties(Separator.prototype, {',
2530' render: function() {',
2531' var containerDomNode = this.group.contentDiv;',
2532' if (isIe) {',
2533' this.unwrappedElementContainer = new SeparatorElementContainer(this.getUnwrappedDomContainer());',
2534' this.wrappedElementContainer = new SeparatorElementContainer(this.getWrappedDomContainer());',
2535' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
2536' } else {',
2537' this.mainElementContainer = new SeparatorElementContainer(this.getMainDomContainer());',
2538' this.elementContainers = [this.mainElementContainer];',
2539' }',
2540' this.content = this.formattedMessage;',
2541' this.rendered = true;',
2542' }',
2543' });',
2544'',
2545' /*----------------------------------------------------------------*/',
2546'',
2547' function GroupElementContainer(group, containerDomNode, isRoot, isWrapped) {',
2548' this.group = group;',
2549' this.containerDomNode = containerDomNode;',
2550' this.isRoot = isRoot;',
2551' this.isWrapped = isWrapped;',
2552' this.expandable = false;',
2553'',
2554' if (this.isRoot) {',
2555' if (isIe) {',
2556' this.contentDiv = logMainContainer.appendChild(document.createElement("div"));',
2557' this.contentDiv.id = this.isWrapped ? "log_wrapped" : "log_unwrapped";',
2558' } else {',
2559' this.contentDiv = logMainContainer;',
2560' }',
2561' } else {',
2562' var groupElementContainer = this;',
2563' ',
2564' this.mainDiv = document.createElement("div");',
2565' this.mainDiv.className = "group";',
2566'',
2567' this.headingDiv = this.mainDiv.appendChild(document.createElement("div"));',
2568' this.headingDiv.className = "groupheading";',
2569'',
2570' this.expander = this.headingDiv.appendChild(document.createElement("span"));',
2571' this.expander.className = "expander unselectable greyedout";',
2572' this.expander.unselectable = true;',
2573' var expanderText = this.group.expanded ? "-" : "+";',
2574' this.expanderTextNode = this.expander.appendChild(document.createTextNode(expanderText));',
2575' ',
2576' this.headingDiv.appendChild(document.createTextNode(" " + this.group.name));',
2577'',
2578' this.contentDiv = this.mainDiv.appendChild(document.createElement("div"));',
2579' var contentCssClass = this.group.expanded ? "expanded" : "collapsed";',
2580' this.contentDiv.className = "groupcontent " + contentCssClass;',
2581'',
2582' this.expander.onclick = function() {',
2583' if (groupElementContainer.group.expandable) {',
2584' groupElementContainer.group.toggleExpanded();',
2585' }',
2586' };',
2587' }',
2588' }',
2589'',
2590' GroupElementContainer.prototype = new LogItemContainerElement();',
2591'',
2592' copyProperties(GroupElementContainer.prototype, {',
2593' toggleExpanded: function() {',
2594' if (!this.isRoot) {',
2595' var oldCssClass, newCssClass, expanderText;',
2596' if (this.group.expanded) {',
2597' newCssClass = "expanded";',
2598' oldCssClass = "collapsed";',
2599' expanderText = "-";',
2600' } else {',
2601' newCssClass = "collapsed";',
2602' oldCssClass = "expanded";',
2603' expanderText = "+";',
2604' }',
2605' replaceClass(this.contentDiv, newCssClass, oldCssClass);',
2606' this.expanderTextNode.nodeValue = expanderText;',
2607' }',
2608' },',
2609'',
2610' remove: function() {',
2611' if (!this.isRoot) {',
2612' this.headingDiv = null;',
2613' this.expander.onclick = null;',
2614' this.expander = null;',
2615' this.expanderTextNode = null;',
2616' this.contentDiv = null;',
2617' this.containerDomNode = null;',
2618' this.mainDiv.parentNode.removeChild(this.mainDiv);',
2619' this.mainDiv = null;',
2620' }',
2621' },',
2622'',
2623' reverseChildren: function() {',
2624' // Invert the order of the log entries',
2625' var node = null;',
2626'',
2627' // Remove all the log container nodes',
2628' var childDomNodes = [];',
2629' while ((node = this.contentDiv.firstChild)) {',
2630' this.contentDiv.removeChild(node);',
2631' childDomNodes.push(node);',
2632' }',
2633'',
2634' // Put them all back in reverse order',
2635' while ((node = childDomNodes.pop())) {',
2636' this.contentDiv.appendChild(node);',
2637' }',
2638' },',
2639'',
2640' update: function() {',
2641' if (!this.isRoot) {',
2642' if (this.group.expandable) {',
2643' removeClass(this.expander, "greyedout");',
2644' } else {',
2645' addClass(this.expander, "greyedout");',
2646' }',
2647' }',
2648' },',
2649'',
2650' clear: function() {',
2651' if (this.isRoot) {',
2652' this.contentDiv.innerHTML = "";',
2653' }',
2654' }',
2655' });',
2656'',
2657' /*----------------------------------------------------------------*/',
2658'',
2659' function Group(name, isRoot, initiallyExpanded) {',
2660' this.name = name;',
2661' this.group = null;',
2662' this.isRoot = isRoot;',
2663' this.initiallyExpanded = initiallyExpanded;',
2664' this.elementContainers = [];',
2665' this.children = [];',
2666' this.expanded = initiallyExpanded;',
2667' this.rendered = false;',
2668' this.expandable = false;',
2669' }',
2670'',
2671' Group.prototype = new LogItem();',
2672'',
2673' copyProperties(Group.prototype, {',
2674' addChild: function(logItem) {',
2675' this.children.push(logItem);',
2676' logItem.group = this;',
2677' },',
2678'',
2679' render: function() {',
2680' if (isIe) {',
2681' var unwrappedDomContainer, wrappedDomContainer;',
2682' if (this.isRoot) {',
2683' unwrappedDomContainer = logMainContainer;',
2684' wrappedDomContainer = logMainContainer;',
2685' } else {',
2686' unwrappedDomContainer = this.getUnwrappedDomContainer();',
2687' wrappedDomContainer = this.getWrappedDomContainer();',
2688' }',
2689' this.unwrappedElementContainer = new GroupElementContainer(this, unwrappedDomContainer, this.isRoot, false);',
2690' this.wrappedElementContainer = new GroupElementContainer(this, wrappedDomContainer, this.isRoot, true);',
2691' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
2692' } else {',
2693' var mainDomContainer = this.isRoot ? logMainContainer : this.getMainDomContainer();',
2694' this.mainElementContainer = new GroupElementContainer(this, mainDomContainer, this.isRoot, false);',
2695' this.elementContainers = [this.mainElementContainer];',
2696' }',
2697' this.rendered = true;',
2698' },',
2699'',
2700' toggleExpanded: function() {',
2701' this.expanded = !this.expanded;',
2702' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2703' this.elementContainers[i].toggleExpanded();',
2704' }',
2705' },',
2706'',
2707' expand: function() {',
2708' if (!this.expanded) {',
2709' this.toggleExpanded();',
2710' }',
2711' },',
2712'',
2713' accept: function(visitor) {',
2714' visitor.visitGroup(this);',
2715' },',
2716'',
2717' reverseChildren: function() {',
2718' if (this.rendered) {',
2719' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2720' this.elementContainers[i].reverseChildren();',
2721' }',
2722' }',
2723' },',
2724'',
2725' update: function() {',
2726' var previouslyExpandable = this.expandable;',
2727' this.expandable = (this.children.length !== 0);',
2728' if (this.expandable !== previouslyExpandable) {',
2729' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2730' this.elementContainers[i].update();',
2731' }',
2732' }',
2733' },',
2734'',
2735' flatten: function() {',
2736' var visitor = new GroupFlattener();',
2737' this.accept(visitor);',
2738' return visitor.logEntriesAndSeparators;',
2739' },',
2740'',
2741' removeChild: function(child, doUpdate) {',
2742' array_remove(this.children, child);',
2743' child.group = null;',
2744' if (doUpdate) {',
2745' this.update();',
2746' }',
2747' },',
2748'',
2749' remove: function(doUpdate, removeFromGroup) {',
2750' for (var i = 0, len = this.children.length; i < len; i++) {',
2751' this.children[i].remove(false, false);',
2752' }',
2753' this.children = [];',
2754' this.update();',
2755' if (this === currentGroup) {',
2756' currentGroup = this.group;',
2757' }',
2758' this.doRemove(doUpdate, removeFromGroup);',
2759' },',
2760'',
2761' serialize: function(items) {',
2762' items.push([LogItem.serializedItemKeys.GROUP_START, this.name]);',
2763' for (var i = 0, len = this.children.length; i < len; i++) {',
2764' this.children[i].serialize(items);',
2765' }',
2766' if (this !== currentGroup) {',
2767' items.push([LogItem.serializedItemKeys.GROUP_END]);',
2768' }',
2769' },',
2770'',
2771' clear: function() {',
2772' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2773' this.elementContainers[i].clear();',
2774' }',
2775' }',
2776' });',
2777'',
2778' /*----------------------------------------------------------------*/',
2779'',
2780' function LogEntryElementContainer() {',
2781' }',
2782'',
2783' LogEntryElementContainer.prototype = new LogItemContainerElement();',
2784'',
2785' copyProperties(LogEntryElementContainer.prototype, {',
2786' remove: function() {',
2787' this.doRemove();',
2788' },',
2789'',
2790' doRemove: function() {',
2791' this.mainDiv.parentNode.removeChild(this.mainDiv);',
2792' this.mainDiv = null;',
2793' this.contentElement = null;',
2794' this.containerDomNode = null;',
2795' },',
2796'',
2797' setContent: function(content, wrappedContent) {',
2798' if (content === this.formattedMessage) {',
2799' this.contentElement.innerHTML = "";',
2800' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));',
2801' } else {',
2802' this.contentElement.innerHTML = content;',
2803' }',
2804' },',
2805'',
2806' setSearchMatch: function(isMatch) {',
2807' var oldCssClass = isMatch ? "searchnonmatch" : "searchmatch";',
2808' var newCssClass = isMatch ? "searchmatch" : "searchnonmatch";',
2809' replaceClass(this.mainDiv, newCssClass, oldCssClass);',
2810' },',
2811'',
2812' clearSearch: function() {',
2813' removeClass(this.mainDiv, "searchmatch");',
2814' removeClass(this.mainDiv, "searchnonmatch");',
2815' }',
2816' });',
2817'',
2818' /*----------------------------------------------------------------*/',
2819'',
2820' function LogEntryWrappedElementContainer(logEntry, containerDomNode) {',
2821' this.logEntry = logEntry;',
2822' this.containerDomNode = containerDomNode;',
2823' this.mainDiv = document.createElement("div");',
2824' this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
2825' this.mainDiv.className = "logentry wrapped " + this.logEntry.level;',
2826' this.contentElement = this.mainDiv;',
2827' }',
2828'',
2829' LogEntryWrappedElementContainer.prototype = new LogEntryElementContainer();',
2830'',
2831' LogEntryWrappedElementContainer.prototype.setContent = function(content, wrappedContent) {',
2832' if (content === this.formattedMessage) {',
2833' this.contentElement.innerHTML = "";',
2834' this.contentElement.appendChild(document.createTextNode(this.formattedMessage));',
2835' } else {',
2836' this.contentElement.innerHTML = wrappedContent;',
2837' }',
2838' };',
2839'',
2840' /*----------------------------------------------------------------*/',
2841'',
2842' function LogEntryUnwrappedElementContainer(logEntry, containerDomNode) {',
2843' this.logEntry = logEntry;',
2844' this.containerDomNode = containerDomNode;',
2845' this.mainDiv = document.createElement("div");',
2846' this.mainDiv.className = "logentry unwrapped " + this.logEntry.level;',
2847' this.pre = this.mainDiv.appendChild(document.createElement("pre"));',
2848' this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
2849' this.pre.className = "unwrapped";',
2850' this.contentElement = this.pre;',
2851' }',
2852'',
2853' LogEntryUnwrappedElementContainer.prototype = new LogEntryElementContainer();',
2854'',
2855' LogEntryUnwrappedElementContainer.prototype.remove = function() {',
2856' this.doRemove();',
2857' this.pre = null;',
2858' };',
2859'',
2860' /*----------------------------------------------------------------*/',
2861'',
2862' function LogEntryMainElementContainer(logEntry, containerDomNode) {',
2863' this.logEntry = logEntry;',
2864' this.containerDomNode = containerDomNode;',
2865' this.mainDiv = document.createElement("div");',
2866' this.mainDiv.className = "logentry nonielogentry " + this.logEntry.level;',
2867' this.contentElement = this.mainDiv.appendChild(document.createElement("span"));',
2868' this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage));',
2869' }',
2870'',
2871' LogEntryMainElementContainer.prototype = new LogEntryElementContainer();',
2872'',
2873' /*----------------------------------------------------------------*/',
2874'',
2875' function LogEntry(level, formattedMessage) {',
2876' this.level = level;',
2877' this.formattedMessage = formattedMessage;',
2878' this.rendered = false;',
2879' }',
2880'',
2881' LogEntry.prototype = new LogItem();',
2882'',
2883' copyProperties(LogEntry.prototype, {',
2884' render: function() {',
2885' var logEntry = this;',
2886' var containerDomNode = this.group.contentDiv;',
2887'',
2888' // Support for the CSS attribute white-space in IE for Windows is',
2889' // non-existent pre version 6 and slightly odd in 6, so instead',
2890' // use two different HTML elements',
2891' if (isIe) {',
2892' this.formattedMessage = this.formattedMessage.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space',
2893' this.unwrappedElementContainer = new LogEntryUnwrappedElementContainer(this, this.getUnwrappedDomContainer());',
2894' this.wrappedElementContainer = new LogEntryWrappedElementContainer(this, this.getWrappedDomContainer());',
2895' this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer];',
2896' } else {',
2897' this.mainElementContainer = new LogEntryMainElementContainer(this, this.getMainDomContainer());',
2898' this.elementContainers = [this.mainElementContainer];',
2899' }',
2900' this.content = this.formattedMessage;',
2901' this.rendered = true;',
2902' },',
2903'',
2904' setContent: function(content, wrappedContent) {',
2905' if (content != this.content) {',
2906' if (isIe && (content !== this.formattedMessage)) {',
2907' content = content.replace(/\\r\\n/g, "\\r"); // Workaround for IE\'s treatment of white space',
2908' }',
2909' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2910' this.elementContainers[i].setContent(content, wrappedContent);',
2911' }',
2912' this.content = content;',
2913' }',
2914' },',
2915'',
2916' getSearchMatches: function() {',
2917' var matches = [];',
2918' var i, len;',
2919' if (isIe) {',
2920' var unwrappedEls = getElementsByClass(this.unwrappedElementContainer.mainDiv, "searchterm", "span");',
2921' var wrappedEls = getElementsByClass(this.wrappedElementContainer.mainDiv, "searchterm", "span");',
2922' for (i = 0, len = unwrappedEls.length; i < len; i++) {',
2923' matches[i] = new Match(this.level, null, unwrappedEls[i], wrappedEls[i]);',
2924' }',
2925' } else {',
2926' var els = getElementsByClass(this.mainElementContainer.mainDiv, "searchterm", "span");',
2927' for (i = 0, len = els.length; i < len; i++) {',
2928' matches[i] = new Match(this.level, els[i]);',
2929' }',
2930' }',
2931' return matches;',
2932' },',
2933'',
2934' setSearchMatch: function(isMatch) {',
2935' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2936' this.elementContainers[i].setSearchMatch(isMatch);',
2937' }',
2938' },',
2939'',
2940' clearSearch: function() {',
2941' for (var i = 0, len = this.elementContainers.length; i < len; i++) {',
2942' this.elementContainers[i].clearSearch();',
2943' }',
2944' },',
2945'',
2946' accept: function(visitor) {',
2947' visitor.visitLogEntry(this);',
2948' },',
2949'',
2950' serialize: function(items) {',
2951' items.push([LogItem.serializedItemKeys.LOG_ENTRY, this.level, this.formattedMessage]);',
2952' }',
2953' });',
2954'',
2955' /*----------------------------------------------------------------*/',
2956'',
2957' function LogItemVisitor() {',
2958' }',
2959'',
2960' LogItemVisitor.prototype = {',
2961' visit: function(logItem) {',
2962' },',
2963'',
2964' visitParent: function(logItem) {',
2965' if (logItem.group) {',
2966' logItem.group.accept(this);',
2967' }',
2968' },',
2969'',
2970' visitChildren: function(logItem) {',
2971' for (var i = 0, len = logItem.children.length; i < len; i++) {',
2972' logItem.children[i].accept(this);',
2973' }',
2974' },',
2975'',
2976' visitLogEntry: function(logEntry) {',
2977' this.visit(logEntry);',
2978' },',
2979'',
2980' visitSeparator: function(separator) {',
2981' this.visit(separator);',
2982' },',
2983'',
2984' visitGroup: function(group) {',
2985' this.visit(group);',
2986' }',
2987' };',
2988'',
2989' /*----------------------------------------------------------------*/',
2990'',
2991' function GroupFlattener() {',
2992' this.logEntriesAndSeparators = [];',
2993' }',
2994'',
2995' GroupFlattener.prototype = new LogItemVisitor();',
2996'',
2997' GroupFlattener.prototype.visitGroup = function(group) {',
2998' this.visitChildren(group);',
2999' };',
3000'',
3001' GroupFlattener.prototype.visitLogEntry = function(logEntry) {',
3002' this.logEntriesAndSeparators.push(logEntry);',
3003' };',
3004'',
3005' GroupFlattener.prototype.visitSeparator = function(separator) {',
3006' this.logEntriesAndSeparators.push(separator);',
3007' };',
3008'',
3009' /*----------------------------------------------------------------*/',
3010'',
3011' window.onload = function() {',
3012' // Sort out document.domain',
3013' if (location.search) {',
3014' var queryBits = unescape(location.search).substr(1).split("&"), nameValueBits;',
3015' for (var i = 0, len = queryBits.length; i < len; i++) {',
3016' nameValueBits = queryBits[i].split("=");',
3017' if (nameValueBits[0] == "log4javascript_domain") {',
3018' document.domain = nameValueBits[1];',
3019' break;',
3020' }',
3021' }',
3022' }',
3023'',
3024' // Create DOM objects',
3025' logMainContainer = $("log");',
3026' if (isIePre7) {',
3027' addClass(logMainContainer, "oldIe");',
3028' }',
3029'',
3030' rootGroup = new Group("root", true);',
3031' rootGroup.render();',
3032' currentGroup = rootGroup;',
3033' ',
3034' setCommandInputWidth();',
3035' setLogContainerHeight();',
3036' toggleLoggingEnabled();',
3037' toggleSearchEnabled();',
3038' toggleSearchFilter();',
3039' toggleSearchHighlight();',
3040' applyFilters();',
3041' checkAllLevels();',
3042' toggleWrap();',
3043' toggleNewestAtTop();',
3044' toggleScrollToLatest();',
3045' renderQueuedLogItems();',
3046' loaded = true;',
3047' $("command").value = "";',
3048' $("command").autocomplete = "off";',
3049' $("command").onkeydown = function(evt) {',
3050' evt = getEvent(evt);',
3051' if (evt.keyCode == 10 || evt.keyCode == 13) { // Return/Enter',
3052' evalCommandLine();',
3053' stopPropagation(evt);',
3054' } else if (evt.keyCode == 27) { // Escape',
3055' this.value = "";',
3056' this.focus();',
3057' } else if (evt.keyCode == 38 && commandHistory.length > 0) { // Up',
3058' currentCommandIndex = Math.max(0, currentCommandIndex - 1);',
3059' this.value = commandHistory[currentCommandIndex];',
3060' moveCaretToEnd(this);',
3061' } else if (evt.keyCode == 40 && commandHistory.length > 0) { // Down',
3062' currentCommandIndex = Math.min(commandHistory.length - 1, currentCommandIndex + 1);',
3063' this.value = commandHistory[currentCommandIndex];',
3064' moveCaretToEnd(this);',
3065' }',
3066' };',
3067'',
3068' // Prevent the keypress moving the caret in Firefox',
3069' $("command").onkeypress = function(evt) {',
3070' evt = getEvent(evt);',
3071' if (evt.keyCode == 38 && commandHistory.length > 0 && evt.preventDefault) { // Up',
3072' evt.preventDefault();',
3073' }',
3074' };',
3075'',
3076' // Prevent the keyup event blurring the input in Opera',
3077' $("command").onkeyup = function(evt) {',
3078' evt = getEvent(evt);',
3079' if (evt.keyCode == 27 && evt.preventDefault) { // Up',
3080' evt.preventDefault();',
3081' this.focus();',
3082' }',
3083' };',
3084'',
3085' // Add document keyboard shortcuts',
3086' document.onkeydown = function keyEventHandler(evt) {',
3087' evt = getEvent(evt);',
3088' switch (evt.keyCode) {',
3089' case 69: // Ctrl + shift + E: re-execute last command',
3090' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
3091' evalLastCommand();',
3092' cancelKeyEvent(evt);',
3093' return false;',
3094' }',
3095' break;',
3096' case 75: // Ctrl + shift + K: focus search',
3097' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
3098' focusSearch();',
3099' cancelKeyEvent(evt);',
3100' return false;',
3101' }',
3102' break;',
3103' case 40: // Ctrl + shift + down arrow: focus command line',
3104' case 76: // Ctrl + shift + L: focus command line',
3105' if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) {',
3106' focusCommandLine();',
3107' cancelKeyEvent(evt);',
3108' return false;',
3109' }',
3110' break;',
3111' }',
3112' };',
3113'',
3114' // Workaround to make sure log div starts at the correct size',
3115' setTimeout(setLogContainerHeight, 20);',
3116'',
3117' setShowCommandLine(showCommandLine);',
3118' doSearch();',
3119' };',
3120'',
3121' window.onunload = function() {',
3122' if (mainWindowExists()) {',
3123' appender.unload();',
3124' }',
3125' appender = null;',
3126' };',
3127'',
3128' /*----------------------------------------------------------------*/',
3129'',
3130' function toggleLoggingEnabled() {',
3131' setLoggingEnabled($("enableLogging").checked);',
3132' }',
3133'',
3134' function setLoggingEnabled(enable) {',
3135' loggingEnabled = enable;',
3136' }',
3137'',
3138' var appender = null;',
3139'',
3140' function setAppender(appenderParam) {',
3141' appender = appenderParam;',
3142' }',
3143'',
3144' function setShowCloseButton(showCloseButton) {',
3145' $("closeButton").style.display = showCloseButton ? "inline" : "none";',
3146' }',
3147'',
3148' function setShowHideButton(showHideButton) {',
3149' $("hideButton").style.display = showHideButton ? "inline" : "none";',
3150' }',
3151'',
3152' var newestAtTop = false;',
3153'',
3154' /*----------------------------------------------------------------*/',
3155'',
3156' function LogItemContentReverser() {',
3157' }',
3158' ',
3159' LogItemContentReverser.prototype = new LogItemVisitor();',
3160' ',
3161' LogItemContentReverser.prototype.visitGroup = function(group) {',
3162' group.reverseChildren();',
3163' this.visitChildren(group);',
3164' };',
3165'',
3166' /*----------------------------------------------------------------*/',
3167'',
3168' function setNewestAtTop(isNewestAtTop) {',
3169' var oldNewestAtTop = newestAtTop;',
3170' var i, iLen, j, jLen;',
3171' newestAtTop = Boolean(isNewestAtTop);',
3172' if (oldNewestAtTop != newestAtTop) {',
3173' var visitor = new LogItemContentReverser();',
3174' rootGroup.accept(visitor);',
3175'',
3176' // Reassemble the matches array',
3177' if (currentSearch) {',
3178' var currentMatch = currentSearch.matches[currentMatchIndex];',
3179' var matchIndex = 0;',
3180' var matches = [];',
3181' var actOnLogEntry = function(logEntry) {',
3182' var logEntryMatches = logEntry.getSearchMatches();',
3183' for (j = 0, jLen = logEntryMatches.length; j < jLen; j++) {',
3184' matches[matchIndex] = logEntryMatches[j];',
3185' if (currentMatch && logEntryMatches[j].equals(currentMatch)) {',
3186' currentMatchIndex = matchIndex;',
3187' }',
3188' matchIndex++;',
3189' }',
3190' };',
3191' if (newestAtTop) {',
3192' for (i = logEntries.length - 1; i >= 0; i--) {',
3193' actOnLogEntry(logEntries[i]);',
3194' }',
3195' } else {',
3196' for (i = 0, iLen = logEntries.length; i < iLen; i++) {',
3197' actOnLogEntry(logEntries[i]);',
3198' }',
3199' }',
3200' currentSearch.matches = matches;',
3201' if (currentMatch) {',
3202' currentMatch.setCurrent();',
3203' }',
3204' } else if (scrollToLatest) {',
3205' doScrollToLatest();',
3206' }',
3207' }',
3208' $("newestAtTop").checked = isNewestAtTop;',
3209' }',
3210'',
3211' function toggleNewestAtTop() {',
3212' var isNewestAtTop = $("newestAtTop").checked;',
3213' setNewestAtTop(isNewestAtTop);',
3214' }',
3215'',
3216' var scrollToLatest = true;',
3217'',
3218' function setScrollToLatest(isScrollToLatest) {',
3219' scrollToLatest = isScrollToLatest;',
3220' if (scrollToLatest) {',
3221' doScrollToLatest();',
3222' }',
3223' $("scrollToLatest").checked = isScrollToLatest;',
3224' }',
3225'',
3226' function toggleScrollToLatest() {',
3227' var isScrollToLatest = $("scrollToLatest").checked;',
3228' setScrollToLatest(isScrollToLatest);',
3229' }',
3230'',
3231' function doScrollToLatest() {',
3232' var l = logMainContainer;',
3233' if (typeof l.scrollTop != "undefined") {',
3234' if (newestAtTop) {',
3235' l.scrollTop = 0;',
3236' } else {',
3237' var latestLogEntry = l.lastChild;',
3238' if (latestLogEntry) {',
3239' l.scrollTop = l.scrollHeight;',
3240' }',
3241' }',
3242' }',
3243' }',
3244'',
3245' var closeIfOpenerCloses = true;',
3246'',
3247' function setCloseIfOpenerCloses(isCloseIfOpenerCloses) {',
3248' closeIfOpenerCloses = isCloseIfOpenerCloses;',
3249' }',
3250'',
3251' var maxMessages = null;',
3252'',
3253' function setMaxMessages(max) {',
3254' maxMessages = max;',
3255' pruneLogEntries();',
3256' }',
3257'',
3258' var showCommandLine = false;',
3259'',
3260' function setShowCommandLine(isShowCommandLine) {',
3261' showCommandLine = isShowCommandLine;',
3262' if (loaded) {',
3263' $("commandLine").style.display = showCommandLine ? "block" : "none";',
3264' setCommandInputWidth();',
3265' setLogContainerHeight();',
3266' }',
3267' }',
3268'',
3269' function focusCommandLine() {',
3270' if (loaded) {',
3271' $("command").focus();',
3272' }',
3273' }',
3274'',
3275' function focusSearch() {',
3276' if (loaded) {',
3277' $("searchBox").focus();',
3278' }',
3279' }',
3280'',
3281' function getLogItems() {',
3282' var items = [];',
3283' for (var i = 0, len = logItems.length; i < len; i++) {',
3284' logItems[i].serialize(items);',
3285' }',
3286' return items;',
3287' }',
3288'',
3289' function setLogItems(items) {',
3290' var loggingReallyEnabled = loggingEnabled;',
3291' // Temporarily turn logging on',
3292' loggingEnabled = true;',
3293' for (var i = 0, len = items.length; i < len; i++) {',
3294' switch (items[i][0]) {',
3295' case LogItem.serializedItemKeys.LOG_ENTRY:',
3296' log(items[i][1], items[i][2]);',
3297' break;',
3298' case LogItem.serializedItemKeys.GROUP_START:',
3299' group(items[i][1]);',
3300' break;',
3301' case LogItem.serializedItemKeys.GROUP_END:',
3302' groupEnd();',
3303' break;',
3304' }',
3305' }',
3306' loggingEnabled = loggingReallyEnabled;',
3307' }',
3308'',
3309' function log(logLevel, formattedMessage) {',
3310' if (loggingEnabled) {',
3311' var logEntry = new LogEntry(logLevel, formattedMessage);',
3312' logEntries.push(logEntry);',
3313' logEntriesAndSeparators.push(logEntry);',
3314' logItems.push(logEntry);',
3315' currentGroup.addChild(logEntry);',
3316' if (loaded) {',
3317' if (logQueuedEventsTimer !== null) {',
3318' clearTimeout(logQueuedEventsTimer);',
3319' }',
3320' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);',
3321' unrenderedLogItemsExist = true;',
3322' }',
3323' }',
3324' }',
3325'',
3326' function renderQueuedLogItems() {',
3327' logQueuedEventsTimer = null;',
3328' var pruned = pruneLogEntries();',
3329'',
3330' // Render any unrendered log entries and apply the current search to them',
3331' var initiallyHasMatches = currentSearch ? currentSearch.hasMatches() : false;',
3332' for (var i = 0, len = logItems.length; i < len; i++) {',
3333' if (!logItems[i].rendered) {',
3334' logItems[i].render();',
3335' logItems[i].appendToLog();',
3336' if (currentSearch && (logItems[i] instanceof LogEntry)) {',
3337' currentSearch.applyTo(logItems[i]);',
3338' }',
3339' }',
3340' }',
3341' if (currentSearch) {',
3342' if (pruned) {',
3343' if (currentSearch.hasVisibleMatches()) {',
3344' if (currentMatchIndex === null) {',
3345' setCurrentMatchIndex(0);',
3346' }',
3347' displayMatches();',
3348' } else {',
3349' displayNoMatches();',
3350' }',
3351' } else if (!initiallyHasMatches && currentSearch.hasVisibleMatches()) {',
3352' setCurrentMatchIndex(0);',
3353' displayMatches();',
3354' }',
3355' }',
3356' if (scrollToLatest) {',
3357' doScrollToLatest();',
3358' }',
3359' unrenderedLogItemsExist = false;',
3360' }',
3361'',
3362' function pruneLogEntries() {',
3363' if ((maxMessages !== null) && (logEntriesAndSeparators.length > maxMessages)) {',
3364' var numberToDelete = logEntriesAndSeparators.length - maxMessages;',
3365' var prunedLogEntries = logEntriesAndSeparators.slice(0, numberToDelete);',
3366' if (currentSearch) {',
3367' currentSearch.removeMatches(prunedLogEntries);',
3368' }',
3369' var group;',
3370' for (var i = 0; i < numberToDelete; i++) {',
3371' group = logEntriesAndSeparators[i].group;',
3372' array_remove(logItems, logEntriesAndSeparators[i]);',
3373' array_remove(logEntries, logEntriesAndSeparators[i]);',
3374' logEntriesAndSeparators[i].remove(true, true);',
3375' if (group.children.length === 0 && group !== currentGroup && group !== rootGroup) {',
3376' array_remove(logItems, group);',
3377' group.remove(true, true);',
3378' }',
3379' }',
3380' logEntriesAndSeparators = array_removeFromStart(logEntriesAndSeparators, numberToDelete);',
3381' return true;',
3382' }',
3383' return false;',
3384' }',
3385'',
3386' function group(name, startExpanded) {',
3387' if (loggingEnabled) {',
3388' initiallyExpanded = (typeof startExpanded === "undefined") ? true : Boolean(startExpanded);',
3389' var newGroup = new Group(name, false, initiallyExpanded);',
3390' currentGroup.addChild(newGroup);',
3391' currentGroup = newGroup;',
3392' logItems.push(newGroup);',
3393' if (loaded) {',
3394' if (logQueuedEventsTimer !== null) {',
3395' clearTimeout(logQueuedEventsTimer);',
3396' }',
3397' logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay);',
3398' unrenderedLogItemsExist = true;',
3399' }',
3400' }',
3401' }',
3402'',
3403' function groupEnd() {',
3404' currentGroup = (currentGroup === rootGroup) ? rootGroup : currentGroup.group;',
3405' }',
3406'',
3407' function mainPageReloaded() {',
3408' currentGroup = rootGroup;',
3409' var separator = new Separator();',
3410' logEntriesAndSeparators.push(separator);',
3411' logItems.push(separator);',
3412' currentGroup.addChild(separator);',
3413' }',
3414'',
3415' function closeWindow() {',
3416' if (appender && mainWindowExists()) {',
3417' appender.close(true);',
3418' } else {',
3419' window.close();',
3420' }',
3421' }',
3422'',
3423' function hide() {',
3424' if (appender && mainWindowExists()) {',
3425' appender.hide();',
3426' }',
3427' }',
3428'',
3429' var mainWindow = window;',
3430' var windowId = "log4javascriptConsoleWindow_" + new Date().getTime() + "_" + ("" + Math.random()).substr(2);',
3431'',
3432' function setMainWindow(win) {',
3433' mainWindow = win;',
3434' mainWindow[windowId] = window;',
3435' // If this is a pop-up, poll the opener to see if it\'s closed',
3436' if (opener && closeIfOpenerCloses) {',
3437' pollOpener();',
3438' }',
3439' }',
3440'',
3441' function pollOpener() {',
3442' if (closeIfOpenerCloses) {',
3443' if (mainWindowExists()) {',
3444' setTimeout(pollOpener, 500);',
3445' } else {',
3446' closeWindow();',
3447' }',
3448' }',
3449' }',
3450'',
3451' function mainWindowExists() {',
3452' try {',
3453' return (mainWindow && !mainWindow.closed &&',
3454' mainWindow[windowId] == window);',
3455' } catch (ex) {}',
3456' return false;',
3457' }',
3458'',
3459' var logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];',
3460'',
3461' function getCheckBox(logLevel) {',
3462' return $("switch_" + logLevel);',
3463' }',
3464'',
3465' function getIeWrappedLogContainer() {',
3466' return $("log_wrapped");',
3467' }',
3468'',
3469' function getIeUnwrappedLogContainer() {',
3470' return $("log_unwrapped");',
3471' }',
3472'',
3473' function applyFilters() {',
3474' for (var i = 0; i < logLevels.length; i++) {',
3475' if (getCheckBox(logLevels[i]).checked) {',
3476' addClass(logMainContainer, logLevels[i]);',
3477' } else {',
3478' removeClass(logMainContainer, logLevels[i]);',
3479' }',
3480' }',
3481' updateSearchFromFilters();',
3482' }',
3483'',
3484' function toggleAllLevels() {',
3485' var turnOn = $("switch_ALL").checked;',
3486' for (var i = 0; i < logLevels.length; i++) {',
3487' getCheckBox(logLevels[i]).checked = turnOn;',
3488' if (turnOn) {',
3489' addClass(logMainContainer, logLevels[i]);',
3490' } else {',
3491' removeClass(logMainContainer, logLevels[i]);',
3492' }',
3493' }',
3494' }',
3495'',
3496' function checkAllLevels() {',
3497' for (var i = 0; i < logLevels.length; i++) {',
3498' if (!getCheckBox(logLevels[i]).checked) {',
3499' getCheckBox("ALL").checked = false;',
3500' return;',
3501' }',
3502' }',
3503' getCheckBox("ALL").checked = true;',
3504' }',
3505'',
3506' function clearLog() {',
3507' rootGroup.clear();',
3508' currentGroup = rootGroup;',
3509' logEntries = [];',
3510' logItems = [];',
3511' logEntriesAndSeparators = [];',
3512' doSearch();',
3513' }',
3514'',
3515' function toggleWrap() {',
3516' var enable = $("wrap").checked;',
3517' if (enable) {',
3518' addClass(logMainContainer, "wrap");',
3519' } else {',
3520' removeClass(logMainContainer, "wrap");',
3521' }',
3522' refreshCurrentMatch();',
3523' }',
3524'',
3525' /* ------------------------------------------------------------------- */',
3526'',
3527' // Search',
3528'',
3529' var searchTimer = null;',
3530'',
3531' function scheduleSearch() {',
3532' try {',
3533' clearTimeout(searchTimer);',
3534' } catch (ex) {',
3535' // Do nothing',
3536' }',
3537' searchTimer = setTimeout(doSearch, 500);',
3538' }',
3539'',
3540' function Search(searchTerm, isRegex, searchRegex, isCaseSensitive) {',
3541' this.searchTerm = searchTerm;',
3542' this.isRegex = isRegex;',
3543' this.searchRegex = searchRegex;',
3544' this.isCaseSensitive = isCaseSensitive;',
3545' this.matches = [];',
3546' }',
3547'',
3548' Search.prototype = {',
3549' hasMatches: function() {',
3550' return this.matches.length > 0;',
3551' },',
3552'',
3553' hasVisibleMatches: function() {',
3554' if (this.hasMatches()) {',
3555' for (var i = 0; i < this.matches.length; i++) {',
3556' if (this.matches[i].isVisible()) {',
3557' return true;',
3558' }',
3559' }',
3560' }',
3561' return false;',
3562' },',
3563'',
3564' match: function(logEntry) {',
3565' var entryText = String(logEntry.formattedMessage);',
3566' var matchesSearch = false;',
3567' if (this.isRegex) {',
3568' matchesSearch = this.searchRegex.test(entryText);',
3569' } else if (this.isCaseSensitive) {',
3570' matchesSearch = (entryText.indexOf(this.searchTerm) > -1);',
3571' } else {',
3572' matchesSearch = (entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1);',
3573' }',
3574' return matchesSearch;',
3575' },',
3576'',
3577' getNextVisibleMatchIndex: function() {',
3578' for (var i = currentMatchIndex + 1; i < this.matches.length; i++) {',
3579' if (this.matches[i].isVisible()) {',
3580' return i;',
3581' }',
3582' }',
3583' // Start again from the first match',
3584' for (i = 0; i <= currentMatchIndex; i++) {',
3585' if (this.matches[i].isVisible()) {',
3586' return i;',
3587' }',
3588' }',
3589' return -1;',
3590' },',
3591'',
3592' getPreviousVisibleMatchIndex: function() {',
3593' for (var i = currentMatchIndex - 1; i >= 0; i--) {',
3594' if (this.matches[i].isVisible()) {',
3595' return i;',
3596' }',
3597' }',
3598' // Start again from the last match',
3599' for (var i = this.matches.length - 1; i >= currentMatchIndex; i--) {',
3600' if (this.matches[i].isVisible()) {',
3601' return i;',
3602' }',
3603' }',
3604' return -1;',
3605' },',
3606'',
3607' applyTo: function(logEntry) {',
3608' var doesMatch = this.match(logEntry);',
3609' if (doesMatch) {',
3610' logEntry.group.expand();',
3611' logEntry.setSearchMatch(true);',
3612' var logEntryContent;',
3613' var wrappedLogEntryContent;',
3614' var searchTermReplacementStartTag = "<span class=\\\"searchterm\\\">";',
3615' var searchTermReplacementEndTag = "<" + "/span>";',
3616' var preTagName = isIe ? "pre" : "span";',
3617' var preStartTag = "<" + preTagName + " class=\\\"pre\\\">";',
3618' var preEndTag = "<" + "/" + preTagName + ">";',
3619' var startIndex = 0;',
3620' var searchIndex, matchedText, textBeforeMatch;',
3621' if (this.isRegex) {',
3622' var flags = this.isCaseSensitive ? "g" : "gi";',
3623' var capturingRegex = new RegExp("(" + this.searchRegex.source + ")", flags);',
3624'',
3625' // Replace the search term with temporary tokens for the start and end tags',
3626' var rnd = ("" + Math.random()).substr(2);',
3627' var startToken = "%%s" + rnd + "%%";',
3628' var endToken = "%%e" + rnd + "%%";',
3629' logEntryContent = logEntry.formattedMessage.replace(capturingRegex, startToken + "$1" + endToken);',
3630'',
3631' // Escape the HTML to get rid of angle brackets',
3632' logEntryContent = escapeHtml(logEntryContent);',
3633'',
3634' // Substitute the proper HTML back in for the search match',
3635' var result;',
3636' var searchString = logEntryContent;',
3637' logEntryContent = "";',
3638' wrappedLogEntryContent = "";',
3639' while ((searchIndex = searchString.indexOf(startToken, startIndex)) > -1) {',
3640' var endTokenIndex = searchString.indexOf(endToken, searchIndex);',
3641' matchedText = searchString.substring(searchIndex + startToken.length, endTokenIndex);',
3642' textBeforeMatch = searchString.substring(startIndex, searchIndex);',
3643' logEntryContent += preStartTag + textBeforeMatch + preEndTag;',
3644' logEntryContent += searchTermReplacementStartTag + preStartTag + matchedText +',
3645' preEndTag + searchTermReplacementEndTag;',
3646' if (isIe) {',
3647' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +',
3648' matchedText + searchTermReplacementEndTag;',
3649' }',
3650' startIndex = endTokenIndex + endToken.length;',
3651' }',
3652' logEntryContent += preStartTag + searchString.substr(startIndex) + preEndTag;',
3653' if (isIe) {',
3654' wrappedLogEntryContent += searchString.substr(startIndex);',
3655' }',
3656' } else {',
3657' logEntryContent = "";',
3658' wrappedLogEntryContent = "";',
3659' var searchTermReplacementLength = searchTermReplacementStartTag.length +',
3660' this.searchTerm.length + searchTermReplacementEndTag.length;',
3661' var searchTermLength = this.searchTerm.length;',
3662' var searchTermLowerCase = this.searchTerm.toLowerCase();',
3663' var logTextLowerCase = logEntry.formattedMessage.toLowerCase();',
3664' while ((searchIndex = logTextLowerCase.indexOf(searchTermLowerCase, startIndex)) > -1) {',
3665' matchedText = escapeHtml(logEntry.formattedMessage.substr(searchIndex, this.searchTerm.length));',
3666' textBeforeMatch = escapeHtml(logEntry.formattedMessage.substring(startIndex, searchIndex));',
3667' var searchTermReplacement = searchTermReplacementStartTag +',
3668' preStartTag + matchedText + preEndTag + searchTermReplacementEndTag;',
3669' logEntryContent += preStartTag + textBeforeMatch + preEndTag + searchTermReplacement;',
3670' if (isIe) {',
3671' wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag +',
3672' matchedText + searchTermReplacementEndTag;',
3673' }',
3674' startIndex = searchIndex + searchTermLength;',
3675' }',
3676' var textAfterLastMatch = escapeHtml(logEntry.formattedMessage.substr(startIndex));',
3677' logEntryContent += preStartTag + textAfterLastMatch + preEndTag;',
3678' if (isIe) {',
3679' wrappedLogEntryContent += textAfterLastMatch;',
3680' }',
3681' }',
3682' logEntry.setContent(logEntryContent, wrappedLogEntryContent);',
3683' var logEntryMatches = logEntry.getSearchMatches();',
3684' this.matches = this.matches.concat(logEntryMatches);',
3685' } else {',
3686' logEntry.setSearchMatch(false);',
3687' logEntry.setContent(logEntry.formattedMessage, logEntry.formattedMessage);',
3688' }',
3689' return doesMatch;',
3690' },',
3691'',
3692' removeMatches: function(logEntries) {',
3693' var matchesToRemoveCount = 0;',
3694' var currentMatchRemoved = false;',
3695' var matchesToRemove = [];',
3696' var i, iLen, j, jLen;',
3697'',
3698' // Establish the list of matches to be removed',
3699' for (i = 0, iLen = this.matches.length; i < iLen; i++) {',
3700' for (j = 0, jLen = logEntries.length; j < jLen; j++) {',
3701' if (this.matches[i].belongsTo(logEntries[j])) {',
3702' matchesToRemove.push(this.matches[i]);',
3703' if (i === currentMatchIndex) {',
3704' currentMatchRemoved = true;',
3705' }',
3706' }',
3707' }',
3708' }',
3709'',
3710' // Set the new current match index if the current match has been deleted',
3711' // This will be the first match that appears after the first log entry being',
3712' // deleted, if one exists; otherwise, it\'s the first match overall',
3713' var newMatch = currentMatchRemoved ? null : this.matches[currentMatchIndex];',
3714' if (currentMatchRemoved) {',
3715' for (i = currentMatchIndex, iLen = this.matches.length; i < iLen; i++) {',
3716' if (this.matches[i].isVisible() && !array_contains(matchesToRemove, this.matches[i])) {',
3717' newMatch = this.matches[i];',
3718' break;',
3719' }',
3720' }',
3721' }',
3722'',
3723' // Remove the matches',
3724' for (i = 0, iLen = matchesToRemove.length; i < iLen; i++) {',
3725' array_remove(this.matches, matchesToRemove[i]);',
3726' matchesToRemove[i].remove();',
3727' }',
3728'',
3729' // Set the new match, if one exists',
3730' if (this.hasVisibleMatches()) {',
3731' if (newMatch === null) {',
3732' setCurrentMatchIndex(0);',
3733' } else {',
3734' // Get the index of the new match',
3735' var newMatchIndex = 0;',
3736' for (i = 0, iLen = this.matches.length; i < iLen; i++) {',
3737' if (newMatch === this.matches[i]) {',
3738' newMatchIndex = i;',
3739' break;',
3740' }',
3741' }',
3742' setCurrentMatchIndex(newMatchIndex);',
3743' }',
3744' } else {',
3745' currentMatchIndex = null;',
3746' displayNoMatches();',
3747' }',
3748' }',
3749' };',
3750'',
3751' function getPageOffsetTop(el, container) {',
3752' var currentEl = el;',
3753' var y = 0;',
3754' while (currentEl && currentEl != container) {',
3755' y += currentEl.offsetTop;',
3756' currentEl = currentEl.offsetParent;',
3757' }',
3758' return y;',
3759' }',
3760'',
3761' function scrollIntoView(el) {',
3762' var logContainer = logMainContainer;',
3763' // Check if the whole width of the element is visible and centre if not',
3764' if (!$("wrap").checked) {',
3765' var logContainerLeft = logContainer.scrollLeft;',
3766' var logContainerRight = logContainerLeft + logContainer.offsetWidth;',
3767' var elLeft = el.offsetLeft;',
3768' var elRight = elLeft + el.offsetWidth;',
3769' if (elLeft < logContainerLeft || elRight > logContainerRight) {',
3770' logContainer.scrollLeft = elLeft - (logContainer.offsetWidth - el.offsetWidth) / 2;',
3771' }',
3772' }',
3773' // Check if the whole height of the element is visible and centre if not',
3774' var logContainerTop = logContainer.scrollTop;',
3775' var logContainerBottom = logContainerTop + logContainer.offsetHeight;',
3776' var elTop = getPageOffsetTop(el) - getToolBarsHeight();',
3777' var elBottom = elTop + el.offsetHeight;',
3778' if (elTop < logContainerTop || elBottom > logContainerBottom) {',
3779' logContainer.scrollTop = elTop - (logContainer.offsetHeight - el.offsetHeight) / 2;',
3780' }',
3781' }',
3782'',
3783' function Match(logEntryLevel, spanInMainDiv, spanInUnwrappedPre, spanInWrappedDiv) {',
3784' this.logEntryLevel = logEntryLevel;',
3785' this.spanInMainDiv = spanInMainDiv;',
3786' if (isIe) {',
3787' this.spanInUnwrappedPre = spanInUnwrappedPre;',
3788' this.spanInWrappedDiv = spanInWrappedDiv;',
3789' }',
3790' this.mainSpan = isIe ? spanInUnwrappedPre : spanInMainDiv;',
3791' }',
3792'',
3793' Match.prototype = {',
3794' equals: function(match) {',
3795' return this.mainSpan === match.mainSpan;',
3796' },',
3797'',
3798' setCurrent: function() {',
3799' if (isIe) {',
3800' addClass(this.spanInUnwrappedPre, "currentmatch");',
3801' addClass(this.spanInWrappedDiv, "currentmatch");',
3802' // Scroll the visible one into view',
3803' var elementToScroll = $("wrap").checked ? this.spanInWrappedDiv : this.spanInUnwrappedPre;',
3804' scrollIntoView(elementToScroll);',
3805' } else {',
3806' addClass(this.spanInMainDiv, "currentmatch");',
3807' scrollIntoView(this.spanInMainDiv);',
3808' }',
3809' },',
3810'',
3811' belongsTo: function(logEntry) {',
3812' if (isIe) {',
3813' return isDescendant(this.spanInUnwrappedPre, logEntry.unwrappedPre);',
3814' } else {',
3815' return isDescendant(this.spanInMainDiv, logEntry.mainDiv);',
3816' }',
3817' },',
3818'',
3819' setNotCurrent: function() {',
3820' if (isIe) {',
3821' removeClass(this.spanInUnwrappedPre, "currentmatch");',
3822' removeClass(this.spanInWrappedDiv, "currentmatch");',
3823' } else {',
3824' removeClass(this.spanInMainDiv, "currentmatch");',
3825' }',
3826' },',
3827'',
3828' isOrphan: function() {',
3829' return isOrphan(this.mainSpan);',
3830' },',
3831'',
3832' isVisible: function() {',
3833' return getCheckBox(this.logEntryLevel).checked;',
3834' },',
3835'',
3836' remove: function() {',
3837' if (isIe) {',
3838' this.spanInUnwrappedPre = null;',
3839' this.spanInWrappedDiv = null;',
3840' } else {',
3841' this.spanInMainDiv = null;',
3842' }',
3843' }',
3844' };',
3845'',
3846' var currentSearch = null;',
3847' var currentMatchIndex = null;',
3848'',
3849' function doSearch() {',
3850' var searchBox = $("searchBox");',
3851' var searchTerm = searchBox.value;',
3852' var isRegex = $("searchRegex").checked;',
3853' var isCaseSensitive = $("searchCaseSensitive").checked;',
3854' var i;',
3855'',
3856' if (searchTerm === "") {',
3857' $("searchReset").disabled = true;',
3858' $("searchNav").style.display = "none";',
3859' removeClass(document.body, "searching");',
3860' removeClass(searchBox, "hasmatches");',
3861' removeClass(searchBox, "nomatches");',
3862' for (i = 0; i < logEntries.length; i++) {',
3863' logEntries[i].clearSearch();',
3864' logEntries[i].setContent(logEntries[i].formattedMessage, logEntries[i].formattedMessage);',
3865' }',
3866' currentSearch = null;',
3867' setLogContainerHeight();',
3868' } else {',
3869' $("searchReset").disabled = false;',
3870' $("searchNav").style.display = "block";',
3871' var searchRegex;',
3872' var regexValid;',
3873' if (isRegex) {',
3874' try {',
3875' searchRegex = isCaseSensitive ? new RegExp(searchTerm, "g") : new RegExp(searchTerm, "gi");',
3876' regexValid = true;',
3877' replaceClass(searchBox, "validregex", "invalidregex");',
3878' searchBox.title = "Valid regex";',
3879' } catch (ex) {',
3880' regexValid = false;',
3881' replaceClass(searchBox, "invalidregex", "validregex");',
3882' searchBox.title = "Invalid regex: " + (ex.message ? ex.message : (ex.description ? ex.description : "unknown error"));',
3883' return;',
3884' }',
3885' } else {',
3886' searchBox.title = "";',
3887' removeClass(searchBox, "validregex");',
3888' removeClass(searchBox, "invalidregex");',
3889' }',
3890' addClass(document.body, "searching");',
3891' currentSearch = new Search(searchTerm, isRegex, searchRegex, isCaseSensitive);',
3892' for (i = 0; i < logEntries.length; i++) {',
3893' currentSearch.applyTo(logEntries[i]);',
3894' }',
3895' setLogContainerHeight();',
3896'',
3897' // Highlight the first search match',
3898' if (currentSearch.hasVisibleMatches()) {',
3899' setCurrentMatchIndex(0);',
3900' displayMatches();',
3901' } else {',
3902' displayNoMatches();',
3903' }',
3904' }',
3905' }',
3906'',
3907' function updateSearchFromFilters() {',
3908' if (currentSearch) {',
3909' if (currentSearch.hasMatches()) {',
3910' if (currentMatchIndex === null) {',
3911' currentMatchIndex = 0;',
3912' }',
3913' var currentMatch = currentSearch.matches[currentMatchIndex];',
3914' if (currentMatch.isVisible()) {',
3915' displayMatches();',
3916' setCurrentMatchIndex(currentMatchIndex);',
3917' } else {',
3918' currentMatch.setNotCurrent();',
3919' // Find the next visible match, if one exists',
3920' var nextVisibleMatchIndex = currentSearch.getNextVisibleMatchIndex();',
3921' if (nextVisibleMatchIndex > -1) {',
3922' setCurrentMatchIndex(nextVisibleMatchIndex);',
3923' displayMatches();',
3924' } else {',
3925' displayNoMatches();',
3926' }',
3927' }',
3928' } else {',
3929' displayNoMatches();',
3930' }',
3931' }',
3932' }',
3933'',
3934' function refreshCurrentMatch() {',
3935' if (currentSearch && currentSearch.hasVisibleMatches()) {',
3936' setCurrentMatchIndex(currentMatchIndex);',
3937' }',
3938' }',
3939'',
3940' function displayMatches() {',
3941' replaceClass($("searchBox"), "hasmatches", "nomatches");',
3942' $("searchBox").title = "" + currentSearch.matches.length + " matches found";',
3943' $("searchNav").style.display = "block";',
3944' setLogContainerHeight();',
3945' }',
3946'',
3947' function displayNoMatches() {',
3948' replaceClass($("searchBox"), "nomatches", "hasmatches");',
3949' $("searchBox").title = "No matches found";',
3950' $("searchNav").style.display = "none";',
3951' setLogContainerHeight();',
3952' }',
3953'',
3954' function toggleSearchEnabled(enable) {',
3955' enable = (typeof enable == "undefined") ? !$("searchDisable").checked : enable;',
3956' $("searchBox").disabled = !enable;',
3957' $("searchReset").disabled = !enable;',
3958' $("searchRegex").disabled = !enable;',
3959' $("searchNext").disabled = !enable;',
3960' $("searchPrevious").disabled = !enable;',
3961' $("searchCaseSensitive").disabled = !enable;',
3962' $("searchNav").style.display = (enable && ($("searchBox").value !== "") &&',
3963' currentSearch && currentSearch.hasVisibleMatches()) ?',
3964' "block" : "none";',
3965' if (enable) {',
3966' removeClass($("search"), "greyedout");',
3967' addClass(document.body, "searching");',
3968' if ($("searchHighlight").checked) {',
3969' addClass(logMainContainer, "searchhighlight");',
3970' } else {',
3971' removeClass(logMainContainer, "searchhighlight");',
3972' }',
3973' if ($("searchFilter").checked) {',
3974' addClass(logMainContainer, "searchfilter");',
3975' } else {',
3976' removeClass(logMainContainer, "searchfilter");',
3977' }',
3978' $("searchDisable").checked = !enable;',
3979' } else {',
3980' addClass($("search"), "greyedout");',
3981' removeClass(document.body, "searching");',
3982' removeClass(logMainContainer, "searchhighlight");',
3983' removeClass(logMainContainer, "searchfilter");',
3984' }',
3985' setLogContainerHeight();',
3986' }',
3987'',
3988' function toggleSearchFilter() {',
3989' var enable = $("searchFilter").checked;',
3990' if (enable) {',
3991' addClass(logMainContainer, "searchfilter");',
3992' } else {',
3993' removeClass(logMainContainer, "searchfilter");',
3994' }',
3995' refreshCurrentMatch();',
3996' }',
3997'',
3998' function toggleSearchHighlight() {',
3999' var enable = $("searchHighlight").checked;',
4000' if (enable) {',
4001' addClass(logMainContainer, "searchhighlight");',
4002' } else {',
4003' removeClass(logMainContainer, "searchhighlight");',
4004' }',
4005' }',
4006'',
4007' function clearSearch() {',
4008' $("searchBox").value = "";',
4009' doSearch();',
4010' }',
4011'',
4012' function searchNext() {',
4013' if (currentSearch !== null && currentMatchIndex !== null) {',
4014' currentSearch.matches[currentMatchIndex].setNotCurrent();',
4015' var nextMatchIndex = currentSearch.getNextVisibleMatchIndex();',
4016' if (nextMatchIndex > currentMatchIndex || confirm("Reached the end of the page. Start from the top?")) {',
4017' setCurrentMatchIndex(nextMatchIndex);',
4018' }',
4019' }',
4020' }',
4021'',
4022' function searchPrevious() {',
4023' if (currentSearch !== null && currentMatchIndex !== null) {',
4024' currentSearch.matches[currentMatchIndex].setNotCurrent();',
4025' var previousMatchIndex = currentSearch.getPreviousVisibleMatchIndex();',
4026' if (previousMatchIndex < currentMatchIndex || confirm("Reached the start of the page. Continue from the bottom?")) {',
4027' setCurrentMatchIndex(previousMatchIndex);',
4028' }',
4029' }',
4030' }',
4031'',
4032' function setCurrentMatchIndex(index) {',
4033' currentMatchIndex = index;',
4034' currentSearch.matches[currentMatchIndex].setCurrent();',
4035' }',
4036'',
4037' /* ------------------------------------------------------------------------- */',
4038'',
4039' // CSS Utilities',
4040'',
4041' function addClass(el, cssClass) {',
4042' if (!hasClass(el, cssClass)) {',
4043' if (el.className) {',
4044' el.className += " " + cssClass;',
4045' } else {',
4046' el.className = cssClass;',
4047' }',
4048' }',
4049' }',
4050'',
4051' function hasClass(el, cssClass) {',
4052' if (el.className) {',
4053' var classNames = el.className.split(" ");',
4054' return array_contains(classNames, cssClass);',
4055' }',
4056' return false;',
4057' }',
4058'',
4059' function removeClass(el, cssClass) {',
4060' if (hasClass(el, cssClass)) {',
4061' // Rebuild the className property',
4062' var existingClasses = el.className.split(" ");',
4063' var newClasses = [];',
4064' for (var i = 0, len = existingClasses.length; i < len; i++) {',
4065' if (existingClasses[i] != cssClass) {',
4066' newClasses[newClasses.length] = existingClasses[i];',
4067' }',
4068' }',
4069' el.className = newClasses.join(" ");',
4070' }',
4071' }',
4072'',
4073' function replaceClass(el, newCssClass, oldCssClass) {',
4074' removeClass(el, oldCssClass);',
4075' addClass(el, newCssClass);',
4076' }',
4077'',
4078' /* ------------------------------------------------------------------------- */',
4079'',
4080' // Other utility functions',
4081'',
4082' function getElementsByClass(el, cssClass, tagName) {',
4083' var elements = el.getElementsByTagName(tagName);',
4084' var matches = [];',
4085' for (var i = 0, len = elements.length; i < len; i++) {',
4086' if (hasClass(elements[i], cssClass)) {',
4087' matches.push(elements[i]);',
4088' }',
4089' }',
4090' return matches;',
4091' }',
4092'',
4093' // Syntax borrowed from Prototype library',
4094' function $(id) {',
4095' return document.getElementById(id);',
4096' }',
4097'',
4098' function isDescendant(node, ancestorNode) {',
4099' while (node != null) {',
4100' if (node === ancestorNode) {',
4101' return true;',
4102' }',
4103' node = node.parentNode;',
4104' }',
4105' return false;',
4106' }',
4107'',
4108' function isOrphan(node) {',
4109' var currentNode = node;',
4110' while (currentNode) {',
4111' if (currentNode == document.body) {',
4112' return false;',
4113' }',
4114' currentNode = currentNode.parentNode;',
4115' }',
4116' return true;',
4117' }',
4118'',
4119' function escapeHtml(str) {',
4120' return str.replace(/&/g, "&amp;").replace(/[<]/g, "&lt;").replace(/>/g, "&gt;");',
4121' }',
4122'',
4123' function getWindowWidth() {',
4124' if (window.innerWidth) {',
4125' return window.innerWidth;',
4126' } else if (document.documentElement && document.documentElement.clientWidth) {',
4127' return document.documentElement.clientWidth;',
4128' } else if (document.body) {',
4129' return document.body.clientWidth;',
4130' }',
4131' return 0;',
4132' }',
4133'',
4134' function getWindowHeight() {',
4135' if (window.innerHeight) {',
4136' return window.innerHeight;',
4137' } else if (document.documentElement && document.documentElement.clientHeight) {',
4138' return document.documentElement.clientHeight;',
4139' } else if (document.body) {',
4140' return document.body.clientHeight;',
4141' }',
4142' return 0;',
4143' }',
4144'',
4145' function getToolBarsHeight() {',
4146' return $("switches").offsetHeight;',
4147' }',
4148'',
4149' function getChromeHeight() {',
4150' var height = getToolBarsHeight();',
4151' if (showCommandLine) {',
4152' height += $("commandLine").offsetHeight;',
4153' }',
4154' return height;',
4155' }',
4156'',
4157' function setLogContainerHeight() {',
4158' if (logMainContainer) {',
4159' var windowHeight = getWindowHeight();',
4160' $("body").style.height = getWindowHeight() + "px";',
4161' logMainContainer.style.height = "" +',
4162' Math.max(0, windowHeight - getChromeHeight()) + "px";',
4163' }',
4164' }',
4165'',
4166' function setCommandInputWidth() {',
4167' if (showCommandLine) {',
4168' $("command").style.width = "" + Math.max(0, $("commandLineContainer").offsetWidth -',
4169' ($("evaluateButton").offsetWidth + 13)) + "px";',
4170' }',
4171' }',
4172'',
4173' window.onresize = function() {',
4174' setCommandInputWidth();',
4175' setLogContainerHeight();',
4176' };',
4177'',
4178' if (!Array.prototype.push) {',
4179' Array.prototype.push = function() {',
4180' for (var i = 0, len = arguments.length; i < len; i++){',
4181' this[this.length] = arguments[i];',
4182' }',
4183' return this.length;',
4184' };',
4185' }',
4186'',
4187' if (!Array.prototype.pop) {',
4188' Array.prototype.pop = function() {',
4189' if (this.length > 0) {',
4190' var val = this[this.length - 1];',
4191' this.length = this.length - 1;',
4192' return val;',
4193' }',
4194' };',
4195' }',
4196'',
4197' if (!Array.prototype.shift) {',
4198' Array.prototype.shift = function() {',
4199' if (this.length > 0) {',
4200' var firstItem = this[0];',
4201' for (var i = 0, len = this.length - 1; i < len; i++) {',
4202' this[i] = this[i + 1];',
4203' }',
4204' this.length = this.length - 1;',
4205' return firstItem;',
4206' }',
4207' };',
4208' }',
4209'',
4210' if (!Array.prototype.splice) {',
4211' Array.prototype.splice = function(startIndex, deleteCount) {',
4212' var itemsAfterDeleted = this.slice(startIndex + deleteCount);',
4213' var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);',
4214' this.length = startIndex;',
4215' // Copy the arguments into a proper Array object',
4216' var argumentsArray = [];',
4217' for (var i = 0, len = arguments.length; i < len; i++) {',
4218' argumentsArray[i] = arguments[i];',
4219' }',
4220' var itemsToAppend = (argumentsArray.length > 2) ?',
4221' itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;',
4222' for (i = 0, len = itemsToAppend.length; i < len; i++) {',
4223' this.push(itemsToAppend[i]);',
4224' }',
4225' return itemsDeleted;',
4226' };',
4227' }',
4228'',
4229' function array_remove(arr, val) {',
4230' var index = -1;',
4231' for (var i = 0, len = arr.length; i < len; i++) {',
4232' if (arr[i] === val) {',
4233' index = i;',
4234' break;',
4235' }',
4236' }',
4237' if (index >= 0) {',
4238' arr.splice(index, 1);',
4239' return index;',
4240' } else {',
4241' return false;',
4242' }',
4243' }',
4244'',
4245' function array_removeFromStart(array, numberToRemove) {',
4246' if (Array.prototype.splice) {',
4247' array.splice(0, numberToRemove);',
4248' } else {',
4249' for (var i = numberToRemove, len = array.length; i < len; i++) {',
4250' array[i - numberToRemove] = array[i];',
4251' }',
4252' array.length = array.length - numberToRemove;',
4253' }',
4254' return array;',
4255' }',
4256'',
4257' function array_contains(arr, val) {',
4258' for (var i = 0, len = arr.length; i < len; i++) {',
4259' if (arr[i] == val) {',
4260' return true;',
4261' }',
4262' }',
4263' return false;',
4264' }',
4265'',
4266' function getErrorMessage(ex) {',
4267' if (ex.message) {',
4268' return ex.message;',
4269' } else if (ex.description) {',
4270' return ex.description;',
4271' }',
4272' return "" + ex;',
4273' }',
4274'',
4275' function moveCaretToEnd(input) {',
4276' if (input.setSelectionRange) {',
4277' input.focus();',
4278' var length = input.value.length;',
4279' input.setSelectionRange(length, length);',
4280' } else if (input.createTextRange) {',
4281' var range = input.createTextRange();',
4282' range.collapse(false);',
4283' range.select();',
4284' }',
4285' input.focus();',
4286' }',
4287'',
4288' function stopPropagation(evt) {',
4289' if (evt.stopPropagation) {',
4290' evt.stopPropagation();',
4291' } else if (typeof evt.cancelBubble != "undefined") {',
4292' evt.cancelBubble = true;',
4293' }',
4294' }',
4295'',
4296' function getEvent(evt) {',
4297' return evt ? evt : event;',
4298' }',
4299'',
4300' function getTarget(evt) {',
4301' return evt.target ? evt.target : evt.srcElement;',
4302' }',
4303'',
4304' function getRelatedTarget(evt) {',
4305' if (evt.relatedTarget) {',
4306' return evt.relatedTarget;',
4307' } else if (evt.srcElement) {',
4308' switch(evt.type) {',
4309' case "mouseover":',
4310' return evt.fromElement;',
4311' case "mouseout":',
4312' return evt.toElement;',
4313' default:',
4314' return evt.srcElement;',
4315' }',
4316' }',
4317' }',
4318'',
4319' function cancelKeyEvent(evt) {',
4320' evt.returnValue = false;',
4321' stopPropagation(evt);',
4322' }',
4323'',
4324' function evalCommandLine() {',
4325' var expr = $("command").value;',
4326' evalCommand(expr);',
4327' $("command").value = "";',
4328' }',
4329'',
4330' function evalLastCommand() {',
4331' if (lastCommand != null) {',
4332' evalCommand(lastCommand);',
4333' }',
4334' }',
4335'',
4336' var lastCommand = null;',
4337' var commandHistory = [];',
4338' var currentCommandIndex = 0;',
4339'',
4340' function evalCommand(expr) {',
4341' if (appender) {',
4342' appender.evalCommandAndAppend(expr);',
4343' } else {',
4344' var prefix = ">>> " + expr + "\\r\\n";',
4345' try {',
4346' log("INFO", prefix + eval(expr));',
4347' } catch (ex) {',
4348' log("ERROR", prefix + "Error: " + getErrorMessage(ex));',
4349' }',
4350' }',
4351' // Update command history',
4352' if (expr != commandHistory[commandHistory.length - 1]) {',
4353' commandHistory.push(expr);',
4354' // Update the appender',
4355' if (appender) {',
4356' appender.storeCommandHistory(commandHistory);',
4357' }',
4358' }',
4359' currentCommandIndex = (expr == commandHistory[currentCommandIndex]) ? currentCommandIndex + 1 : commandHistory.length;',
4360' lastCommand = expr;',
4361' }',
4362' //]]>',
4363' </script>',
4364' <style type="text/css">',
4365' body {',
4366' background-color: white;',
4367' color: black;',
4368' padding: 0;',
4369' margin: 0;',
4370' font-family: tahoma, verdana, arial, helvetica, sans-serif;',
4371' overflow: hidden;',
4372' }',
4373'',
4374' div#switchesContainer input {',
4375' margin-bottom: 0;',
4376' }',
4377'',
4378' div.toolbar {',
4379' border-top: solid #ffffff 1px;',
4380' border-bottom: solid #aca899 1px;',
4381' background-color: #f1efe7;',
4382' padding: 3px 5px;',
4383' font-size: 68.75%;',
4384' }',
4385'',
4386' div.toolbar, div#search input {',
4387' font-family: tahoma, verdana, arial, helvetica, sans-serif;',
4388' }',
4389'',
4390' div.toolbar input.button {',
4391' padding: 0 5px;',
4392' font-size: 100%;',
4393' }',
4394'',
4395' div.toolbar input.hidden {',
4396' display: none;',
4397' }',
4398'',
4399' div#switches input#clearButton {',
4400' margin-left: 20px;',
4401' }',
4402'',
4403' div#levels label {',
4404' font-weight: bold;',
4405' }',
4406'',
4407' div#levels label, div#options label {',
4408' margin-right: 5px;',
4409' }',
4410'',
4411' div#levels label#wrapLabel {',
4412' font-weight: normal;',
4413' }',
4414'',
4415' div#search label {',
4416' margin-right: 10px;',
4417' }',
4418'',
4419' div#search label.searchboxlabel {',
4420' margin-right: 0;',
4421' }',
4422'',
4423' div#search input {',
4424' font-size: 100%;',
4425' }',
4426'',
4427' div#search input.validregex {',
4428' color: green;',
4429' }',
4430'',
4431' div#search input.invalidregex {',
4432' color: red;',
4433' }',
4434'',
4435' div#search input.nomatches {',
4436' color: white;',
4437' background-color: #ff6666;',
4438' }',
4439'',
4440' div#search input.nomatches {',
4441' color: white;',
4442' background-color: #ff6666;',
4443' }',
4444'',
4445' div#searchNav {',
4446' display: none;',
4447' }',
4448'',
4449' div#commandLine {',
4450' display: none;',
4451' }',
4452'',
4453' div#commandLine input#command {',
4454' font-size: 100%;',
4455' font-family: Courier New, Courier;',
4456' }',
4457'',
4458' div#commandLine input#evaluateButton {',
4459' }',
4460'',
4461' *.greyedout {',
4462' color: gray !important;',
4463' border-color: gray !important;',
4464' }',
4465'',
4466' *.greyedout *.alwaysenabled { color: black; }',
4467'',
4468' *.unselectable {',
4469' -khtml-user-select: none;',
4470' -moz-user-select: none;',
4471' user-select: none;',
4472' }',
4473'',
4474' div#log {',
4475' font-family: Courier New, Courier;',
4476' font-size: 75%;',
4477' width: 100%;',
4478' overflow: auto;',
4479' clear: both;',
4480' position: relative;',
4481' }',
4482'',
4483' div.group {',
4484' border-color: #cccccc;',
4485' border-style: solid;',
4486' border-width: 1px 0 1px 1px;',
4487' overflow: visible;',
4488' }',
4489'',
4490' div.oldIe div.group, div.oldIe div.group *, div.oldIe *.logentry {',
4491' height: 1%;',
4492' }',
4493'',
4494' div.group div.groupheading span.expander {',
4495' border: solid black 1px;',
4496' font-family: Courier New, Courier;',
4497' font-size: 0.833em;',
4498' background-color: #eeeeee;',
4499' position: relative;',
4500' top: -1px;',
4501' color: black;',
4502' padding: 0 2px;',
4503' cursor: pointer;',
4504' cursor: hand;',
4505' height: 1%;',
4506' }',
4507'',
4508' div.group div.groupcontent {',
4509' margin-left: 10px;',
4510' padding-bottom: 2px;',
4511' overflow: visible;',
4512' }',
4513'',
4514' div.group div.expanded {',
4515' display: block;',
4516' }',
4517'',
4518' div.group div.collapsed {',
4519' display: none;',
4520' }',
4521'',
4522' *.logentry {',
4523' overflow: visible;',
4524' display: none;',
4525' white-space: pre;',
4526' }',
4527'',
4528' span.pre {',
4529' white-space: pre;',
4530' }',
4531' ',
4532' pre.unwrapped {',
4533' display: inline !important;',
4534' }',
4535'',
4536' pre.unwrapped pre.pre, div.wrapped pre.pre {',
4537' display: inline;',
4538' }',
4539'',
4540' div.wrapped pre.pre {',
4541' white-space: normal;',
4542' }',
4543'',
4544' div.wrapped {',
4545' display: none;',
4546' }',
4547'',
4548' body.searching *.logentry span.currentmatch {',
4549' color: white !important;',
4550' background-color: green !important;',
4551' }',
4552'',
4553' body.searching div.searchhighlight *.logentry span.searchterm {',
4554' color: black;',
4555' background-color: yellow;',
4556' }',
4557'',
4558' div.wrap *.logentry {',
4559' white-space: normal !important;',
4560' border-width: 0 0 1px 0;',
4561' border-color: #dddddd;',
4562' border-style: dotted;',
4563' }',
4564'',
4565' div.wrap #log_wrapped, #log_unwrapped {',
4566' display: block;',
4567' }',
4568'',
4569' div.wrap #log_unwrapped, #log_wrapped {',
4570' display: none;',
4571' }',
4572'',
4573' div.wrap *.logentry span.pre {',
4574' overflow: visible;',
4575' white-space: normal;',
4576' }',
4577'',
4578' div.wrap *.logentry pre.unwrapped {',
4579' display: none;',
4580' }',
4581'',
4582' div.wrap *.logentry span.wrapped {',
4583' display: inline;',
4584' }',
4585'',
4586' div.searchfilter *.searchnonmatch {',
4587' display: none !important;',
4588' }',
4589'',
4590' div#log *.TRACE, label#label_TRACE {',
4591' color: #666666;',
4592' }',
4593'',
4594' div#log *.DEBUG, label#label_DEBUG {',
4595' color: green;',
4596' }',
4597'',
4598' div#log *.INFO, label#label_INFO {',
4599' color: #000099;',
4600' }',
4601'',
4602' div#log *.WARN, label#label_WARN {',
4603' color: #999900;',
4604' }',
4605'',
4606' div#log *.ERROR, label#label_ERROR {',
4607' color: red;',
4608' }',
4609'',
4610' div#log *.FATAL, label#label_FATAL {',
4611' color: #660066;',
4612' }',
4613'',
4614' div.TRACE#log *.TRACE,',
4615' div.DEBUG#log *.DEBUG,',
4616' div.INFO#log *.INFO,',
4617' div.WARN#log *.WARN,',
4618' div.ERROR#log *.ERROR,',
4619' div.FATAL#log *.FATAL {',
4620' display: block;',
4621' }',
4622'',
4623' div#log div.separator {',
4624' background-color: #cccccc;',
4625' margin: 5px 0;',
4626' line-height: 1px;',
4627' }',
4628' </style>',
4629' </head>',
4630'',
4631' <body id="body">',
4632' <div id="switchesContainer">',
4633' <div id="switches">',
4634' <div id="levels" class="toolbar">',
4635' Filters:',
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>',
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>',
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>',
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>',
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>',
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>',
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>',
4643' </div>',
4644' <div id="search" class="toolbar">',
4645' <label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" />',
4646' <input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" />',
4647' <input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label>',
4648' <input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label>',
4649' <input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label>',
4650' <div id="searchNav">',
4651' <input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext()" class="button" title="Go to the next matching log entry" />',
4652' <input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" />',
4653' <input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label>',
4654' <input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label>',
4655' </div>',
4656' </div>',
4657' <div id="options" class="toolbar">',
4658' Options:',
4659' <input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label>',
4660' <input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label>',
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>',
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>',
4663' <input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages" />',
4664' <input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" />',
4665' <input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" />',
4666' </div>',
4667' </div>',
4668' </div>',
4669' <div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div>',
4670' <div id="commandLine" class="toolbar">',
4671' <div id="commandLineContainer">',
4672' <input type="text" id="command" title="Enter a JavaScript command here and hit return or press \'Evaluate\'" />',
4673' <input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" />',
4674' </div>',
4675' </div>',
4676' </body>',
4677'</html>',
4678''
4679];
4680 };
4681
4682 var defaultCommandLineFunctions = [];
4683
4684 ConsoleAppender = function() {};
4685
4686 var consoleAppenderIdCounter = 1;
4687 ConsoleAppender.prototype = new Appender();
4688
4689 ConsoleAppender.prototype.create = function(inPage, container,
4690 lazyInit, initiallyMinimized, useDocumentWrite, width, height, focusConsoleWindow) {
4691 var appender = this;
4692
4693 // Common properties
4694 var initialized = false;
4695 var consoleWindowCreated = false;
4696 var consoleWindowLoaded = false;
4697 var consoleClosed = false;
4698
4699 var queuedLoggingEvents = [];
4700 var isSupported = true;
4701 var consoleAppenderId = consoleAppenderIdCounter++;
4702
4703 // Local variables
4704 initiallyMinimized = extractBooleanFromParam(initiallyMinimized, this.defaults.initiallyMinimized);
4705 lazyInit = extractBooleanFromParam(lazyInit, this.defaults.lazyInit);
4706 useDocumentWrite = extractBooleanFromParam(useDocumentWrite, this.defaults.useDocumentWrite);
4707 var newestMessageAtTop = this.defaults.newestMessageAtTop;
4708 var scrollToLatestMessage = this.defaults.scrollToLatestMessage;
4709 width = width ? width : this.defaults.width;
4710 height = height ? height : this.defaults.height;
4711 var maxMessages = this.defaults.maxMessages;
4712 var showCommandLine = this.defaults.showCommandLine;
4713 var commandLineObjectExpansionDepth = this.defaults.commandLineObjectExpansionDepth;
4714 var showHideButton = this.defaults.showHideButton;
4715 var showCloseButton = this.defaults.showCloseButton;
4716 var showLogEntryDeleteButtons = this.defaults.showLogEntryDeleteButtons;
4717
4718 this.setLayout(this.defaults.layout);
4719
4720 // Functions whose implementations vary between subclasses
4721 var init, createWindow, safeToAppend, getConsoleWindow, open;
4722
4723 // Configuration methods. The function scope is used to prevent
4724 // direct alteration to the appender configuration properties.
4725 var appenderName = inPage ? "InPageAppender" : "PopUpAppender";
4726 var checkCanConfigure = function(configOptionName) {
4727 if (consoleWindowCreated) {
4728 handleError(appenderName + ": configuration option '" + configOptionName + "' may not be set after the appender has been initialized");
4729 return false;
4730 }
4731 return true;
4732 };
4733
4734 var consoleWindowExists = function() {
4735 return (consoleWindowLoaded && isSupported && !consoleClosed);
4736 };
4737
4738 this.isNewestMessageAtTop = function() { return newestMessageAtTop; };
4739 this.setNewestMessageAtTop = function(newestMessageAtTopParam) {
4740 newestMessageAtTop = bool(newestMessageAtTopParam);
4741 if (consoleWindowExists()) {
4742 getConsoleWindow().setNewestAtTop(newestMessageAtTop);
4743 }
4744 };
4745
4746 this.isScrollToLatestMessage = function() { return scrollToLatestMessage; };
4747 this.setScrollToLatestMessage = function(scrollToLatestMessageParam) {
4748 scrollToLatestMessage = bool(scrollToLatestMessageParam);
4749 if (consoleWindowExists()) {
4750 getConsoleWindow().setScrollToLatest(scrollToLatestMessage);
4751 }
4752 };
4753
4754 this.getWidth = function() { return width; };
4755 this.setWidth = function(widthParam) {
4756 if (checkCanConfigure("width")) {
4757 width = extractStringFromParam(widthParam, width);
4758 }
4759 };
4760
4761 this.getHeight = function() { return height; };
4762 this.setHeight = function(heightParam) {
4763 if (checkCanConfigure("height")) {
4764 height = extractStringFromParam(heightParam, height);
4765 }
4766 };
4767
4768 this.getMaxMessages = function() { return maxMessages; };
4769 this.setMaxMessages = function(maxMessagesParam) {
4770 maxMessages = extractIntFromParam(maxMessagesParam, maxMessages);
4771 if (consoleWindowExists()) {
4772 getConsoleWindow().setMaxMessages(maxMessages);
4773 }
4774 };
4775
4776 this.isShowCommandLine = function() { return showCommandLine; };
4777 this.setShowCommandLine = function(showCommandLineParam) {
4778 showCommandLine = bool(showCommandLineParam);
4779 if (consoleWindowExists()) {
4780 getConsoleWindow().setShowCommandLine(showCommandLine);
4781 }
4782 };
4783
4784 this.isShowHideButton = function() { return showHideButton; };
4785 this.setShowHideButton = function(showHideButtonParam) {
4786 showHideButton = bool(showHideButtonParam);
4787 if (consoleWindowExists()) {
4788 getConsoleWindow().setShowHideButton(showHideButton);
4789 }
4790 };
4791
4792 this.isShowCloseButton = function() { return showCloseButton; };
4793 this.setShowCloseButton = function(showCloseButtonParam) {
4794 showCloseButton = bool(showCloseButtonParam);
4795 if (consoleWindowExists()) {
4796 getConsoleWindow().setShowCloseButton(showCloseButton);
4797 }
4798 };
4799
4800 this.getCommandLineObjectExpansionDepth = function() { return commandLineObjectExpansionDepth; };
4801 this.setCommandLineObjectExpansionDepth = function(commandLineObjectExpansionDepthParam) {
4802 commandLineObjectExpansionDepth = extractIntFromParam(commandLineObjectExpansionDepthParam, commandLineObjectExpansionDepth);
4803 };
4804
4805 var minimized = initiallyMinimized;
4806 this.isInitiallyMinimized = function() { return initiallyMinimized; };
4807 this.setInitiallyMinimized = function(initiallyMinimizedParam) {
4808 if (checkCanConfigure("initiallyMinimized")) {
4809 initiallyMinimized = bool(initiallyMinimizedParam);
4810 minimized = initiallyMinimized;
4811 }
4812 };
4813
4814 this.isUseDocumentWrite = function() { return useDocumentWrite; };
4815 this.setUseDocumentWrite = function(useDocumentWriteParam) {
4816 if (checkCanConfigure("useDocumentWrite")) {
4817 useDocumentWrite = bool(useDocumentWriteParam);
4818 }
4819 };
4820
4821 // Common methods
4822 function QueuedLoggingEvent(loggingEvent, formattedMessage) {
4823 this.loggingEvent = loggingEvent;
4824 this.levelName = loggingEvent.level.name;
4825 this.formattedMessage = formattedMessage;
4826 }
4827
4828 QueuedLoggingEvent.prototype.append = function() {
4829 getConsoleWindow().log(this.levelName, this.formattedMessage);
4830 };
4831
4832 function QueuedGroup(name, initiallyExpanded) {
4833 this.name = name;
4834 this.initiallyExpanded = initiallyExpanded;
4835 }
4836
4837 QueuedGroup.prototype.append = function() {
4838 getConsoleWindow().group(this.name, this.initiallyExpanded);
4839 };
4840
4841 function QueuedGroupEnd() {}
4842
4843 QueuedGroupEnd.prototype.append = function() {
4844 getConsoleWindow().groupEnd();
4845 };
4846
4847 var checkAndAppend = function() {
4848 // Next line forces a check of whether the window has been closed
4849 safeToAppend();
4850 if (!initialized) {
4851 init();
4852 } else if (consoleClosed && reopenWhenClosed) {
4853 createWindow();
4854 }
4855 if (safeToAppend()) {
4856 appendQueuedLoggingEvents();
4857 }
4858 };
4859
4860 this.append = function(loggingEvent) {
4861 if (isSupported) {
4862 // Format the message
4863 var formattedMessage = appender.getLayout().format(loggingEvent);
4864 if (this.getLayout().ignoresThrowable()) {
4865 formattedMessage += loggingEvent.getThrowableStrRep();
4866 }
4867 queuedLoggingEvents.push(new QueuedLoggingEvent(loggingEvent, formattedMessage));
4868 checkAndAppend();
4869 }
4870 };
4871
4872 this.group = function(name, initiallyExpanded) {
4873 if (isSupported) {
4874 queuedLoggingEvents.push(new QueuedGroup(name, initiallyExpanded));
4875 checkAndAppend();
4876 }
4877 };
4878
4879 this.groupEnd = function() {
4880 if (isSupported) {
4881 queuedLoggingEvents.push(new QueuedGroupEnd());
4882 checkAndAppend();
4883 }
4884 };
4885
4886 var appendQueuedLoggingEvents = function() {
4887 var currentLoggingEvent;
4888 while (queuedLoggingEvents.length > 0) {
4889 queuedLoggingEvents.shift().append();
4890 }
4891 if (focusConsoleWindow) {
4892 getConsoleWindow().focus();
4893 }
4894 };
4895
4896 this.setAddedToLogger = function(logger) {
4897 this.loggers.push(logger);
4898 if (enabled && !lazyInit) {
4899 init();
4900 }
4901 };
4902
4903 this.clear = function() {
4904 if (consoleWindowExists()) {
4905 getConsoleWindow().clearLog();
4906 }
4907 queuedLoggingEvents.length = 0;
4908 };
4909
4910 this.focus = function() {
4911 if (consoleWindowExists()) {
4912 getConsoleWindow().focus();
4913 }
4914 };
4915
4916 this.focusCommandLine = function() {
4917 if (consoleWindowExists()) {
4918 getConsoleWindow().focusCommandLine();
4919 }
4920 };
4921
4922 this.focusSearch = function() {
4923 if (consoleWindowExists()) {
4924 getConsoleWindow().focusSearch();
4925 }
4926 };
4927
4928 var commandWindow = window;
4929
4930 this.getCommandWindow = function() { return commandWindow; };
4931 this.setCommandWindow = function(commandWindowParam) {
4932 commandWindow = commandWindowParam;
4933 };
4934
4935 this.executeLastCommand = function() {
4936 if (consoleWindowExists()) {
4937 getConsoleWindow().evalLastCommand();
4938 }
4939 };
4940
4941 var commandLayout = new PatternLayout("%m");
4942 this.getCommandLayout = function() { return commandLayout; };
4943 this.setCommandLayout = function(commandLayoutParam) {
4944 commandLayout = commandLayoutParam;
4945 };
4946
4947 this.evalCommandAndAppend = function(expr) {
4948 var commandReturnValue = { appendResult: true, isError: false };
4949 var commandOutput = "";
4950 // Evaluate the command
4951 try {
4952 var result, i;
4953 // The next three lines constitute a workaround for IE. Bizarrely, iframes seem to have no
4954 // eval method on the window object initially, but once execScript has been called on
4955 // it once then the eval method magically appears. See http://www.thismuchiknow.co.uk/?p=25
4956 if (!commandWindow.eval && commandWindow.execScript) {
4957 commandWindow.execScript("null");
4958 }
4959
4960 var commandLineFunctionsHash = {};
4961 for (i = 0, len = commandLineFunctions.length; i < len; i++) {
4962 commandLineFunctionsHash[commandLineFunctions[i][0]] = commandLineFunctions[i][1];
4963 }
4964
4965 // Keep an array of variables that are being changed in the command window so that they
4966 // can be restored to their original values afterwards
4967 var objectsToRestore = [];
4968 var addObjectToRestore = function(name) {
4969 objectsToRestore.push([name, commandWindow[name]]);
4970 };
4971
4972 addObjectToRestore("appender");
4973 commandWindow.appender = appender;
4974
4975 addObjectToRestore("commandReturnValue");
4976 commandWindow.commandReturnValue = commandReturnValue;
4977
4978 addObjectToRestore("commandLineFunctionsHash");
4979 commandWindow.commandLineFunctionsHash = commandLineFunctionsHash;
4980
4981 var addFunctionToWindow = function(name) {
4982 addObjectToRestore(name);
4983 commandWindow[name] = function() {
4984 return this.commandLineFunctionsHash[name](appender, arguments, commandReturnValue);
4985 };
4986 };
4987
4988 for (i = 0, len = commandLineFunctions.length; i < len; i++) {
4989 addFunctionToWindow(commandLineFunctions[i][0]);
4990 }
4991
4992 // Another bizarre workaround to get IE to eval in the global scope
4993 if (commandWindow === window && commandWindow.execScript) {
4994 addObjectToRestore("evalExpr");
4995 addObjectToRestore("result");
4996 window.evalExpr = expr;
4997 commandWindow.execScript("window.result=eval(window.evalExpr);");
4998 result = window.result;
4999 } else {
5000 result = commandWindow.eval(expr);
5001 }
5002 commandOutput = isUndefined(result) ? result : formatObjectExpansion(result, commandLineObjectExpansionDepth);
5003
5004 // Restore variables in the command window to their original state
5005 for (i = 0, len = objectsToRestore.length; i < len; i++) {
5006 commandWindow[objectsToRestore[i][0]] = objectsToRestore[i][1];
5007 }
5008 } catch (ex) {
5009 commandOutput = "Error evaluating command: " + getExceptionStringRep(ex);
5010 commandReturnValue.isError = true;
5011 }
5012 // Append command output
5013 if (commandReturnValue.appendResult) {
5014 var message = ">>> " + expr;
5015 if (!isUndefined(commandOutput)) {
5016 message += newLine + commandOutput;
5017 }
5018 var level = commandReturnValue.isError ? Level.ERROR : Level.INFO;
5019 var loggingEvent = new LoggingEvent(null, new Date(), level, [message], null);
5020 var mainLayout = this.getLayout();
5021 this.setLayout(commandLayout);
5022 this.append(loggingEvent);
5023 this.setLayout(mainLayout);
5024 }
5025 };
5026
5027 var commandLineFunctions = defaultCommandLineFunctions.concat([]);
5028
5029 this.addCommandLineFunction = function(functionName, commandLineFunction) {
5030 commandLineFunctions.push([functionName, commandLineFunction]);
5031 };
5032
5033 var commandHistoryCookieName = "log4javascriptCommandHistory";
5034 this.storeCommandHistory = function(commandHistory) {
5035 setCookie(commandHistoryCookieName, commandHistory.join(","));
5036 };
5037
5038 var writeHtml = function(doc) {
5039 var lines = getConsoleHtmlLines();
5040 doc.open();
5041 for (var i = 0, len = lines.length; i < len; i++) {
5042 doc.writeln(lines[i]);
5043 }
5044 doc.close();
5045 };
5046
5047 // Set up event listeners
5048 this.setEventTypes(["load", "unload"]);
5049
5050 var consoleWindowLoadHandler = function() {
5051 var win = getConsoleWindow();
5052 win.setAppender(appender);
5053 win.setNewestAtTop(newestMessageAtTop);
5054 win.setScrollToLatest(scrollToLatestMessage);
5055 win.setMaxMessages(maxMessages);
5056 win.setShowCommandLine(showCommandLine);
5057 win.setShowHideButton(showHideButton);
5058 win.setShowCloseButton(showCloseButton);
5059 win.setMainWindow(window);
5060
5061 // Restore command history stored in cookie
5062 var storedValue = getCookie(commandHistoryCookieName);
5063 if (storedValue) {
5064 win.commandHistory = storedValue.split(",");
5065 win.currentCommandIndex = win.commandHistory.length;
5066 }
5067
5068 appender.dispatchEvent("load", { "win" : win });
5069 };
5070
5071 this.unload = function() {
5072 logLog.debug("unload " + this + ", caller: " + this.unload.caller);
5073 if (!consoleClosed) {
5074 logLog.debug("really doing unload " + this);
5075 consoleClosed = true;
5076 consoleWindowLoaded = false;
5077 consoleWindowCreated = false;
5078 appender.dispatchEvent("unload", {});
5079 }
5080 };
5081
5082 var pollConsoleWindow = function(windowTest, interval, successCallback, errorMessage) {
5083 function doPoll() {
5084 try {
5085 // Test if the console has been closed while polling
5086 if (consoleClosed) {
5087 clearInterval(poll);
5088 }
5089 if (windowTest(getConsoleWindow())) {
5090 clearInterval(poll);
5091 successCallback();
5092 }
5093 } catch (ex) {
5094 clearInterval(poll);
5095 isSupported = false;
5096 handleError(errorMessage, ex);
5097 }
5098 }
5099
5100 // Poll the pop-up since the onload event is not reliable
5101 var poll = setInterval(doPoll, interval);
5102 };
5103
5104 var getConsoleUrl = function() {
5105 var documentDomainSet = (document.domain != location.hostname);
5106 return useDocumentWrite ? "" : getBaseUrl() + "console_uncompressed.html" +
5107 (documentDomainSet ? "?log4javascript_domain=" + escape(document.domain) : "");
5108 };
5109
5110 // Define methods and properties that vary between subclasses
5111 if (inPage) {
5112 // InPageAppender
5113
5114 var containerElement = null;
5115
5116 // Configuration methods. The function scope is used to prevent
5117 // direct alteration to the appender configuration properties.
5118 var cssProperties = [];
5119 this.addCssProperty = function(name, value) {
5120 if (checkCanConfigure("cssProperties")) {
5121 cssProperties.push([name, value]);
5122 }
5123 };
5124
5125 // Define useful variables
5126 var windowCreationStarted = false;
5127 var iframeContainerDiv;
5128 var iframeId = uniqueId + "_InPageAppender_" + consoleAppenderId;
5129
5130 this.hide = function() {
5131 if (initialized && consoleWindowCreated) {
5132 if (consoleWindowExists()) {
5133 getConsoleWindow().$("command").blur();
5134 }
5135 iframeContainerDiv.style.display = "none";
5136 minimized = true;
5137 }
5138 };
5139
5140 this.show = function() {
5141 if (initialized) {
5142 if (consoleWindowCreated) {
5143 iframeContainerDiv.style.display = "block";
5144 this.setShowCommandLine(showCommandLine); // Force IE to update
5145 minimized = false;
5146 } else if (!windowCreationStarted) {
5147 createWindow(true);
5148 }
5149 }
5150 };
5151
5152 this.isVisible = function() {
5153 return !minimized && !consoleClosed;
5154 };
5155
5156 this.close = function(fromButton) {
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?"))) {
5158 iframeContainerDiv.parentNode.removeChild(iframeContainerDiv);
5159 this.unload();
5160 }
5161 };
5162
5163 // Create open, init, getConsoleWindow and safeToAppend functions
5164 open = function() {
5165 var initErrorMessage = "InPageAppender.open: unable to create console iframe";
5166
5167 function finalInit() {
5168 try {
5169 if (!initiallyMinimized) {
5170 appender.show();
5171 }
5172 consoleWindowLoadHandler();
5173 consoleWindowLoaded = true;
5174 appendQueuedLoggingEvents();
5175 } catch (ex) {
5176 isSupported = false;
5177 handleError(initErrorMessage, ex);
5178 }
5179 }
5180
5181 function writeToDocument() {
5182 try {
5183 var windowTest = function(win) { return isLoaded(win); };
5184 if (useDocumentWrite) {
5185 writeHtml(getConsoleWindow().document);
5186 }
5187 if (windowTest(getConsoleWindow())) {
5188 finalInit();
5189 } else {
5190 pollConsoleWindow(windowTest, 100, finalInit, initErrorMessage);
5191 }
5192 } catch (ex) {
5193 isSupported = false;
5194 handleError(initErrorMessage, ex);
5195 }
5196 }
5197
5198 minimized = false;
5199 iframeContainerDiv = containerElement.appendChild(document.createElement("div"));
5200
5201 iframeContainerDiv.style.width = width;
5202 iframeContainerDiv.style.height = height;
5203 iframeContainerDiv.style.border = "solid gray 1px";
5204
5205 for (var i = 0, len = cssProperties.length; i < len; i++) {
5206 iframeContainerDiv.style[cssProperties[i][0]] = cssProperties[i][1];
5207 }
5208
5209 var iframeSrc = useDocumentWrite ? "" : " src='" + getConsoleUrl() + "'";
5210
5211 // Adding an iframe using the DOM would be preferable, but it doesn't work
5212 // in IE5 on Windows, or in Konqueror prior to version 3.5 - in Konqueror
5213 // it creates the iframe fine but I haven't been able to find a way to obtain
5214 // the iframe's window object
5215 iframeContainerDiv.innerHTML = "<iframe id='" + iframeId + "' name='" + iframeId +
5216 "' width='100%' height='100%' frameborder='0'" + iframeSrc +
5217 " scrolling='no'></iframe>";
5218 consoleClosed = false;
5219
5220 // Write the console HTML to the iframe
5221 var iframeDocumentExistsTest = function(win) {
5222 try {
5223 return bool(win) && bool(win.document);
5224 } catch (ex) {
5225 return false;
5226 }
5227 };
5228 if (iframeDocumentExistsTest(getConsoleWindow())) {
5229 writeToDocument();
5230 } else {
5231 pollConsoleWindow(iframeDocumentExistsTest, 100, writeToDocument, initErrorMessage);
5232 }
5233 consoleWindowCreated = true;
5234 };
5235
5236 createWindow = function(show) {
5237 if (show || !initiallyMinimized) {
5238 var pageLoadHandler = function() {
5239 if (!container) {
5240 // Set up default container element
5241 containerElement = document.createElement("div");
5242 containerElement.style.position = "fixed";
5243 containerElement.style.left = "0";
5244 containerElement.style.right = "0";
5245 containerElement.style.bottom = "0";
5246 document.body.appendChild(containerElement);
5247 appender.addCssProperty("borderWidth", "1px 0 0 0");
5248 appender.addCssProperty("zIndex", 1000000); // Can't find anything authoritative that says how big z-index can be
5249 open();
5250 } else {
5251 try {
5252 var el = document.getElementById(container);
5253 if (el.nodeType == 1) {
5254 containerElement = el;
5255 }
5256 open();
5257 } catch (ex) {
5258 handleError("InPageAppender.init: invalid container element '" + container + "' supplied", ex);
5259 }
5260 }
5261 };
5262
5263 // Test the type of the container supplied. First, check if it's an element
5264 if (pageLoaded && container && container.appendChild) {
5265 containerElement = container;
5266 open();
5267 } else if (pageLoaded) {
5268 pageLoadHandler();
5269 } else {
5270 log4javascript.addEventListener("load", pageLoadHandler);
5271 }
5272 windowCreationStarted = true;
5273 }
5274 };
5275
5276 init = function() {
5277 createWindow();
5278 initialized = true;
5279 };
5280
5281 getConsoleWindow = function() {
5282 var iframe = window.frames[iframeId];
5283 if (iframe) {
5284 return iframe;
5285 }
5286 };
5287
5288 safeToAppend = function() {
5289 if (isSupported && !consoleClosed) {
5290 if (consoleWindowCreated && !consoleWindowLoaded && getConsoleWindow() && isLoaded(getConsoleWindow())) {
5291 consoleWindowLoaded = true;
5292 }
5293 return consoleWindowLoaded;
5294 }
5295 return false;
5296 };
5297 } else {
5298 // PopUpAppender
5299
5300 // Extract params
5301 var useOldPopUp = appender.defaults.useOldPopUp;
5302 var complainAboutPopUpBlocking = appender.defaults.complainAboutPopUpBlocking;
5303 var reopenWhenClosed = this.defaults.reopenWhenClosed;
5304
5305 // Configuration methods. The function scope is used to prevent
5306 // direct alteration to the appender configuration properties.
5307 this.isUseOldPopUp = function() { return useOldPopUp; };
5308 this.setUseOldPopUp = function(useOldPopUpParam) {
5309 if (checkCanConfigure("useOldPopUp")) {
5310 useOldPopUp = bool(useOldPopUpParam);
5311 }
5312 };
5313
5314 this.isComplainAboutPopUpBlocking = function() { return complainAboutPopUpBlocking; };
5315 this.setComplainAboutPopUpBlocking = function(complainAboutPopUpBlockingParam) {
5316 if (checkCanConfigure("complainAboutPopUpBlocking")) {
5317 complainAboutPopUpBlocking = bool(complainAboutPopUpBlockingParam);
5318 }
5319 };
5320
5321 this.isFocusPopUp = function() { return focusConsoleWindow; };
5322 this.setFocusPopUp = function(focusPopUpParam) {
5323 // This property can be safely altered after logging has started
5324 focusConsoleWindow = bool(focusPopUpParam);
5325 };
5326
5327 this.isReopenWhenClosed = function() { return reopenWhenClosed; };
5328 this.setReopenWhenClosed = function(reopenWhenClosedParam) {
5329 // This property can be safely altered after logging has started
5330 reopenWhenClosed = bool(reopenWhenClosedParam);
5331 };
5332
5333 this.close = function() {
5334 logLog.debug("close " + this);
5335 try {
5336 popUp.close();
5337 this.unload();
5338 } catch (ex) {
5339 // Do nothing
5340 }
5341 };
5342
5343 this.hide = function() {
5344 logLog.debug("hide " + this);
5345 if (consoleWindowExists()) {
5346 this.close();
5347 }
5348 };
5349
5350 this.show = function() {
5351 logLog.debug("show " + this);
5352 if (!consoleWindowCreated) {
5353 open();
5354 }
5355 };
5356
5357 this.isVisible = function() {
5358 return safeToAppend();
5359 };
5360
5361 // Define useful variables
5362 var popUp;
5363
5364 // Create open, init, getConsoleWindow and safeToAppend functions
5365 open = function() {
5366 var windowProperties = "width=" + width + ",height=" + height + ",status,resizable";
5367 var frameInfo = "";
5368 try {
5369 var frameEl = window.frameElement;
5370 if (frameEl) {
5371 frameInfo = "_" + frameEl.tagName + "_" + (frameEl.name || frameEl.id || "");
5372 }
5373 } catch (e) {
5374 frameInfo = "_inaccessibleParentFrame";
5375 }
5376 var windowName = "PopUp_" + location.host.replace(/[^a-z0-9]/gi, "_") + "_" + consoleAppenderId + frameInfo;
5377 if (!useOldPopUp || !useDocumentWrite) {
5378 // Ensure a previous window isn't used by using a unique name
5379 windowName = windowName + "_" + uniqueId;
5380 }
5381
5382 var checkPopUpClosed = function(win) {
5383 if (consoleClosed) {
5384 return true;
5385 } else {
5386 try {
5387 return bool(win) && win.closed;
5388 } catch(ex) {}
5389 }
5390 return false;
5391 };
5392
5393 var popUpClosedCallback = function() {
5394 if (!consoleClosed) {
5395 appender.unload();
5396 }
5397 };
5398
5399 function finalInit() {
5400 getConsoleWindow().setCloseIfOpenerCloses(!useOldPopUp || !useDocumentWrite);
5401 consoleWindowLoadHandler();
5402 consoleWindowLoaded = true;
5403 appendQueuedLoggingEvents();
5404 pollConsoleWindow(checkPopUpClosed, 500, popUpClosedCallback,
5405 "PopUpAppender.checkPopUpClosed: error checking pop-up window");
5406 }
5407
5408 try {
5409 popUp = window.open(getConsoleUrl(), windowName, windowProperties);
5410 consoleClosed = false;
5411 consoleWindowCreated = true;
5412 if (popUp && popUp.document) {
5413 if (useDocumentWrite && useOldPopUp && isLoaded(popUp)) {
5414 popUp.mainPageReloaded();
5415 finalInit();
5416 } else {
5417 if (useDocumentWrite) {
5418 writeHtml(popUp.document);
5419 }
5420 // Check if the pop-up window object is available
5421 var popUpLoadedTest = function(win) { return bool(win) && isLoaded(win); };
5422 if (isLoaded(popUp)) {
5423 finalInit();
5424 } else {
5425 pollConsoleWindow(popUpLoadedTest, 100, finalInit,
5426 "PopUpAppender.init: unable to create console window");
5427 }
5428 }
5429 } else {
5430 isSupported = false;
5431 logLog.warn("PopUpAppender.init: pop-ups blocked, please unblock to use PopUpAppender");
5432 if (complainAboutPopUpBlocking) {
5433 handleError("log4javascript: pop-up windows appear to be blocked. Please unblock them to use pop-up logging.");
5434 }
5435 }
5436 } catch (ex) {
5437 handleError("PopUpAppender.init: error creating pop-up", ex);
5438 }
5439 };
5440
5441 createWindow = function() {
5442 if (!initiallyMinimized) {
5443 open();
5444 }
5445 };
5446
5447 init = function() {
5448 createWindow();
5449 initialized = true;
5450 };
5451
5452 getConsoleWindow = function() {
5453 return popUp;
5454 };
5455
5456 safeToAppend = function() {
5457 if (isSupported && !isUndefined(popUp) && !consoleClosed) {
5458 if (popUp.closed ||
5459 (consoleWindowLoaded && isUndefined(popUp.closed))) { // Extra check for Opera
5460 appender.unload();
5461 logLog.debug("PopUpAppender: pop-up closed");
5462 return false;
5463 }
5464 if (!consoleWindowLoaded && isLoaded(popUp)) {
5465 consoleWindowLoaded = true;
5466 }
5467 }
5468 return isSupported && consoleWindowLoaded && !consoleClosed;
5469 };
5470 }
5471
5472 // Expose getConsoleWindow so that automated tests can check the DOM
5473 this.getConsoleWindow = getConsoleWindow;
5474 };
5475
5476 ConsoleAppender.addGlobalCommandLineFunction = function(functionName, commandLineFunction) {
5477 defaultCommandLineFunctions.push([functionName, commandLineFunction]);
5478 };
5479
5480 /* ------------------------------------------------------------------ */
5481
5482 function PopUpAppender(lazyInit, initiallyMinimized, useDocumentWrite,
5483 width, height) {
5484 this.create(false, null, lazyInit, initiallyMinimized,
5485 useDocumentWrite, width, height, this.defaults.focusPopUp);
5486 }
5487
5488 PopUpAppender.prototype = new ConsoleAppender();
5489
5490 PopUpAppender.prototype.defaults = {
5491 layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),
5492 initiallyMinimized: false,
5493 focusPopUp: false,
5494 lazyInit: true,
5495 useOldPopUp: true,
5496 complainAboutPopUpBlocking: true,
5497 newestMessageAtTop: false,
5498 scrollToLatestMessage: true,
5499 width: "600",
5500 height: "400",
5501 reopenWhenClosed: false,
5502 maxMessages: null,
5503 showCommandLine: true,
5504 commandLineObjectExpansionDepth: 1,
5505 showHideButton: false,
5506 showCloseButton: true,
5507 showLogEntryDeleteButtons: true,
5508 useDocumentWrite: true
5509 };
5510
5511 PopUpAppender.prototype.toString = function() {
5512 return "PopUpAppender";
5513 };
5514
5515 log4javascript.PopUpAppender = PopUpAppender;
5516
5517 /* ------------------------------------------------------------------ */
5518
5519 function InPageAppender(container, lazyInit, initiallyMinimized,
5520 useDocumentWrite, width, height) {
5521 this.create(true, container, lazyInit, initiallyMinimized,
5522 useDocumentWrite, width, height, false);
5523 }
5524
5525 InPageAppender.prototype = new ConsoleAppender();
5526
5527 InPageAppender.prototype.defaults = {
5528 layout: new PatternLayout("%d{HH:mm:ss} %-5p - %m{1}%n"),
5529 initiallyMinimized: false,
5530 lazyInit: true,
5531 newestMessageAtTop: false,
5532 scrollToLatestMessage: true,
5533 width: "100%",
5534 height: "220px",
5535 maxMessages: null,
5536 showCommandLine: true,
5537 commandLineObjectExpansionDepth: 1,
5538 showHideButton: false,
5539 showCloseButton: false,
5540 showLogEntryDeleteButtons: true,
5541 useDocumentWrite: true
5542 };
5543
5544 InPageAppender.prototype.toString = function() {
5545 return "InPageAppender";
5546 };
5547
5548 log4javascript.InPageAppender = InPageAppender;
5549
5550 // Next line for backwards compatibility
5551 log4javascript.InlineAppender = InPageAppender;
5552 })();
5553 /* ---------------------------------------------------------------------- */
5554 // Console extension functions
5555
5556 function padWithSpaces(str, len) {
5557 if (str.length < len) {
5558 var spaces = [];
5559 var numberOfSpaces = Math.max(0, len - str.length);
5560 for (var i = 0; i < numberOfSpaces; i++) {
5561 spaces[i] = " ";
5562 }
5563 str += spaces.join("");
5564 }
5565 return str;
5566 }
5567
5568 (function() {
5569 function dir(obj) {
5570 var maxLen = 0;
5571 // Obtain the length of the longest property name
5572 for (var p in obj) {
5573 maxLen = Math.max(toStr(p).length, maxLen);
5574 }
5575 // Create the nicely formatted property list
5576 var propList = [];
5577 for (p in obj) {
5578 var propNameStr = " " + padWithSpaces(toStr(p), maxLen + 2);
5579 var propVal;
5580 try {
5581 propVal = splitIntoLines(toStr(obj[p])).join(padWithSpaces(newLine, maxLen + 6));
5582 } catch (ex) {
5583 propVal = "[Error obtaining property. Details: " + getExceptionMessage(ex) + "]";
5584 }
5585 propList.push(propNameStr + propVal);
5586 }
5587 return propList.join(newLine);
5588 }
5589
5590 var nodeTypes = {
5591 ELEMENT_NODE: 1,
5592 ATTRIBUTE_NODE: 2,
5593 TEXT_NODE: 3,
5594 CDATA_SECTION_NODE: 4,
5595 ENTITY_REFERENCE_NODE: 5,
5596 ENTITY_NODE: 6,
5597 PROCESSING_INSTRUCTION_NODE: 7,
5598 COMMENT_NODE: 8,
5599 DOCUMENT_NODE: 9,
5600 DOCUMENT_TYPE_NODE: 10,
5601 DOCUMENT_FRAGMENT_NODE: 11,
5602 NOTATION_NODE: 12
5603 };
5604
5605 var preFormattedElements = ["script", "pre"];
5606
5607 // This should be the definitive list, as specified by the XHTML 1.0 Transitional DTD
5608 var emptyElements = ["br", "img", "hr", "param", "link", "area", "input", "col", "base", "meta"];
5609 var indentationUnit = " ";
5610
5611 // Create and return an XHTML string from the node specified
5612 function getXhtml(rootNode, includeRootNode, indentation, startNewLine, preformatted) {
5613 includeRootNode = (typeof includeRootNode == "undefined") ? true : !!includeRootNode;
5614 if (typeof indentation != "string") {
5615 indentation = "";
5616 }
5617 startNewLine = !!startNewLine;
5618 preformatted = !!preformatted;
5619 var xhtml;
5620
5621 function isWhitespace(node) {
5622 return ((node.nodeType == nodeTypes.TEXT_NODE) && /^[ \t\r\n]*$/.test(node.nodeValue));
5623 }
5624
5625 function fixAttributeValue(attrValue) {
5626 return attrValue.toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
5627 }
5628
5629 function getStyleAttributeValue(el) {
5630 var stylePairs = el.style.cssText.split(";");
5631 var styleValue = "";
5632 var isFirst = true;
5633 for (var j = 0, len = stylePairs.length; j < len; j++) {
5634 var nameValueBits = stylePairs[j].split(":");
5635 var props = [];
5636 if (!/^\s*$/.test(nameValueBits[0])) {
5637 props.push(trim(nameValueBits[0]).toLowerCase() + ":" + trim(nameValueBits[1]));
5638 }
5639 styleValue = props.join(";");
5640 }
5641 return styleValue;
5642 }
5643
5644 function getNamespace(el) {
5645 if (el.prefix) {
5646 return el.prefix;
5647 } else if (el.outerHTML) {
5648 var regex = new RegExp("<([^:]+):" + el.tagName + "[^>]*>", "i");
5649 if (regex.test(el.outerHTML)) {
5650 return RegExp.$1.toLowerCase();
5651 }
5652 }
5653 return "";
5654 }
5655
5656 var lt = "<";
5657 var gt = ">";
5658
5659 if (includeRootNode && rootNode.nodeType != nodeTypes.DOCUMENT_FRAGMENT_NODE) {
5660 switch (rootNode.nodeType) {
5661 case nodeTypes.ELEMENT_NODE:
5662 var tagName = rootNode.tagName.toLowerCase();
5663 xhtml = startNewLine ? newLine + indentation : "";
5664 xhtml += lt;
5665 // Allow for namespaces, where present
5666 var prefix = getNamespace(rootNode);
5667 var hasPrefix = !!prefix;
5668 if (hasPrefix) {
5669 xhtml += prefix + ":";
5670 }
5671 xhtml += tagName;
5672 for (i = 0, len = rootNode.attributes.length; i < len; i++) {
5673 var currentAttr = rootNode.attributes[i];
5674 // Check the attribute is valid.
5675 if (! currentAttr.specified ||
5676 currentAttr.nodeValue === null ||
5677 currentAttr.nodeName.toLowerCase() === "style" ||
5678 typeof currentAttr.nodeValue !== "string" ||
5679 currentAttr.nodeName.indexOf("_moz") === 0) {
5680 continue;
5681 }
5682 xhtml += " " + currentAttr.nodeName.toLowerCase() + "=\"";
5683 xhtml += fixAttributeValue(currentAttr.nodeValue);
5684 xhtml += "\"";
5685 }
5686 // Style needs to be done separately as it is not reported as an
5687 // attribute in IE
5688 if (rootNode.style.cssText) {
5689 var styleValue = getStyleAttributeValue(rootNode);
5690 if (styleValue !== "") {
5691 xhtml += " style=\"" + getStyleAttributeValue(rootNode) + "\"";
5692 }
5693 }
5694 if (array_contains(emptyElements, tagName) ||
5695 (hasPrefix && !rootNode.hasChildNodes())) {
5696 xhtml += "/" + gt;
5697 } else {
5698 xhtml += gt;
5699 // Add output for childNodes collection (which doesn't include attribute nodes)
5700 var childStartNewLine = !(rootNode.childNodes.length === 1 &&
5701 rootNode.childNodes[0].nodeType === nodeTypes.TEXT_NODE);
5702 var childPreformatted = array_contains(preFormattedElements, tagName);
5703 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
5704 xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit,
5705 childStartNewLine, childPreformatted);
5706 }
5707 // Add the end tag
5708 var endTag = lt + "/" + tagName + gt;
5709 xhtml += childStartNewLine ? newLine + indentation + endTag : endTag;
5710 }
5711 return xhtml;
5712 case nodeTypes.TEXT_NODE:
5713 if (isWhitespace(rootNode)) {
5714 xhtml = "";
5715 } else {
5716 if (preformatted) {
5717 xhtml = rootNode.nodeValue;
5718 } else {
5719 // Trim whitespace from each line of the text node
5720 var lines = splitIntoLines(trim(rootNode.nodeValue));
5721 var trimmedLines = [];
5722 for (var i = 0, len = lines.length; i < len; i++) {
5723 trimmedLines[i] = trim(lines[i]);
5724 }
5725 xhtml = trimmedLines.join(newLine + indentation);
5726 }
5727 if (startNewLine) {
5728 xhtml = newLine + indentation + xhtml;
5729 }
5730 }
5731 return xhtml;
5732 case nodeTypes.CDATA_SECTION_NODE:
5733 return "<![CDA" + "TA[" + rootNode.nodeValue + "]" + "]>" + newLine;
5734 case nodeTypes.DOCUMENT_NODE:
5735 xhtml = "";
5736 // Add output for childNodes collection (which doesn't include attribute nodes)
5737 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
5738 xhtml += getXhtml(rootNode.childNodes[i], true, indentation);
5739 }
5740 return xhtml;
5741 default:
5742 return "";
5743 }
5744 } else {
5745 xhtml = "";
5746 // Add output for childNodes collection (which doesn't include attribute nodes)
5747 for (var i = 0, len = rootNode.childNodes.length; i < len; i++) {
5748 xhtml += getXhtml(rootNode.childNodes[i], true, indentation + indentationUnit);
5749 }
5750 return xhtml;
5751 }
5752 }
5753
5754 function createCommandLineFunctions() {
5755 ConsoleAppender.addGlobalCommandLineFunction("$", function(appender, args, returnValue) {
5756 return document.getElementById(args[0]);
5757 });
5758
5759 ConsoleAppender.addGlobalCommandLineFunction("dir", function(appender, args, returnValue) {
5760 var lines = [];
5761 for (var i = 0, len = args.length; i < len; i++) {
5762 lines[i] = dir(args[i]);
5763 }
5764 return lines.join(newLine + newLine);
5765 });
5766
5767 ConsoleAppender.addGlobalCommandLineFunction("dirxml", function(appender, args, returnValue) {
5768 var lines = [];
5769 for (var i = 0, len = args.length; i < len; i++) {
5770 var win = appender.getCommandWindow();
5771 lines[i] = getXhtml(args[i]);
5772 }
5773 return lines.join(newLine + newLine);
5774 });
5775
5776 ConsoleAppender.addGlobalCommandLineFunction("cd", function(appender, args, returnValue) {
5777 var win, message;
5778 if (args.length === 0 || args[0] === "") {
5779 win = window;
5780 message = "Command line set to run in main window";
5781 } else {
5782 if (args[0].window == args[0]) {
5783 win = args[0];
5784 message = "Command line set to run in frame '" + args[0].name + "'";
5785 } else {
5786 win = window.frames[args[0]];
5787 if (win) {
5788 message = "Command line set to run in frame '" + args[0] + "'";
5789 } else {
5790 returnValue.isError = true;
5791 message = "Frame '" + args[0] + "' does not exist";
5792 win = appender.getCommandWindow();
5793 }
5794 }
5795 }
5796 appender.setCommandWindow(win);
5797 return message;
5798 });
5799
5800 ConsoleAppender.addGlobalCommandLineFunction("clear", function(appender, args, returnValue) {
5801 returnValue.appendResult = false;
5802 appender.clear();
5803 });
5804
5805 ConsoleAppender.addGlobalCommandLineFunction("keys", function(appender, args, returnValue) {
5806 var keys = [];
5807 for (var k in args[0]) {
5808 keys.push(k);
5809 }
5810 return keys;
5811 });
5812
5813 ConsoleAppender.addGlobalCommandLineFunction("values", function(appender, args, returnValue) {
5814 var values = [];
5815 for (var k in args[0]) {
5816 try {
5817 values.push(args[0][k]);
5818 } catch (ex) {
5819 logLog.warn("values(): Unable to obtain value for key " + k + ". Details: " + getExceptionMessage(ex));
5820 }
5821 }
5822 return values;
5823 });
5824
5825 ConsoleAppender.addGlobalCommandLineFunction("expansionDepth", function(appender, args, returnValue) {
5826 var expansionDepth = parseInt(args[0], 10);
5827 if (isNaN(expansionDepth) || expansionDepth < 0) {
5828 returnValue.isError = true;
5829 return "" + args[0] + " is not a valid expansion depth";
5830 } else {
5831 appender.setCommandLineObjectExpansionDepth(expansionDepth);
5832 return "Object expansion depth set to " + expansionDepth;
5833 }
5834 });
5835 }
5836
5837 function init() {
5838 // Add command line functions
5839 createCommandLineFunctions();
5840 }
5841
5842 /* ------------------------------------------------------------------ */
5843
5844 init();
5845 })();
5846
5847 /* ---------------------------------------------------------------------- */
5848 // Main load
5849
5850 log4javascript.setDocumentReady = function() {
5851 pageLoaded = true;
5852 log4javascript.dispatchEvent("load", {});
5853 };
5854
5855 if (window.addEventListener) {
5856 window.addEventListener("load", log4javascript.setDocumentReady, false);
5857 } else if (window.attachEvent) {
5858 window.attachEvent("onload", log4javascript.setDocumentReady);
5859 } else {
5860 var oldOnload = window.onload;
5861 if (typeof window.onload != "function") {
5862 window.onload = log4javascript.setDocumentReady;
5863 } else {
5864 window.onload = function(evt) {
5865 if (oldOnload) {
5866 oldOnload(evt);
5867 }
5868 log4javascript.setDocumentReady();
5869 };
5870 }
5871 }
5872
5873 // Ensure that the log4javascript object is available in the window. This
5874 // is necessary for log4javascript to be available in IE if loaded using
5875 // Dojo's module system
5876 window.log4javascript = log4javascript;
5877
5878 return log4javascript;
5879})();