2 * Copyright 2013 Tim Down.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
20 * log4javascript is a logging framework for JavaScript based on log4j
\r
21 * for Java. This file contains all core log4javascript code and is the only
\r
22 * file required to use log4javascript, unless you require support for
\r
23 * document.domain, in which case you will also need console.html, which must be
\r
24 * stored in the same directory as the main log4javascript.js file.
\r
26 * Author: Tim Down <tim@log4javascript.org>
\r
28 * Edition: log4javascript_production
\r
29 * Build date: 19 March 2013
\r
30 * Website: http://log4javascript.org
\r
33 /* -------------------------------------------------------------------------- */
\r
34 // Array-related stuff
\r
36 // Next three methods are solely for IE5, which is missing them
\r
37 if (!Array.prototype.push) {
\r
38 Array.prototype.push = function() {
\r
39 for (var i = 0, len = arguments.length; i < len; i++){
\r
40 this[this.length] = arguments[i];
\r
46 if (!Array.prototype.shift) {
\r
47 Array.prototype.shift = function() {
\r
48 if (this.length > 0) {
\r
49 var firstItem = this[0];
\r
50 for (var i = 0, len = this.length - 1; i < len; i++) {
\r
51 this[i] = this[i + 1];
\r
53 this.length = this.length - 1;
\r
59 if (!Array.prototype.splice) {
\r
60 Array.prototype.splice = function(startIndex, deleteCount) {
\r
61 var itemsAfterDeleted = this.slice(startIndex + deleteCount);
\r
62 var itemsDeleted = this.slice(startIndex, startIndex + deleteCount);
\r
63 this.length = startIndex;
\r
64 // Copy the arguments into a proper Array object
\r
65 var argumentsArray = [];
\r
66 for (var i = 0, len = arguments.length; i < len; i++) {
\r
67 argumentsArray[i] = arguments[i];
\r
69 var itemsToAppend = (argumentsArray.length > 2) ?
\r
70 itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted;
\r
71 for (i = 0, len = itemsToAppend.length; i < len; i++) {
\r
72 this.push(itemsToAppend[i]);
\r
74 return itemsDeleted;
\r
78 /* -------------------------------------------------------------------------- */
\r
80 var log4javascript = (function() {
\r
82 function isUndefined(obj) {
\r
83 return typeof obj == "undefined";
\r
86 /* ---------------------------------------------------------------------- */
\r
87 // Custom event support
\r
89 function EventSupport() {}
\r
91 EventSupport.prototype = {
\r
94 setEventTypes: function(eventTypesParam) {
\r
95 if (eventTypesParam instanceof Array) {
\r
96 this.eventTypes = eventTypesParam;
\r
97 this.eventListeners = {};
\r
98 for (var i = 0, len = this.eventTypes.length; i < len; i++) {
\r
99 this.eventListeners[this.eventTypes[i]] = [];
\r
102 handleError("log4javascript.EventSupport [" + this + "]: setEventTypes: eventTypes parameter must be an Array");
\r
106 addEventListener: function(eventType, listener) {
\r
107 if (typeof listener == "function") {
\r
108 if (!array_contains(this.eventTypes, eventType)) {
\r
109 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: no event called '" + eventType + "'");
\r
111 this.eventListeners[eventType].push(listener);
\r
113 handleError("log4javascript.EventSupport [" + this + "]: addEventListener: listener must be a function");
\r
117 removeEventListener: function(eventType, listener) {
\r
118 if (typeof listener == "function") {
\r
119 if (!array_contains(this.eventTypes, eventType)) {
\r
120 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: no event called '" + eventType + "'");
\r
122 array_remove(this.eventListeners[eventType], listener);
\r
124 handleError("log4javascript.EventSupport [" + this + "]: removeEventListener: listener must be a function");
\r
128 dispatchEvent: function(eventType, eventArgs) {
\r
129 if (array_contains(this.eventTypes, eventType)) {
\r
130 var listeners = this.eventListeners[eventType];
\r
131 for (var i = 0, len = listeners.length; i < len; i++) {
\r
132 listeners[i](this, eventType, eventArgs);
\r
135 handleError("log4javascript.EventSupport [" + this + "]: dispatchEvent: no event called '" + eventType + "'");
\r
140 /* -------------------------------------------------------------------------- */
\r
142 var applicationStartDate = new Date();
\r
143 var uniqueId = "log4javascript_" + applicationStartDate.getTime() + "_" +
\r
144 Math.floor(Math.random() * 100000000);
\r
145 var emptyFunction = function() {};
\r
146 var newLine = "\r\n";
\r
147 var pageLoaded = false;
\r
149 // Create main log4javascript object; this will be assigned public properties
\r
150 function Log4JavaScript() {}
\r
151 Log4JavaScript.prototype = new EventSupport();
\r
153 log4javascript = new Log4JavaScript();
\r
154 log4javascript.version = "1.4.6";
\r
155 log4javascript.edition = "log4javascript_production";
\r
157 /* -------------------------------------------------------------------------- */
\r
158 // Utility functions
\r
160 function toStr(obj) {
\r
161 if (obj && obj.toString) {
\r
162 return obj.toString();
\r
164 return String(obj);
\r
168 function getExceptionMessage(ex) {
\r
171 } else if (ex.description) {
\r
172 return ex.description;
\r
178 // Gets the portion of the URL after the last slash
\r
179 function getUrlFileName(url) {
\r
180 var lastSlashIndex = Math.max(url.lastIndexOf("/"), url.lastIndexOf("\\"));
\r
181 return url.substr(lastSlashIndex + 1);
\r
184 // Returns a nicely formatted representation of an error
\r
185 function getExceptionStringRep(ex) {
\r
187 var exStr = "Exception: " + getExceptionMessage(ex);
\r
189 if (ex.lineNumber) {
\r
190 exStr += " on line number " + ex.lineNumber;
\r
193 exStr += " in file " + getUrlFileName(ex.fileName);
\r
195 } catch (localEx) {
\r
196 logLog.warn("Unable to obtain file and line information for error");
\r
198 if (showStackTraces && ex.stack) {
\r
199 exStr += newLine + "Stack trace:" + newLine + ex.stack;
\r
206 function bool(obj) {
\r
207 return Boolean(obj);
\r
210 function trim(str) {
\r
211 return str.replace(/^\s+/, "").replace(/\s+$/, "");
\r
214 function splitIntoLines(text) {
\r
215 // Ensure all line breaks are \n only
\r
216 var text2 = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
\r
217 return text2.split("\n");
\r
220 var urlEncode = (typeof window.encodeURIComponent != "undefined") ?
\r
222 return encodeURIComponent(str);
\r
225 return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F").replace(/=/g, "%3D");
\r
228 var urlDecode = (typeof window.decodeURIComponent != "undefined") ?
\r
230 return decodeURIComponent(str);
\r
233 return unescape(str).replace(/%2B/g, "+").replace(/%22/g, "\"").replace(/%27/g, "'").replace(/%2F/g, "/").replace(/%3D/g, "=");
\r
236 function array_remove(arr, val) {
\r
238 for (var i = 0, len = arr.length; i < len; i++) {
\r
239 if (arr[i] === val) {
\r
245 arr.splice(index, 1);
\r
252 function array_contains(arr, val) {
\r
253 for(var i = 0, len = arr.length; i < len; i++) {
\r
254 if (arr[i] == val) {
\r
261 function extractBooleanFromParam(param, defaultValue) {
\r
262 if (isUndefined(param)) {
\r
263 return defaultValue;
\r
265 return bool(param);
\r
269 function extractStringFromParam(param, defaultValue) {
\r
270 if (isUndefined(param)) {
\r
271 return defaultValue;
\r
273 return String(param);
\r
277 function extractIntFromParam(param, defaultValue) {
\r
278 if (isUndefined(param)) {
\r
279 return defaultValue;
\r
282 var value = parseInt(param, 10);
\r
283 return isNaN(value) ? defaultValue : value;
\r
285 logLog.warn("Invalid int param " + param, ex);
\r
286 return defaultValue;
\r
291 function extractFunctionFromParam(param, defaultValue) {
\r
292 if (typeof param == "function") {
\r
295 return defaultValue;
\r
299 function isError(err) {
\r
300 return (err instanceof Error);
\r
303 if (!Function.prototype.apply){
\r
304 Function.prototype.apply = function(obj, args) {
\r
305 var methodName = "__apply__";
\r
306 if (typeof obj[methodName] != "undefined") {
\r
307 methodName += String(Math.random()).substr(2);
\r
309 obj[methodName] = this;
\r
311 var argsStrings = [];
\r
312 for (var i = 0, len = args.length; i < len; i++) {
\r
313 argsStrings[i] = "args[" + i + "]";
\r
315 var script = "obj." + methodName + "(" + argsStrings.join(",") + ")";
\r
316 var returnValue = eval(script);
\r
317 delete obj[methodName];
\r
318 return returnValue;
\r
322 if (!Function.prototype.call){
\r
323 Function.prototype.call = function(obj) {
\r
325 for (var i = 1, len = arguments.length; i < len; i++) {
\r
326 args[i - 1] = arguments[i];
\r
328 return this.apply(obj, args);
\r
332 function getListenersPropertyName(eventName) {
\r
333 return "__log4javascript_listeners__" + eventName;
\r
336 function addEvent(node, eventName, listener, useCapture, win) {
\r
337 win = win ? win : window;
\r
338 if (node.addEventListener) {
\r
339 node.addEventListener(eventName, listener, useCapture);
\r
340 } else if (node.attachEvent) {
\r
341 node.attachEvent("on" + eventName, listener);
\r
343 var propertyName = getListenersPropertyName(eventName);
\r
344 if (!node[propertyName]) {
\r
345 node[propertyName] = [];
\r
346 // Set event handler
\r
347 node["on" + eventName] = function(evt) {
\r
348 evt = getEvent(evt, win);
\r
349 var listenersPropertyName = getListenersPropertyName(eventName);
\r
351 // Clone the array of listeners to leave the original untouched
\r
352 var listeners = this[listenersPropertyName].concat([]);
\r
353 var currentListener;
\r
355 // Call each listener in turn
\r
356 while ((currentListener = listeners.shift())) {
\r
357 currentListener.call(this, evt);
\r
361 node[propertyName].push(listener);
\r
365 function removeEvent(node, eventName, listener, useCapture) {
\r
366 if (node.removeEventListener) {
\r
367 node.removeEventListener(eventName, listener, useCapture);
\r
368 } else if (node.detachEvent) {
\r
369 node.detachEvent("on" + eventName, listener);
\r
371 var propertyName = getListenersPropertyName(eventName);
\r
372 if (node[propertyName]) {
\r
373 array_remove(node[propertyName], listener);
\r
378 function getEvent(evt, win) {
\r
379 win = win ? win : window;
\r
380 return evt ? evt : win.event;
\r
383 function stopEventPropagation(evt) {
\r
384 if (evt.stopPropagation) {
\r
385 evt.stopPropagation();
\r
386 } else if (typeof evt.cancelBubble != "undefined") {
\r
387 evt.cancelBubble = true;
\r
389 evt.returnValue = false;
\r
392 /* ---------------------------------------------------------------------- */
\r
393 // Simple logging for log4javascript itself
\r
400 setQuietMode: function(quietMode) {
\r
401 this.quietMode = bool(quietMode);
\r
406 alertAllErrors: false,
\r
408 setAlertAllErrors: function(alertAllErrors) {
\r
409 this.alertAllErrors = alertAllErrors;
\r
412 debug: function(message) {
\r
413 this.debugMessages.push(message);
\r
416 displayDebug: function() {
\r
417 alert(this.debugMessages.join(newLine));
\r
420 warn: function(message, exception) {
\r
423 error: function(message, exception) {
\r
424 if (++this.numberOfErrors == 1 || this.alertAllErrors) {
\r
425 if (!this.quietMode) {
\r
426 var alertMessage = "log4javascript error: " + message;
\r
428 alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception);
\r
430 alert(alertMessage);
\r
435 log4javascript.logLog = logLog;
\r
437 log4javascript.setEventTypes(["load", "error"]);
\r
439 function handleError(message, exception) {
\r
440 logLog.error(message, exception);
\r
441 log4javascript.dispatchEvent("error", { "message": message, "exception": exception });
\r
444 log4javascript.handleError = handleError;
\r
446 /* ---------------------------------------------------------------------- */
\r
448 var enabled = !((typeof log4javascript_disabled != "undefined") &&
\r
449 log4javascript_disabled);
\r
451 log4javascript.setEnabled = function(enable) {
\r
452 enabled = bool(enable);
\r
455 log4javascript.isEnabled = function() {
\r
459 var useTimeStampsInMilliseconds = true;
\r
461 log4javascript.setTimeStampsInMilliseconds = function(timeStampsInMilliseconds) {
\r
462 useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
\r
465 log4javascript.isTimeStampsInMilliseconds = function() {
\r
466 return useTimeStampsInMilliseconds;
\r
470 // This evaluates the given expression in the current scope, thus allowing
\r
471 // scripts to access private variables. Particularly useful for testing
\r
472 log4javascript.evalInScope = function(expr) {
\r
476 var showStackTraces = false;
\r
478 log4javascript.setShowStackTraces = function(show) {
\r
479 showStackTraces = bool(show);
\r
482 /* ---------------------------------------------------------------------- */
\r
485 var Level = function(level, name) {
\r
486 this.level = level;
\r
490 Level.prototype = {
\r
491 toString: function() {
\r
494 equals: function(level) {
\r
495 return this.level == level.level;
\r
497 isGreaterOrEqual: function(level) {
\r
498 return this.level >= level.level;
\r
502 Level.ALL = new Level(Number.MIN_VALUE, "ALL");
\r
503 Level.TRACE = new Level(10000, "TRACE");
\r
504 Level.DEBUG = new Level(20000, "DEBUG");
\r
505 Level.INFO = new Level(30000, "INFO");
\r
506 Level.WARN = new Level(40000, "WARN");
\r
507 Level.ERROR = new Level(50000, "ERROR");
\r
508 Level.FATAL = new Level(60000, "FATAL");
\r
509 Level.OFF = new Level(Number.MAX_VALUE, "OFF");
\r
511 log4javascript.Level = Level;
\r
513 /* ---------------------------------------------------------------------- */
\r
516 function Timer(name, level) {
\r
518 this.level = isUndefined(level) ? Level.INFO : level;
\r
519 this.start = new Date();
\r
522 Timer.prototype.getElapsedTime = function() {
\r
523 return new Date().getTime() - this.start.getTime();
\r
526 /* ---------------------------------------------------------------------- */
\r
529 var anonymousLoggerName = "[anonymous]";
\r
530 var defaultLoggerName = "[default]";
\r
531 var nullLoggerName = "[null]";
\r
532 var rootLoggerName = "root";
\r
534 function Logger(name) {
\r
536 this.parent = null;
\r
537 this.children = [];
\r
539 var appenders = [];
\r
540 var loggerLevel = null;
\r
541 var isRoot = (this.name === rootLoggerName);
\r
542 var isNull = (this.name === nullLoggerName);
\r
544 var appenderCache = null;
\r
545 var appenderCacheInvalidated = false;
\r
547 this.addChild = function(childLogger) {
\r
548 this.children.push(childLogger);
\r
549 childLogger.parent = this;
\r
550 childLogger.invalidateAppenderCache();
\r
554 var additive = true;
\r
555 this.getAdditivity = function() {
\r
559 this.setAdditivity = function(additivity) {
\r
560 var valueChanged = (additive != additivity);
\r
561 additive = additivity;
\r
562 if (valueChanged) {
\r
563 this.invalidateAppenderCache();
\r
567 // Create methods that use the appenders variable in this scope
\r
568 this.addAppender = function(appender) {
\r
570 handleError("Logger.addAppender: you may not add an appender to the null logger");
\r
572 if (appender instanceof log4javascript.Appender) {
\r
573 if (!array_contains(appenders, appender)) {
\r
574 appenders.push(appender);
\r
575 appender.setAddedToLogger(this);
\r
576 this.invalidateAppenderCache();
\r
579 handleError("Logger.addAppender: appender supplied ('" +
\r
580 toStr(appender) + "') is not a subclass of Appender");
\r
585 this.removeAppender = function(appender) {
\r
586 array_remove(appenders, appender);
\r
587 appender.setRemovedFromLogger(this);
\r
588 this.invalidateAppenderCache();
\r
591 this.removeAllAppenders = function() {
\r
592 var appenderCount = appenders.length;
\r
593 if (appenderCount > 0) {
\r
594 for (var i = 0; i < appenderCount; i++) {
\r
595 appenders[i].setRemovedFromLogger(this);
\r
597 appenders.length = 0;
\r
598 this.invalidateAppenderCache();
\r
602 this.getEffectiveAppenders = function() {
\r
603 if (appenderCache === null || appenderCacheInvalidated) {
\r
604 // Build appender cache
\r
605 var parentEffectiveAppenders = (isRoot || !this.getAdditivity()) ?
\r
606 [] : this.parent.getEffectiveAppenders();
\r
607 appenderCache = parentEffectiveAppenders.concat(appenders);
\r
608 appenderCacheInvalidated = false;
\r
610 return appenderCache;
\r
613 this.invalidateAppenderCache = function() {
\r
614 appenderCacheInvalidated = true;
\r
615 for (var i = 0, len = this.children.length; i < len; i++) {
\r
616 this.children[i].invalidateAppenderCache();
\r
620 this.log = function(level, params) {
\r
621 if (enabled && level.isGreaterOrEqual(this.getEffectiveLevel())) {
\r
622 // Check whether last param is an exception
\r
624 var finalParamIndex = params.length - 1;
\r
625 var lastParam = params[finalParamIndex];
\r
626 if (params.length > 1 && isError(lastParam)) {
\r
627 exception = lastParam;
\r
631 // Construct genuine array for the params
\r
633 for (var i = 0; i <= finalParamIndex; i++) {
\r
634 messages[i] = params[i];
\r
637 var loggingEvent = new LoggingEvent(
\r
638 this, new Date(), level, messages, exception);
\r
640 this.callAppenders(loggingEvent);
\r
644 this.callAppenders = function(loggingEvent) {
\r
645 var effectiveAppenders = this.getEffectiveAppenders();
\r
646 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
647 effectiveAppenders[i].doAppend(loggingEvent);
\r
651 this.setLevel = function(level) {
\r
652 // Having a level of null on the root logger would be very bad.
\r
653 if (isRoot && level === null) {
\r
654 handleError("Logger.setLevel: you cannot set the level of the root logger to null");
\r
655 } else if (level instanceof Level) {
\r
656 loggerLevel = level;
\r
658 handleError("Logger.setLevel: level supplied to logger " +
\r
659 this.name + " is not an instance of log4javascript.Level");
\r
663 this.getLevel = function() {
\r
664 return loggerLevel;
\r
667 this.getEffectiveLevel = function() {
\r
668 for (var logger = this; logger !== null; logger = logger.parent) {
\r
669 var level = logger.getLevel();
\r
670 if (level !== null) {
\r
676 this.group = function(name, initiallyExpanded) {
\r
678 var effectiveAppenders = this.getEffectiveAppenders();
\r
679 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
680 effectiveAppenders[i].group(name, initiallyExpanded);
\r
685 this.groupEnd = function() {
\r
687 var effectiveAppenders = this.getEffectiveAppenders();
\r
688 for (var i = 0, len = effectiveAppenders.length; i < len; i++) {
\r
689 effectiveAppenders[i].groupEnd();
\r
696 this.time = function(name, level) {
\r
698 if (isUndefined(name)) {
\r
699 handleError("Logger.time: a name for the timer must be supplied");
\r
700 } else if (level && !(level instanceof Level)) {
\r
701 handleError("Logger.time: level supplied to timer " +
\r
702 name + " is not an instance of log4javascript.Level");
\r
704 timers[name] = new Timer(name, level);
\r
709 this.timeEnd = function(name) {
\r
711 if (isUndefined(name)) {
\r
712 handleError("Logger.timeEnd: a name for the timer must be supplied");
\r
713 } else if (timers[name]) {
\r
714 var timer = timers[name];
\r
715 var milliseconds = timer.getElapsedTime();
\r
716 this.log(timer.level, ["Timer " + toStr(name) + " completed in " + milliseconds + "ms"]);
\r
717 delete timers[name];
\r
719 logLog.warn("Logger.timeEnd: no timer found with name " + name);
\r
724 this.assert = function(expr) {
\r
725 if (enabled && !expr) {
\r
727 for (var i = 1, len = arguments.length; i < len; i++) {
\r
728 args.push(arguments[i]);
\r
730 args = (args.length > 0) ? args : ["Assertion Failure"];
\r
731 args.push(newLine);
\r
733 this.log(Level.ERROR, args);
\r
737 this.toString = function() {
\r
738 return "Logger[" + this.name + "]";
\r
742 Logger.prototype = {
\r
743 trace: function() {
\r
744 this.log(Level.TRACE, arguments);
\r
747 debug: function() {
\r
748 this.log(Level.DEBUG, arguments);
\r
752 this.log(Level.INFO, arguments);
\r
756 this.log(Level.WARN, arguments);
\r
759 error: function() {
\r
760 this.log(Level.ERROR, arguments);
\r
763 fatal: function() {
\r
764 this.log(Level.FATAL, arguments);
\r
767 isEnabledFor: function(level) {
\r
768 return level.isGreaterOrEqual(this.getEffectiveLevel());
\r
771 isTraceEnabled: function() {
\r
772 return this.isEnabledFor(Level.TRACE);
\r
775 isDebugEnabled: function() {
\r
776 return this.isEnabledFor(Level.DEBUG);
\r
779 isInfoEnabled: function() {
\r
780 return this.isEnabledFor(Level.INFO);
\r
783 isWarnEnabled: function() {
\r
784 return this.isEnabledFor(Level.WARN);
\r
787 isErrorEnabled: function() {
\r
788 return this.isEnabledFor(Level.ERROR);
\r
791 isFatalEnabled: function() {
\r
792 return this.isEnabledFor(Level.FATAL);
\r
796 Logger.prototype.trace.isEntryPoint = true;
\r
797 Logger.prototype.debug.isEntryPoint = true;
\r
798 Logger.prototype.info.isEntryPoint = true;
\r
799 Logger.prototype.warn.isEntryPoint = true;
\r
800 Logger.prototype.error.isEntryPoint = true;
\r
801 Logger.prototype.fatal.isEntryPoint = true;
\r
803 /* ---------------------------------------------------------------------- */
\r
804 // Logger access methods
\r
806 // Hashtable of loggers keyed by logger name
\r
808 var loggerNames = [];
\r
810 var ROOT_LOGGER_DEFAULT_LEVEL = Level.DEBUG;
\r
811 var rootLogger = new Logger(rootLoggerName);
\r
812 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
\r
814 log4javascript.getRootLogger = function() {
\r
818 log4javascript.getLogger = function(loggerName) {
\r
819 // Use default logger if loggerName is not specified or invalid
\r
820 if (!(typeof loggerName == "string")) {
\r
821 loggerName = anonymousLoggerName;
\r
822 logLog.warn("log4javascript.getLogger: non-string logger name " +
\r
823 toStr(loggerName) + " supplied, returning anonymous logger");
\r
826 // Do not allow retrieval of the root logger by name
\r
827 if (loggerName == rootLoggerName) {
\r
828 handleError("log4javascript.getLogger: root logger may not be obtained by name");
\r
831 // Create the logger for this name if it doesn't already exist
\r
832 if (!loggers[loggerName]) {
\r
833 var logger = new Logger(loggerName);
\r
834 loggers[loggerName] = logger;
\r
835 loggerNames.push(loggerName);
\r
837 // Set up parent logger, if it doesn't exist
\r
838 var lastDotIndex = loggerName.lastIndexOf(".");
\r
840 if (lastDotIndex > -1) {
\r
841 var parentLoggerName = loggerName.substring(0, lastDotIndex);
\r
842 parentLogger = log4javascript.getLogger(parentLoggerName); // Recursively sets up grandparents etc.
\r
844 parentLogger = rootLogger;
\r
846 parentLogger.addChild(logger);
\r
848 return loggers[loggerName];
\r
851 var defaultLogger = null;
\r
852 log4javascript.getDefaultLogger = function() {
\r
853 if (!defaultLogger) {
\r
854 defaultLogger = log4javascript.getLogger(defaultLoggerName);
\r
855 var a = new log4javascript.PopUpAppender();
\r
856 defaultLogger.addAppender(a);
\r
858 return defaultLogger;
\r
861 var nullLogger = null;
\r
862 log4javascript.getNullLogger = function() {
\r
864 nullLogger = new Logger(nullLoggerName);
\r
865 nullLogger.setLevel(Level.OFF);
\r
870 // Destroys all loggers
\r
871 log4javascript.resetConfiguration = function() {
\r
872 rootLogger.setLevel(ROOT_LOGGER_DEFAULT_LEVEL);
\r
876 /* ---------------------------------------------------------------------- */
\r
879 var LoggingEvent = function(logger, timeStamp, level, messages,
\r
881 this.logger = logger;
\r
882 this.timeStamp = timeStamp;
\r
883 this.timeStampInMilliseconds = timeStamp.getTime();
\r
884 this.timeStampInSeconds = Math.floor(this.timeStampInMilliseconds / 1000);
\r
885 this.milliseconds = this.timeStamp.getMilliseconds();
\r
886 this.level = level;
\r
887 this.messages = messages;
\r
888 this.exception = exception;
\r
891 LoggingEvent.prototype = {
\r
892 getThrowableStrRep: function() {
\r
893 return this.exception ?
\r
894 getExceptionStringRep(this.exception) : "";
\r
896 getCombinedMessages: function() {
\r
897 return (this.messages.length == 1) ? this.messages[0] :
\r
898 this.messages.join(newLine);
\r
900 toString: function() {
\r
901 return "LoggingEvent[" + this.level + "]";
\r
905 log4javascript.LoggingEvent = LoggingEvent;
\r
907 /* ---------------------------------------------------------------------- */
\r
908 // Layout prototype
\r
910 var Layout = function() {
\r
913 Layout.prototype = {
\r
915 loggerKey: "logger",
\r
916 timeStampKey: "timestamp",
\r
917 millisecondsKey: "milliseconds",
\r
919 messageKey: "message",
\r
920 exceptionKey: "exception",
\r
923 loggerKey: "logger",
\r
924 timeStampKey: "timestamp",
\r
925 millisecondsKey: "milliseconds",
\r
927 messageKey: "message",
\r
928 exceptionKey: "exception",
\r
932 batchSeparator: "",
\r
933 returnsPostData: false,
\r
934 overrideTimeStampsSetting: false,
\r
935 useTimeStampsInMilliseconds: null,
\r
937 format: function() {
\r
938 handleError("Layout.format: layout supplied has no format() method");
\r
941 ignoresThrowable: function() {
\r
942 handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");
\r
945 getContentType: function() {
\r
946 return "text/plain";
\r
949 allowBatching: function() {
\r
953 setTimeStampsInMilliseconds: function(timeStampsInMilliseconds) {
\r
954 this.overrideTimeStampsSetting = true;
\r
955 this.useTimeStampsInMilliseconds = bool(timeStampsInMilliseconds);
\r
958 isTimeStampsInMilliseconds: function() {
\r
959 return this.overrideTimeStampsSetting ?
\r
960 this.useTimeStampsInMilliseconds : useTimeStampsInMilliseconds;
\r
963 getTimeStampValue: function(loggingEvent) {
\r
964 return this.isTimeStampsInMilliseconds() ?
\r
965 loggingEvent.timeStampInMilliseconds : loggingEvent.timeStampInSeconds;
\r
968 getDataValues: function(loggingEvent, combineMessages) {
\r
970 [this.loggerKey, loggingEvent.logger.name],
\r
971 [this.timeStampKey, this.getTimeStampValue(loggingEvent)],
\r
972 [this.levelKey, loggingEvent.level.name],
\r
973 [this.urlKey, window.location.href],
\r
974 [this.messageKey, combineMessages ? loggingEvent.getCombinedMessages() : loggingEvent.messages]
\r
976 if (!this.isTimeStampsInMilliseconds()) {
\r
977 dataValues.push([this.millisecondsKey, loggingEvent.milliseconds]);
\r
979 if (loggingEvent.exception) {
\r
980 dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]);
\r
982 if (this.hasCustomFields()) {
\r
983 for (var i = 0, len = this.customFields.length; i < len; i++) {
\r
984 var val = this.customFields[i].value;
\r
986 // Check if the value is a function. If so, execute it, passing it the
\r
987 // current layout and the logging event
\r
988 if (typeof val === "function") {
\r
989 val = val(this, loggingEvent);
\r
991 dataValues.push([this.customFields[i].name, val]);
\r
997 setKeys: function(loggerKey, timeStampKey, levelKey, messageKey,
\r
998 exceptionKey, urlKey, millisecondsKey) {
\r
999 this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey);
\r
1000 this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey);
\r
1001 this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey);
\r
1002 this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey);
\r
1003 this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey);
\r
1004 this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey);
\r
1005 this.millisecondsKey = extractStringFromParam(millisecondsKey, this.defaults.millisecondsKey);
\r
1008 setCustomField: function(name, value) {
\r
1009 var fieldUpdated = false;
\r
1010 for (var i = 0, len = this.customFields.length; i < len; i++) {
\r
1011 if (this.customFields[i].name === name) {
\r
1012 this.customFields[i].value = value;
\r
1013 fieldUpdated = true;
\r
1016 if (!fieldUpdated) {
\r
1017 this.customFields.push({"name": name, "value": value});
\r
1021 hasCustomFields: function() {
\r
1022 return (this.customFields.length > 0);
\r
1025 toString: function() {
\r
1026 handleError("Layout.toString: all layouts must override this method");
\r
1030 log4javascript.Layout = Layout;
\r
1032 /* ---------------------------------------------------------------------- */
\r
1033 // Appender prototype
\r
1035 var Appender = function() {};
\r
1037 Appender.prototype = new EventSupport();
\r
1039 Appender.prototype.layout = new PatternLayout();
\r
1040 Appender.prototype.threshold = Level.ALL;
\r
1041 Appender.prototype.loggers = [];
\r
1043 // Performs threshold checks before delegating actual logging to the
\r
1044 // subclass's specific append method.
\r
1045 Appender.prototype.doAppend = function(loggingEvent) {
\r
1046 if (enabled && loggingEvent.level.level >= this.threshold.level) {
\r
1047 this.append(loggingEvent);
\r
1051 Appender.prototype.append = function(loggingEvent) {};
\r
1053 Appender.prototype.setLayout = function(layout) {
\r
1054 if (layout instanceof Layout) {
\r
1055 this.layout = layout;
\r
1057 handleError("Appender.setLayout: layout supplied to " +
\r
1058 this.toString() + " is not a subclass of Layout");
\r
1062 Appender.prototype.getLayout = function() {
\r
1063 return this.layout;
\r
1066 Appender.prototype.setThreshold = function(threshold) {
\r
1067 if (threshold instanceof Level) {
\r
1068 this.threshold = threshold;
\r
1070 handleError("Appender.setThreshold: threshold supplied to " +
\r
1071 this.toString() + " is not a subclass of Level");
\r
1075 Appender.prototype.getThreshold = function() {
\r
1076 return this.threshold;
\r
1079 Appender.prototype.setAddedToLogger = function(logger) {
\r
1080 this.loggers.push(logger);
\r
1083 Appender.prototype.setRemovedFromLogger = function(logger) {
\r
1084 array_remove(this.loggers, logger);
\r
1087 Appender.prototype.group = emptyFunction;
\r
1088 Appender.prototype.groupEnd = emptyFunction;
\r
1090 Appender.prototype.toString = function() {
\r
1091 handleError("Appender.toString: all appenders must override this method");
\r
1094 log4javascript.Appender = Appender;
\r
1096 /* ---------------------------------------------------------------------- */
\r
1099 function SimpleLayout() {
\r
1100 this.customFields = [];
\r
1103 SimpleLayout.prototype = new Layout();
\r
1105 SimpleLayout.prototype.format = function(loggingEvent) {
\r
1106 return loggingEvent.level.name + " - " + loggingEvent.getCombinedMessages();
\r
1109 SimpleLayout.prototype.ignoresThrowable = function() {
\r
1113 SimpleLayout.prototype.toString = function() {
\r
1114 return "SimpleLayout";
\r
1117 log4javascript.SimpleLayout = SimpleLayout;
\r
1118 /* ----------------------------------------------------------------------- */
\r
1121 function NullLayout() {
\r
1122 this.customFields = [];
\r
1125 NullLayout.prototype = new Layout();
\r
1127 NullLayout.prototype.format = function(loggingEvent) {
\r
1128 return loggingEvent.messages;
\r
1131 NullLayout.prototype.ignoresThrowable = function() {
\r
1135 NullLayout.prototype.toString = function() {
\r
1136 return "NullLayout";
\r
1139 log4javascript.NullLayout = NullLayout;
\r
1140 /* ---------------------------------------------------------------------- */
\r
1143 function XmlLayout(combineMessages) {
\r
1144 this.combineMessages = extractBooleanFromParam(combineMessages, true);
\r
1145 this.customFields = [];
\r
1148 XmlLayout.prototype = new Layout();
\r
1150 XmlLayout.prototype.isCombinedMessages = function() {
\r
1151 return this.combineMessages;
\r
1154 XmlLayout.prototype.getContentType = function() {
\r
1155 return "text/xml";
\r
1158 XmlLayout.prototype.escapeCdata = function(str) {
\r
1159 return str.replace(/\]\]>/, "]]>]]><![CDATA[");
\r
1162 XmlLayout.prototype.format = function(loggingEvent) {
\r
1163 var layout = this;
\r
1165 function formatMessage(message) {
\r
1166 message = (typeof message === "string") ? message : toStr(message);
\r
1167 return "<log4javascript:message><![CDATA[" +
\r
1168 layout.escapeCdata(message) + "]]></log4javascript:message>";
\r
1171 var str = "<log4javascript:event logger=\"" + loggingEvent.logger.name +
\r
1172 "\" timestamp=\"" + this.getTimeStampValue(loggingEvent) + "\"";
\r
1173 if (!this.isTimeStampsInMilliseconds()) {
\r
1174 str += " milliseconds=\"" + loggingEvent.milliseconds + "\"";
\r
1176 str += " level=\"" + loggingEvent.level.name + "\">" + newLine;
\r
1177 if (this.combineMessages) {
\r
1178 str += formatMessage(loggingEvent.getCombinedMessages());
\r
1180 str += "<log4javascript:messages>" + newLine;
\r
1181 for (i = 0, len = loggingEvent.messages.length; i < len; i++) {
\r
1182 str += formatMessage(loggingEvent.messages[i]) + newLine;
\r
1184 str += "</log4javascript:messages>" + newLine;
\r
1186 if (this.hasCustomFields()) {
\r
1187 for (i = 0, len = this.customFields.length; i < len; i++) {
\r
1188 str += "<log4javascript:customfield name=\"" +
\r
1189 this.customFields[i].name + "\"><![CDATA[" +
\r
1190 this.customFields[i].value.toString() +
\r
1191 "]]></log4javascript:customfield>" + newLine;
\r
1194 if (loggingEvent.exception) {
\r
1195 str += "<log4javascript:exception><![CDATA[" +
\r
1196 getExceptionStringRep(loggingEvent.exception) +
\r
1197 "]]></log4javascript:exception>" + newLine;
\r
1199 str += "</log4javascript:event>" + newLine + newLine;
\r
1203 XmlLayout.prototype.ignoresThrowable = function() {
\r
1207 XmlLayout.prototype.toString = function() {
\r
1208 return "XmlLayout";
\r
1211 log4javascript.XmlLayout = XmlLayout;
\r
1212 /* ---------------------------------------------------------------------- */
\r
1213 // JsonLayout related
\r
1215 function escapeNewLines(str) {
\r
1216 return str.replace(/\r\n|\r|\n/g, "\\r\\n");
\r
1219 function JsonLayout(readable, combineMessages) {
\r
1220 this.readable = extractBooleanFromParam(readable, false);
\r
1221 this.combineMessages = extractBooleanFromParam(combineMessages, true);
\r
1222 this.batchHeader = this.readable ? "[" + newLine : "[";
\r
1223 this.batchFooter = this.readable ? "]" + newLine : "]";
\r
1224 this.batchSeparator = this.readable ? "," + newLine : ",";
\r
1226 this.colon = this.readable ? ": " : ":";
\r
1227 this.tab = this.readable ? "\t" : "";
\r
1228 this.lineBreak = this.readable ? newLine : "";
\r
1229 this.customFields = [];
\r
1232 /* ---------------------------------------------------------------------- */
\r
1235 JsonLayout.prototype = new Layout();
\r
1237 JsonLayout.prototype.isReadable = function() {
\r
1238 return this.readable;
\r
1241 JsonLayout.prototype.isCombinedMessages = function() {
\r
1242 return this.combineMessages;
\r
1245 JsonLayout.prototype.format = function(loggingEvent) {
\r
1246 var layout = this;
\r
1247 var dataValues = this.getDataValues(loggingEvent, this.combineMessages);
\r
1248 var str = "{" + this.lineBreak;
\r
1251 function formatValue(val, prefix, expand) {
\r
1252 // Check the type of the data value to decide whether quotation marks
\r
1253 // or expansion are required
\r
1254 var formattedValue;
\r
1255 var valType = typeof val;
\r
1256 if (val instanceof Date) {
\r
1257 formattedValue = String(val.getTime());
\r
1258 } else if (expand && (val instanceof Array)) {
\r
1259 formattedValue = "[" + layout.lineBreak;
\r
1260 for (var i = 0, len = val.length; i < len; i++) {
\r
1261 var childPrefix = prefix + layout.tab;
\r
1262 formattedValue += childPrefix + formatValue(val[i], childPrefix, false);
\r
1263 if (i < val.length - 1) {
\r
1264 formattedValue += ",";
\r
1266 formattedValue += layout.lineBreak;
\r
1268 formattedValue += prefix + "]";
\r
1269 } else if (valType !== "number" && valType !== "boolean") {
\r
1270 formattedValue = "\"" + escapeNewLines(toStr(val).replace(/\"/g, "\\\"")) + "\"";
\r
1272 formattedValue = val;
\r
1274 return formattedValue;
\r
1277 for (i = 0, len = dataValues.length - 1; i <= len; i++) {
\r
1278 str += this.tab + "\"" + dataValues[i][0] + "\"" + this.colon + formatValue(dataValues[i][1], this.tab, true);
\r
1282 str += this.lineBreak;
\r
1285 str += "}" + this.lineBreak;
\r
1289 JsonLayout.prototype.ignoresThrowable = function() {
\r
1293 JsonLayout.prototype.toString = function() {
\r
1294 return "JsonLayout";
\r
1297 JsonLayout.prototype.getContentType = function() {
\r
1298 return "application/json";
\r
1301 log4javascript.JsonLayout = JsonLayout;
\r
1302 /* ---------------------------------------------------------------------- */
\r
1303 // HttpPostDataLayout
\r
1305 function HttpPostDataLayout() {
\r
1307 this.customFields = [];
\r
1308 this.returnsPostData = true;
\r
1311 HttpPostDataLayout.prototype = new Layout();
\r
1313 // Disable batching
\r
1314 HttpPostDataLayout.prototype.allowBatching = function() {
\r
1318 HttpPostDataLayout.prototype.format = function(loggingEvent) {
\r
1319 var dataValues = this.getDataValues(loggingEvent);
\r
1320 var queryBits = [];
\r
1321 for (var i = 0, len = dataValues.length; i < len; i++) {
\r
1322 var val = (dataValues[i][1] instanceof Date) ?
\r
1323 String(dataValues[i][1].getTime()) : dataValues[i][1];
\r
1324 queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(val));
\r
1326 return queryBits.join("&");
\r
1329 HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) {
\r
1333 HttpPostDataLayout.prototype.toString = function() {
\r
1334 return "HttpPostDataLayout";
\r
1337 log4javascript.HttpPostDataLayout = HttpPostDataLayout;
\r
1338 /* ---------------------------------------------------------------------- */
\r
1339 // formatObjectExpansion
\r
1341 function formatObjectExpansion(obj, depth, indentation) {
\r
1342 var objectsExpanded = [];
\r
1344 function doFormat(obj, depth, indentation) {
\r
1345 var i, j, len, childDepth, childIndentation, childLines, expansion,
\r
1348 if (!indentation) {
\r
1352 function formatString(text) {
\r
1353 var lines = splitIntoLines(text);
\r
1354 for (var j = 1, jLen = lines.length; j < jLen; j++) {
\r
1355 lines[j] = indentation + lines[j];
\r
1357 return lines.join(newLine);
\r
1360 if (obj === null) {
\r
1362 } else if (typeof obj == "undefined") {
\r
1363 return "undefined";
\r
1364 } else if (typeof obj == "string") {
\r
1365 return formatString(obj);
\r
1366 } else if (typeof obj == "object" && array_contains(objectsExpanded, obj)) {
\r
1368 expansion = toStr(obj);
\r
1370 expansion = "Error formatting property. Details: " + getExceptionStringRep(ex);
\r
1372 return expansion + " [already expanded]";
\r
1373 } else if ((obj instanceof Array) && depth > 0) {
\r
1374 objectsExpanded.push(obj);
\r
1375 expansion = "[" + newLine;
\r
1376 childDepth = depth - 1;
\r
1377 childIndentation = indentation + " ";
\r
1379 for (i = 0, len = obj.length; i < len; i++) {
\r
1381 childExpansion = doFormat(obj[i], childDepth, childIndentation);
\r
1382 childLines.push(childIndentation + childExpansion);
\r
1384 childLines.push(childIndentation + "Error formatting array member. Details: " +
\r
1385 getExceptionStringRep(ex) + "");
\r
1388 expansion += childLines.join("," + newLine) + newLine + indentation + "]";
\r
1390 } else if (Object.prototype.toString.call(obj) == "[object Date]") {
\r
1391 return obj.toString();
\r
1392 } else if (typeof obj == "object" && depth > 0) {
\r
1393 objectsExpanded.push(obj);
\r
1394 expansion = "{" + newLine;
\r
1395 childDepth = depth - 1;
\r
1396 childIndentation = indentation + " ";
\r
1400 childExpansion = doFormat(obj[i], childDepth, childIndentation);
\r
1401 childLines.push(childIndentation + i + ": " + childExpansion);
\r
1403 childLines.push(childIndentation + i + ": Error formatting property. Details: " +
\r
1404 getExceptionStringRep(ex));
\r
1407 expansion += childLines.join("," + newLine) + newLine + indentation + "}";
\r
1410 return formatString(toStr(obj));
\r
1413 return doFormat(obj, depth, indentation);
\r
1415 /* ---------------------------------------------------------------------- */
\r
1416 // Date-related stuff
\r
1418 var SimpleDateFormat;
\r
1421 var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;
\r
1422 var monthNames = ["January", "February", "March", "April", "May", "June",
\r
1423 "July", "August", "September", "October", "November", "December"];
\r
1424 var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
\r
1425 var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
\r
1446 var ONE_DAY = 24 * 60 * 60 * 1000;
\r
1447 var ONE_WEEK = 7 * ONE_DAY;
\r
1448 var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;
\r
1450 var newDateAtMidnight = function(year, month, day) {
\r
1451 var d = new Date(year, month, day, 0, 0, 0);
\r
1452 d.setMilliseconds(0);
\r
1456 Date.prototype.getDifference = function(date) {
\r
1457 return this.getTime() - date.getTime();
\r
1460 Date.prototype.isBefore = function(d) {
\r
1461 return this.getTime() < d.getTime();
\r
1464 Date.prototype.getUTCTime = function() {
\r
1465 return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
\r
1466 this.getSeconds(), this.getMilliseconds());
\r
1469 Date.prototype.getTimeSince = function(d) {
\r
1470 return this.getUTCTime() - d.getUTCTime();
\r
1473 Date.prototype.getPreviousSunday = function() {
\r
1474 // Using midday avoids any possibility of DST messing things up
\r
1475 var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
\r
1476 var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
\r
1477 return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
\r
1478 previousSunday.getDate());
\r
1481 Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
\r
1482 if (isUndefined(this.minimalDaysInFirstWeek)) {
\r
1483 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
\r
1485 var previousSunday = this.getPreviousSunday();
\r
1486 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
\r
1487 var numberOfSundays = previousSunday.isBefore(startOfYear) ?
\r
1488 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
\r
1489 var numberOfDaysInFirstWeek = 7 - startOfYear.getDay();
\r
1490 var weekInYear = numberOfSundays;
\r
1491 if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
\r
1494 return weekInYear;
\r
1497 Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
\r
1498 if (isUndefined(this.minimalDaysInFirstWeek)) {
\r
1499 minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
\r
1501 var previousSunday = this.getPreviousSunday();
\r
1502 var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
\r
1503 var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
\r
1504 0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK);
\r
1505 var numberOfDaysInFirstWeek = 7 - startOfMonth.getDay();
\r
1506 var weekInMonth = numberOfSundays;
\r
1507 if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
\r
1510 return weekInMonth;
\r
1513 Date.prototype.getDayInYear = function() {
\r
1514 var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
\r
1515 return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
\r
1518 /* ------------------------------------------------------------------ */
\r
1520 SimpleDateFormat = function(formatString) {
\r
1521 this.formatString = formatString;
\r
1525 * Sets the minimum number of days in a week in order for that week to
\r
1526 * be considered as belonging to a particular month or year
\r
1528 SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
\r
1529 this.minimalDaysInFirstWeek = days;
\r
1532 SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() {
\r
1533 return isUndefined(this.minimalDaysInFirstWeek) ?
\r
1534 DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
\r
1537 var padWithZeroes = function(str, len) {
\r
1538 while (str.length < len) {
\r
1544 var formatText = function(data, numberOfLetters, minLength) {
\r
1545 return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
\r
1548 var formatNumber = function(data, numberOfLetters) {
\r
1549 var dataString = "" + data;
\r
1550 // Pad with 0s as necessary
\r
1551 return padWithZeroes(dataString, numberOfLetters);
\r
1554 SimpleDateFormat.prototype.format = function(date) {
\r
1555 var formattedString = "";
\r
1557 var searchString = this.formatString;
\r
1558 while ((result = regex.exec(searchString))) {
\r
1559 var quotedString = result[1];
\r
1560 var patternLetters = result[2];
\r
1561 var otherLetters = result[3];
\r
1562 var otherCharacters = result[4];
\r
1564 // If the pattern matched is quoted string, output the text between the quotes
\r
1565 if (quotedString) {
\r
1566 if (quotedString == "''") {
\r
1567 formattedString += "'";
\r
1569 formattedString += quotedString.substring(1, quotedString.length - 1);
\r
1571 } else if (otherLetters) {
\r
1572 // Swallow non-pattern letters by doing nothing here
\r
1573 } else if (otherCharacters) {
\r
1574 // Simply output other characters
\r
1575 formattedString += otherCharacters;
\r
1576 } else if (patternLetters) {
\r
1577 // Replace pattern letters
\r
1578 var patternLetter = patternLetters.charAt(0);
\r
1579 var numberOfLetters = patternLetters.length;
\r
1581 switch(patternLetter) {
\r
1586 rawData = date.getFullYear();
\r
1589 rawData = date.getMonth();
\r
1592 rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
\r
1595 rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
\r
1598 rawData = date.getDayInYear();
\r
1601 rawData = date.getDate();
\r
1604 rawData = 1 + Math.floor((date.getDate() - 1) / 7);
\r
1607 rawData = dayNames[date.getDay()];
\r
1610 rawData = (date.getHours() >= 12) ? "PM" : "AM";
\r
1613 rawData = date.getHours();
\r
1616 rawData = date.getHours() || 24;
\r
1619 rawData = date.getHours() % 12;
\r
1622 rawData = (date.getHours() % 12) || 12;
\r
1625 rawData = date.getMinutes();
\r
1628 rawData = date.getSeconds();
\r
1631 rawData = date.getMilliseconds();
\r
1634 rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time.
\r
1637 // Format the raw data depending on the type
\r
1638 switch(types[patternLetter]) {
\r
1640 formattedString += formatText(rawData, numberOfLetters, 2);
\r
1643 formattedString += formatText(rawData, numberOfLetters, 3);
\r
1646 formattedString += formatNumber(rawData, numberOfLetters);
\r
1649 if (numberOfLetters <= 3) {
\r
1650 // Output a 2-digit year
\r
1651 var dataString = "" + rawData;
\r
1652 formattedString += dataString.substr(2, 2);
\r
1654 formattedString += formatNumber(rawData, numberOfLetters);
\r
1658 if (numberOfLetters >= 3) {
\r
1659 formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
\r
1661 // NB. Months returned by getMonth are zero-based
\r
1662 formattedString += formatNumber(rawData + 1, numberOfLetters);
\r
1666 var isPositive = (rawData > 0);
\r
1667 // The following line looks like a mistake but isn't
\r
1668 // because of the way getTimezoneOffset measures.
\r
1669 var prefix = isPositive ? "-" : "+";
\r
1670 var absData = Math.abs(rawData);
\r
1673 var hours = "" + Math.floor(absData / 60);
\r
1674 hours = padWithZeroes(hours, 2);
\r
1676 var minutes = "" + (absData % 60);
\r
1677 minutes = padWithZeroes(minutes, 2);
\r
1679 formattedString += prefix + hours + minutes;
\r
1683 searchString = searchString.substr(result.index + result[0].length);
\r
1685 return formattedString;
\r
1689 log4javascript.SimpleDateFormat = SimpleDateFormat;
\r
1691 /* ---------------------------------------------------------------------- */
\r
1694 function PatternLayout(pattern) {
\r
1696 this.pattern = pattern;
\r
1698 this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
\r
1700 this.customFields = [];
\r
1703 PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
\r
1704 PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n";
\r
1705 PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
\r
1706 PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS";
\r
1707 PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS";
\r
1709 PatternLayout.prototype = new Layout();
\r
1711 PatternLayout.prototype.format = function(loggingEvent) {
\r
1712 var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([acdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;
\r
1713 var formattedString = "";
\r
1715 var searchString = this.pattern;
\r
1717 // Cannot use regex global flag since it doesn't work with exec in IE5
\r
1718 while ((result = regex.exec(searchString))) {
\r
1719 var matchedString = result[0];
\r
1720 var padding = result[1];
\r
1721 var truncation = result[2];
\r
1722 var conversionCharacter = result[3];
\r
1723 var specifier = result[5];
\r
1724 var text = result[6];
\r
1726 // Check if the pattern matched was just normal text
\r
1728 formattedString += "" + text;
\r
1730 // Create a raw replacement string based on the conversion
\r
1731 // character and specifier
\r
1732 var replacement = "";
\r
1733 switch(conversionCharacter) {
\r
1734 case "a": // Array of messages
\r
1735 case "m": // Message
\r
1738 depth = parseInt(specifier, 10);
\r
1739 if (isNaN(depth)) {
\r
1740 handleError("PatternLayout.format: invalid specifier '" +
\r
1741 specifier + "' for conversion character '" + conversionCharacter +
\r
1742 "' - should be a number");
\r
1746 var messages = (conversionCharacter === "a") ? loggingEvent.messages[0] : loggingEvent.messages;
\r
1747 for (var i = 0, len = messages.length; i < len; i++) {
\r
1748 if (i > 0 && (replacement.charAt(replacement.length - 1) !== " ")) {
\r
1749 replacement += " ";
\r
1751 if (depth === 0) {
\r
1752 replacement += messages[i];
\r
1754 replacement += formatObjectExpansion(messages[i], depth);
\r
1758 case "c": // Logger name
\r
1759 var loggerName = loggingEvent.logger.name;
\r
1761 var precision = parseInt(specifier, 10);
\r
1762 var loggerNameBits = loggingEvent.logger.name.split(".");
\r
1763 if (precision >= loggerNameBits.length) {
\r
1764 replacement = loggerName;
\r
1766 replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
\r
1769 replacement = loggerName;
\r
1773 var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
\r
1775 dateFormat = specifier;
\r
1776 // Pick up special cases
\r
1777 if (dateFormat == "ISO8601") {
\r
1778 dateFormat = PatternLayout.ISO8601_DATEFORMAT;
\r
1779 } else if (dateFormat == "ABSOLUTE") {
\r
1780 dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
\r
1781 } else if (dateFormat == "DATE") {
\r
1782 dateFormat = PatternLayout.DATETIME_DATEFORMAT;
\r
1785 // Format the date
\r
1786 replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);
\r
1788 case "f": // Custom field
\r
1789 if (this.hasCustomFields()) {
\r
1790 var fieldIndex = 0;
\r
1792 fieldIndex = parseInt(specifier, 10);
\r
1793 if (isNaN(fieldIndex)) {
\r
1794 handleError("PatternLayout.format: invalid specifier '" +
\r
1795 specifier + "' for conversion character 'f' - should be a number");
\r
1796 } else if (fieldIndex === 0) {
\r
1797 handleError("PatternLayout.format: invalid specifier '" +
\r
1798 specifier + "' for conversion character 'f' - must be greater than zero");
\r
1799 } else if (fieldIndex > this.customFields.length) {
\r
1800 handleError("PatternLayout.format: invalid specifier '" +
\r
1801 specifier + "' for conversion character 'f' - there aren't that many custom fields");
\r
1803 fieldIndex = fieldIndex - 1;
\r
1806 var val = this.customFields[fieldIndex].value;
\r
1807 if (typeof val == "function") {
\r
1808 val = val(this, loggingEvent);
\r
1810 replacement = val;
\r
1813 case "n": // New line
\r
1814 replacement = newLine;
\r
1816 case "p": // Level
\r
1817 replacement = loggingEvent.level.name;
\r
1819 case "r": // Milliseconds since log4javascript startup
\r
1820 replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate);
\r
1822 case "%": // Literal % sign
\r
1823 replacement = "%";
\r
1826 replacement = matchedString;
\r
1829 // Format the replacement according to any padding or
\r
1830 // truncation specified
\r
1833 // First, truncation
\r
1835 l = parseInt(truncation.substr(1), 10);
\r
1836 var strLen = replacement.length;
\r
1838 replacement = replacement.substring(strLen - l, strLen);
\r
1843 if (padding.charAt(0) == "-") {
\r
1844 l = parseInt(padding.substr(1), 10);
\r
1845 // Right pad with spaces
\r
1846 while (replacement.length < l) {
\r
1847 replacement += " ";
\r
1850 l = parseInt(padding, 10);
\r
1851 // Left pad with spaces
\r
1852 while (replacement.length < l) {
\r
1853 replacement = " " + replacement;
\r
1857 formattedString += replacement;
\r
1859 searchString = searchString.substr(result.index + result[0].length);
\r
1861 return formattedString;
\r
1864 PatternLayout.prototype.ignoresThrowable = function() {
\r
1868 PatternLayout.prototype.toString = function() {
\r
1869 return "PatternLayout";
\r
1872 log4javascript.PatternLayout = PatternLayout;
\r
1873 /* ---------------------------------------------------------------------- */
\r
1874 // AjaxAppender related
\r
1876 var xmlHttpFactories = [
\r
1877 function() { return new XMLHttpRequest(); },
\r
1878 function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
\r
1879 function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
\r
1882 var getXmlHttp = function(errorHandler) {
\r
1883 // This is only run the first time; the value of getXmlHttp gets
\r
1884 // replaced with the factory that succeeds on the first run
\r
1885 var xmlHttp = null, factory;
\r
1886 for (var i = 0, len = xmlHttpFactories.length; i < len; i++) {
\r
1887 factory = xmlHttpFactories[i];
\r
1889 xmlHttp = factory();
\r
1890 getXmlHttp = factory;
\r
1895 // If we're here, all factories have failed, so throw an error
\r
1896 if (errorHandler) {
\r
1899 handleError("getXmlHttp: unable to obtain XMLHttpRequest object");
\r
1903 function isHttpRequestSuccessful(xmlHttp) {
\r
1904 return isUndefined(xmlHttp.status) || xmlHttp.status === 0 ||
\r
1905 (xmlHttp.status >= 200 && xmlHttp.status < 300) ||
\r
1906 xmlHttp.status == 1223 /* Fix for IE */;
\r
1909 /* ---------------------------------------------------------------------- */
\r
1912 function AjaxAppender(url) {
\r
1913 var appender = this;
\r
1914 var isSupported = true;
\r
1916 handleError("AjaxAppender: URL must be specified in constructor");
\r
1917 isSupported = false;
\r
1920 var timed = this.defaults.timed;
\r
1921 var waitForResponse = this.defaults.waitForResponse;
\r
1922 var batchSize = this.defaults.batchSize;
\r
1923 var timerInterval = this.defaults.timerInterval;
\r
1924 var requestSuccessCallback = this.defaults.requestSuccessCallback;
\r
1925 var failCallback = this.defaults.failCallback;
\r
1926 var postVarName = this.defaults.postVarName;
\r
1927 var sendAllOnUnload = this.defaults.sendAllOnUnload;
\r
1928 var contentType = this.defaults.contentType;
\r
1929 var sessionId = null;
\r
1931 var queuedLoggingEvents = [];
\r
1932 var queuedRequests = [];
\r
1934 var sending = false;
\r
1935 var initialized = false;
\r
1937 // Configuration methods. The function scope is used to prevent
\r
1938 // direct alteration to the appender configuration properties.
\r
1939 function checkCanConfigure(configOptionName) {
\r
1940 if (initialized) {
\r
1941 handleError("AjaxAppender: configuration option '" +
\r
1942 configOptionName +
\r
1943 "' may not be set after the appender has been initialized");
\r
1949 this.getSessionId = function() { return sessionId; };
\r
1950 this.setSessionId = function(sessionIdParam) {
\r
1951 sessionId = extractStringFromParam(sessionIdParam, null);
\r
1952 this.layout.setCustomField("sessionid", sessionId);
\r
1955 this.setLayout = function(layoutParam) {
\r
1956 if (checkCanConfigure("layout")) {
\r
1957 this.layout = layoutParam;
\r
1958 // Set the session id as a custom field on the layout, if not already present
\r
1959 if (sessionId !== null) {
\r
1960 this.setSessionId(sessionId);
\r
1965 this.isTimed = function() { return timed; };
\r
1966 this.setTimed = function(timedParam) {
\r
1967 if (checkCanConfigure("timed")) {
\r
1968 timed = bool(timedParam);
\r
1972 this.getTimerInterval = function() { return timerInterval; };
\r
1973 this.setTimerInterval = function(timerIntervalParam) {
\r
1974 if (checkCanConfigure("timerInterval")) {
\r
1975 timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
\r
1979 this.isWaitForResponse = function() { return waitForResponse; };
\r
1980 this.setWaitForResponse = function(waitForResponseParam) {
\r
1981 if (checkCanConfigure("waitForResponse")) {
\r
1982 waitForResponse = bool(waitForResponseParam);
\r
1986 this.getBatchSize = function() { return batchSize; };
\r
1987 this.setBatchSize = function(batchSizeParam) {
\r
1988 if (checkCanConfigure("batchSize")) {
\r
1989 batchSize = extractIntFromParam(batchSizeParam, batchSize);
\r
1993 this.isSendAllOnUnload = function() { return sendAllOnUnload; };
\r
1994 this.setSendAllOnUnload = function(sendAllOnUnloadParam) {
\r
1995 if (checkCanConfigure("sendAllOnUnload")) {
\r
1996 sendAllOnUnload = extractBooleanFromParam(sendAllOnUnloadParam, sendAllOnUnload);
\r
2000 this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
\r
2001 requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
\r
2004 this.setFailCallback = function(failCallbackParam) {
\r
2005 failCallback = extractFunctionFromParam(failCallbackParam, failCallback);
\r
2008 this.getPostVarName = function() { return postVarName; };
\r
2009 this.setPostVarName = function(postVarNameParam) {
\r
2010 if (checkCanConfigure("postVarName")) {
\r
2011 postVarName = extractStringFromParam(postVarNameParam, postVarName);
\r
2015 this.getHeaders = function() { return headers; };
\r
2016 this.addHeader = function(name, value) {
\r
2017 if (name.toLowerCase() == "content-type") {
\r
2018 contentType = value;
\r
2020 headers.push( { name: name, value: value } );
\r
2024 // Internal functions
\r
2025 function sendAll() {
\r
2026 if (isSupported && enabled) {
\r
2028 var currentRequestBatch;
\r
2029 if (waitForResponse) {
\r
2030 // Send the first request then use this function as the callback once
\r
2031 // the response comes back
\r
2032 if (queuedRequests.length > 0) {
\r
2033 currentRequestBatch = queuedRequests.shift();
\r
2034 sendRequest(preparePostData(currentRequestBatch), sendAll);
\r
2038 scheduleSending();
\r
2042 // Rattle off all the requests without waiting to see the response
\r
2043 while ((currentRequestBatch = queuedRequests.shift())) {
\r
2044 sendRequest(preparePostData(currentRequestBatch));
\r
2048 scheduleSending();
\r
2054 this.sendAll = sendAll;
\r
2056 // Called when the window unloads. At this point we're past caring about
\r
2057 // waiting for responses or timers or incomplete batches - everything
\r
2059 function sendAllRemaining() {
\r
2060 var sendingAnything = false;
\r
2061 if (isSupported && enabled) {
\r
2062 // Create requests for everything left over, batched as normal
\r
2063 var actualBatchSize = appender.getLayout().allowBatching() ? batchSize : 1;
\r
2064 var currentLoggingEvent;
\r
2065 var batchedLoggingEvents = [];
\r
2066 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
\r
2067 batchedLoggingEvents.push(currentLoggingEvent);
\r
2068 if (queuedLoggingEvents.length >= actualBatchSize) {
\r
2069 // Queue this batch of log entries
\r
2070 queuedRequests.push(batchedLoggingEvents);
\r
2071 batchedLoggingEvents = [];
\r
2074 // If there's a partially completed batch, add it
\r
2075 if (batchedLoggingEvents.length > 0) {
\r
2076 queuedRequests.push(batchedLoggingEvents);
\r
2078 sendingAnything = (queuedRequests.length > 0);
\r
2079 waitForResponse = false;
\r
2083 return sendingAnything;
\r
2086 this.sendAllRemaining = sendAllRemaining;
\r
2088 function preparePostData(batchedLoggingEvents) {
\r
2089 // Format the logging events
\r
2090 var formattedMessages = [];
\r
2091 var currentLoggingEvent;
\r
2092 var postData = "";
\r
2093 while ((currentLoggingEvent = batchedLoggingEvents.shift())) {
\r
2094 var currentFormattedMessage = appender.getLayout().format(currentLoggingEvent);
\r
2095 if (appender.getLayout().ignoresThrowable()) {
\r
2096 currentFormattedMessage += currentLoggingEvent.getThrowableStrRep();
\r
2098 formattedMessages.push(currentFormattedMessage);
\r
2100 // Create the post data string
\r
2101 if (batchedLoggingEvents.length == 1) {
\r
2102 postData = formattedMessages.join("");
\r
2104 postData = appender.getLayout().batchHeader +
\r
2105 formattedMessages.join(appender.getLayout().batchSeparator) +
\r
2106 appender.getLayout().batchFooter;
\r
2108 if (contentType == appender.defaults.contentType) {
\r
2109 postData = appender.getLayout().returnsPostData ? postData :
\r
2110 urlEncode(postVarName) + "=" + urlEncode(postData);
\r
2111 // Add the layout name to the post data
\r
2112 if (postData.length > 0) {
\r
2115 postData += "layout=" + urlEncode(appender.getLayout().toString());
\r
2120 function scheduleSending() {
\r
2121 window.setTimeout(sendAll, timerInterval);
\r
2124 function xmlHttpErrorHandler() {
\r
2125 var msg = "AjaxAppender: could not create XMLHttpRequest object. AjaxAppender disabled";
\r
2127 isSupported = false;
\r
2128 if (failCallback) {
\r
2129 failCallback(msg);
\r
2133 function sendRequest(postData, successCallback) {
\r
2135 var xmlHttp = getXmlHttp(xmlHttpErrorHandler);
\r
2136 if (isSupported) {
\r
2137 if (xmlHttp.overrideMimeType) {
\r
2138 xmlHttp.overrideMimeType(appender.getLayout().getContentType());
\r
2140 xmlHttp.onreadystatechange = function() {
\r
2141 if (xmlHttp.readyState == 4) {
\r
2142 if (isHttpRequestSuccessful(xmlHttp)) {
\r
2143 if (requestSuccessCallback) {
\r
2144 requestSuccessCallback(xmlHttp);
\r
2146 if (successCallback) {
\r
2147 successCallback(xmlHttp);
\r
2150 var msg = "AjaxAppender.append: XMLHttpRequest request to URL " +
\r
2151 url + " returned status code " + xmlHttp.status;
\r
2153 if (failCallback) {
\r
2154 failCallback(msg);
\r
2157 xmlHttp.onreadystatechange = emptyFunction;
\r
2161 xmlHttp.open("POST", url, true);
\r
2163 for (var i = 0, header; header = headers[i++]; ) {
\r
2164 xmlHttp.setRequestHeader(header.name, header.value);
\r
2166 xmlHttp.setRequestHeader("Content-Type", contentType);
\r
2167 } catch (headerEx) {
\r
2168 var msg = "AjaxAppender.append: your browser's XMLHttpRequest implementation" +
\r
2169 " does not support setRequestHeader, therefore cannot post data. AjaxAppender disabled";
\r
2171 isSupported = false;
\r
2172 if (failCallback) {
\r
2173 failCallback(msg);
\r
2177 xmlHttp.send(postData);
\r
2180 var errMsg = "AjaxAppender.append: error sending log message to " + url;
\r
2181 handleError(errMsg, ex);
\r
2182 isSupported = false;
\r
2183 if (failCallback) {
\r
2184 failCallback(errMsg + ". Details: " + getExceptionStringRep(ex));
\r
2189 this.append = function(loggingEvent) {
\r
2190 if (isSupported) {
\r
2191 if (!initialized) {
\r
2194 queuedLoggingEvents.push(loggingEvent);
\r
2195 var actualBatchSize = this.getLayout().allowBatching() ? batchSize : 1;
\r
2197 if (queuedLoggingEvents.length >= actualBatchSize) {
\r
2198 var currentLoggingEvent;
\r
2199 var batchedLoggingEvents = [];
\r
2200 while ((currentLoggingEvent = queuedLoggingEvents.shift())) {
\r
2201 batchedLoggingEvents.push(currentLoggingEvent);
\r
2203 // Queue this batch of log entries
\r
2204 queuedRequests.push(batchedLoggingEvents);
\r
2206 // If using a timer, the queue of requests will be processed by the
\r
2207 // timer function, so nothing needs to be done here.
\r
2208 if (!timed && (!waitForResponse || (waitForResponse && !sending))) {
\r
2216 initialized = true;
\r
2217 // Add unload event to send outstanding messages
\r
2218 if (sendAllOnUnload) {
\r
2219 var oldBeforeUnload = window.onbeforeunload;
\r
2220 window.onbeforeunload = function() {
\r
2221 if (oldBeforeUnload) {
\r
2222 oldBeforeUnload();
\r
2224 if (sendAllRemaining()) {
\r
2225 return "Sending log messages";
\r
2231 scheduleSending();
\r
2236 AjaxAppender.prototype = new Appender();
\r
2238 AjaxAppender.prototype.defaults = {
\r
2239 waitForResponse: false,
\r
2241 timerInterval: 1000,
\r
2243 sendAllOnUnload: false,
\r
2244 requestSuccessCallback: null,
\r
2245 failCallback: null,
\r
2246 postVarName: "data",
\r
2247 contentType: "application/x-www-form-urlencoded"
\r
2250 AjaxAppender.prototype.layout = new HttpPostDataLayout();
\r
2252 AjaxAppender.prototype.toString = function() {
\r
2253 return "AjaxAppender";
\r
2256 log4javascript.AjaxAppender = AjaxAppender;
\r
2258 /* ---------------------------------------------------------------------- */
\r
2261 log4javascript.setDocumentReady = function() {
\r
2262 pageLoaded = true;
\r
2263 log4javascript.dispatchEvent("load", {});
\r
2266 if (window.addEventListener) {
\r
2267 window.addEventListener("load", log4javascript.setDocumentReady, false);
\r
2268 } else if (window.attachEvent) {
\r
2269 window.attachEvent("onload", log4javascript.setDocumentReady);
\r
2271 var oldOnload = window.onload;
\r
2272 if (typeof window.onload != "function") {
\r
2273 window.onload = log4javascript.setDocumentReady;
\r
2275 window.onload = function(evt) {
\r
2279 log4javascript.setDocumentReady();
\r
2284 // Ensure that the log4javascript object is available in the window. This
\r
2285 // is necessary for log4javascript to be available in IE if loaded using
\r
2286 // Dojo's module system
\r
2287 window.log4javascript = log4javascript;
\r
2289 return log4javascript;
\r