blob: 1a29621db3c2341165958eaeaf7958ef71c95d15 [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_production
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_production";
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 // AjaxAppender related
1875
1876 var xmlHttpFactories = [
1877 function() { return new XMLHttpRequest(); },
1878 function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
1879 function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
1880 ];
1881
1882 var getXmlHttp = function(errorHandler) {
1883 // This is only run the first time; the value of getXmlHttp gets
1884 // replaced with the factory that succeeds on the first run
1885 var xmlHttp = null, factory;
1886 for (var i = 0, len = xmlHttpFactories.length; i < len; i++) {
1887 factory = xmlHttpFactories[i];
1888 try {
1889 xmlHttp = factory();
1890 getXmlHttp = factory;
1891 return xmlHttp;
1892 } catch (e) {
1893 }
1894 }
1895 // If we're here, all factories have failed, so throw an error
1896 if (errorHandler) {
1897 errorHandler();
1898 } else {
1899 handleError("getXmlHttp: unable to obtain XMLHttpRequest object");
1900 }
1901 };
1902
1903 function isHttpRequestSuccessful(xmlHttp) {
1904 return isUndefined(xmlHttp.status) || xmlHttp.status === 0 ||
1905 (xmlHttp.status >= 200 && xmlHttp.status < 300) ||
1906 xmlHttp.status == 1223 /* Fix for IE */;
1907 }
1908
1909 /* ---------------------------------------------------------------------- */
1910 // AjaxAppender
1911
1912 function AjaxAppender(url) {
1913 var appender = this;
1914 var isSupported = true;
1915 if (!url) {
1916 handleError("AjaxAppender: URL must be specified in constructor");
1917 isSupported = false;
1918 }
1919
1920 var timed = this.defaults.timed;
1921 var waitForResponse = this.defaults.waitForResponse;
1922 var batchSize = this.defaults.batchSize;
1923 var timerInterval = this.defaults.timerInterval;
1924 var requestSuccessCallback = this.defaults.requestSuccessCallback;
1925 var failCallback = this.defaults.failCallback;
1926 var postVarName = this.defaults.postVarName;
1927 var sendAllOnUnload = this.defaults.sendAllOnUnload;
1928 var contentType = this.defaults.contentType;
1929 var sessionId = null;
1930
1931 var queuedLoggingEvents = [];
1932 var queuedRequests = [];
1933 var headers = [];
1934 var sending = false;
1935 var initialized = false;
1936
1937 // Configuration methods. The function scope is used to prevent
1938 // direct alteration to the appender configuration properties.
1939 function checkCanConfigure(configOptionName) {
1940 if (initialized) {
1941 handleError("AjaxAppender: configuration option '" +
1942 configOptionName +
1943 "' may not be set after the appender has been initialized");
1944 return false;
1945 }
1946 return true;
1947 }
1948
1949 this.getSessionId = function() { return sessionId; };
1950 this.setSessionId = function(sessionIdParam) {
1951 sessionId = extractStringFromParam(sessionIdParam, null);
1952 this.layout.setCustomField("sessionid", sessionId);
1953 };
1954
1955 this.setLayout = function(layoutParam) {
1956 if (checkCanConfigure("layout")) {
1957 this.layout = layoutParam;
1958 // Set the session id as a custom field on the layout, if not already present
1959 if (sessionId !== null) {
1960 this.setSessionId(sessionId);
1961 }
1962 }
1963 };
1964
1965 this.isTimed = function() { return timed; };
1966 this.setTimed = function(timedParam) {
1967 if (checkCanConfigure("timed")) {
1968 timed = bool(timedParam);
1969 }
1970 };
1971
1972 this.getTimerInterval = function() { return timerInterval; };
1973 this.setTimerInterval = function(timerIntervalParam) {
1974 if (checkCanConfigure("timerInterval")) {
1975 timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
1976 }
1977 };
1978
1979 this.isWaitForResponse = function() { return waitForResponse; };
1980 this.setWaitForResponse = function(waitForResponseParam) {
1981 if (checkCanConfigure("waitForResponse")) {
1982 waitForResponse = bool(waitForResponseParam);
1983 }
1984 };
1985
1986 this.getBatchSize = function() { return batchSize; };
1987 this.setBatchSize = function(batchSizeParam) {
1988 if (checkCanConfigure("batchSize")) {
1989 batchSize = extractIntFromParam(batchSizeParam, batchSize);
1990 }
1991 };
1992
1993 this.isSendAllOnUnload = function() { return sendAllOnUnload; };
1994 this.setSendAllOnUnload = function(sendAllOnUnloadParam) {
1995 if (checkCanConfigure("sendAllOnUnload")) {
1996 sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload);
1997 }
1998 };
1999
2000 this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
2001 requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
2002 };
2003
2004 this.setFailCallback = function(failCallbackParam) {
2005 failCallback = extractFunctionFromParam(failCallbackParam, failCallback);
2006 };
2007
2008 this.getPostVarName = function() { return postVarName; };
2009 this.setPostVarName = function(postVarNameParam) {
2010 if (checkCanConfigure("postVarName")) {
2011 postVarName = extractStringFromParam(postVarNameParam, postVarName);
2012 }
2013 };
2014
2015 this.getHeaders = function() { return headers; };
2016 this.addHeader = function(name, value) {
2017 if (name.toLowerCase() == "content-type") {
2018 contentType = value;
2019 } else {
2020 headers.push( { name: name, value: value } );
2021 }
2022 };
2023
2024 // Internal functions
2025 function sendAll() {
2026 if (isSupported && enabled) {
2027 sending = true;
2028 var currentRequestBatch;
2029 if (waitForResponse) {
2030 // Send the first request then use this function as the callback once
2031 // the response comes back
2032 if (queuedRequests.length > 0) {
2033 currentRequestBatch = queuedRequests.shift();
2034 sendRequest(preparePostData(currentRequestBatch), sendAll);
2035 } else {
2036 sending = false;
2037 if (timed) {
2038 scheduleSending();
2039 }
2040 }
2041 } else {
2042 // Rattle off all the requests without waiting to see the response
2043 while ((currentRequestBatch = queuedRequests.shift())) {
2044 sendRequest(preparePostData(currentRequestBatch));
2045 }
2046 sending = false;
2047 if (timed) {
2048 scheduleSending();
2049 }
2050 }
2051 }
2052 }
2053
2054 this.sendAll = sendAll;
2055
2056 // Called when the window unloads. At this point we're past caring about
2057 // waiting for responses or timers or incomplete batches - everything
2058 // must go, now
2059 function sendAllRemaining() {
2060 var sendingAnything = false;
2061 if (isSupported && enabled) {
2062 // Create requests for everything left over, batched as normal
2063 var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1;
2064 var currentLoggingEvent;
2065 var batchedLoggingEvents = [];
2066 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
2067 batchedLoggingEvents.push(currentLoggingEvent);
2068 if (queuedLoggingEvents.length >= actualBatchSize) {
2069 // Queue this batch of log entries
2070 queuedRequests.push(batchedLoggingEvents);
2071 batchedLoggingEvents = [];
2072 }
2073 }
2074 // If there's a partially completed batch, add it
2075 if (batchedLoggingEvents.length > 0) {
2076 queuedRequests.push(batchedLoggingEvents);
2077 }
2078 sendingAnything = (queuedRequests.length > 0);
2079 waitForResponse = false;
2080 timed = false;
2081 sendAll();
2082 }
2083 return sendingAnything;
2084 }
2085
2086 this.sendAllRemaining = sendAllRemaining;
2087
2088 function preparePostData(batchedLoggingEvents) {
2089 // Format the logging events
2090 var formattedMessages = [];
2091 var currentLoggingEvent;
2092 var postData = "";
2093 while ((currentLoggingEvent = batchedLoggingEvents.shift())) {
2094 var currentFormattedMessage = appender.getLayout().format(currentLoggingEvent);
2095 if (appender.getLayout().ignoresThrowable()) {
2096 currentFormattedMessage += currentLoggingEvent.getThrowableStrRep();
2097 }
2098 formattedMessages.push(currentFormattedMessage);
2099 }
2100 // Create the post data string
2101 if (batchedLoggingEvents.length == 1) {
2102 postData = formattedMessages.join("");
2103 } else {
2104 postData = appender.getLayout().batchHeader +
2105 formattedMessages.join(appender.getLayout().batchSeparator) +
2106 appender.getLayout().batchFooter;
2107 }
2108 if (contentType == appender.defaults.contentType) {
2109 postData = appender.getLayout().returnsPostData ? postData :
2110 urlEncode(postVarName) + "=" + urlEncode(postData);
2111 // Add the layout name to the post data
2112 if (postData.length > 0) {
2113 postData += "&";
2114 }
2115 postData += "layout=" + urlEncode(appender.getLayout().toString());
2116 }
2117 return postData;
2118 }
2119
2120 function scheduleSending() {
2121 window.setTimeout(sendAll, timerInterval);
2122 }
2123
2124 function xmlHttpErrorHandler() {
2125 var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";
2126 handleError(msg);
2127 isSupported = false;
2128 if (failCallback) {
2129 failCallback(msg);
2130 }
2131 }
2132
2133 function sendRequest(postData, successCallback) {
2134 try {
2135 var xmlHttp = getXmlHttp(xmlHttpErrorHandler);
2136 if (isSupported) {
2137 if (xmlHttp.overrideMimeType) {
2138 xmlHttp.overrideMimeType(appender.getLayout().getContentType());
2139 }
2140 xmlHttp.onreadystatechange = function() {
2141 if (xmlHttp.readyState == 4) {
2142 if (isHttpRequestSuccessful(xmlHttp)) {
2143 if (requestSuccessCallback) {
2144 requestSuccessCallback(xmlHttp);
2145 }
2146 if (successCallback) {
2147 successCallback(xmlHttp);
2148 }
2149 } else {
2150 var msg = "AjaxAppender.append: XMLHttpRequest request to URL " +
2151 url + " returned status code " + xmlHttp.status;
2152 handleError(msg);
2153 if (failCallback) {
2154 failCallback(msg);
2155 }
2156 }
2157 xmlHttp.onreadystatechange = emptyFunction;
2158 xmlHttp = null;
2159 }
2160 };
2161 xmlHttp.open("POST", url, true);
2162 try {
2163 for (var i = 0, header; header = headers[i++]; ) {
2164 xmlHttp.setRequestHeader(header.name, header.value);
2165 }
2166 xmlHttp.setRequestHeader("Content-Type", contentType);
2167 } catch (headerEx) {
2168 var msg = "AjaxAppender.append: your browser's XMLHttpRequest implementation" +
2169 " does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled";
2170 handleError(msg);
2171 isSupported = false;
2172 if (failCallback) {
2173 failCallback(msg);
2174 }
2175 return;
2176 }
2177 xmlHttp.send(postData);
2178 }
2179 } catch (ex) {
2180 var errMsg = "AjaxAppender.append: error sending log message to " + url;
2181 handleError(errMsg, ex);
2182 isSupported = false;
2183 if (failCallback) {
2184 failCallback(errMsg + ". Details: " + getExceptionStringRep(ex));
2185 }
2186 }
2187 }
2188
2189 this.append = function(loggingEvent) {
2190 if (isSupported) {
2191 if (!initialized) {
2192 init();
2193 }
2194 queuedLoggingEvents.push(loggingEvent);
2195 var actualBatchSize = this.getLayout().allowBatching() ? batchSize : 1;
2196
2197 if (queuedLoggingEvents.length >= actualBatchSize) {
2198 var currentLoggingEvent;
2199 var batchedLoggingEvents = [];
2200 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
2201 batchedLoggingEvents.push(currentLoggingEvent);
2202 }
2203 // Queue this batch of log entries
2204 queuedRequests.push(batchedLoggingEvents);
2205
2206 // If using a timer, the queue of requests will be processed by the
2207 // timer function, so nothing needs to be done here.
2208 if (!timed && (!waitForResponse || (waitForResponse && !sending))) {
2209 sendAll();
2210 }
2211 }
2212 }
2213 };
2214
2215 function init() {
2216 initialized = true;
2217 // Add unload event to send outstanding messages
2218 if (sendAllOnUnload) {
2219 var oldBeforeUnload = window.onbeforeunload;
2220 window.onbeforeunload = function() {
2221 if (oldBeforeUnload) {
2222 oldBeforeUnload();
2223 }
2224 if (sendAllRemaining()) {
2225 return "Sending log messages";
2226 }
2227 };
2228 }
2229 // Start timer
2230 if (timed) {
2231 scheduleSending();
2232 }
2233 }
2234 }
2235
2236 AjaxAppender.prototype = new Appender();
2237
2238 AjaxAppender.prototype.defaults = {
2239 waitForResponse: false,
2240 timed: false,
2241 timerInterval: 1000,
2242 batchSize: 1,
2243 sendAllOnUnload: false,
2244 requestSuccessCallback: null,
2245 failCallback: null,
2246 postVarName: "data",
2247 contentType: "application/x-www-form-urlencoded"
2248 };
2249
2250 AjaxAppender.prototype.layout = new HttpPostDataLayout();
2251
2252 AjaxAppender.prototype.toString = function() {
2253 return "AjaxAppender";
2254 };
2255
2256 log4javascript.AjaxAppender = AjaxAppender;
2257
2258 /* ---------------------------------------------------------------------- */
2259 // Main load
2260
2261 log4javascript.setDocumentReady = function() {
2262 pageLoaded = true;
2263 log4javascript.dispatchEvent("load", {});
2264 };
2265
2266 if (window.addEventListener) {
2267 window.addEventListener("load", log4javascript.setDocumentReady, false);
2268 } else if (window.attachEvent) {
2269 window.attachEvent("onload", log4javascript.setDocumentReady);
2270 } else {
2271 var oldOnload = window.onload;
2272 if (typeof window.onload != "function") {
2273 window.onload = log4javascript.setDocumentReady;
2274 } else {
2275 window.onload = function(evt) {
2276 if (oldOnload) {
2277 oldOnload(evt);
2278 }
2279 log4javascript.setDocumentReady();
2280 };
2281 }
2282 }
2283
2284 // Ensure that the log4javascript object is available in the window. This
2285 // is necessary for log4javascript to be available in IE if loaded using
2286 // Dojo's module system
2287 window.log4javascript = log4javascript;
2288
2289 return log4javascript;
2290})();