tation, childLines;
if ((obj instanceof Array) && depth > 0) {
if (!indentation) {
indentation = "";
}
output = "[" + newLine;
childDepth = depth - 1;
childIndentation = indentation + " ";
childLines = [];
for (i = 0; i < obj.length; i++) {
childLines.push(childIndentation + formatObjectExpansion(obj[i], childDepth, childIndentation));
}
output += childLines.join("," + newLine) + newLine + indentation + "]";
return output;
} else if (typeof obj == "object" && depth > 0) {
if (!indentation) {
indentation = "";
}
output = "" + "{" + newLine;
childDepth = depth - 1;
childIndentation = indentation + " ";
childLines = [];
for (i in obj) {
childLines.push(childIndentation + i + ": " + formatObjectExpansion(obj[i], childDepth, childIndentation));
}
output += childLines.join("," + newLine) + newLine + indentation + "}";
return output;
} else if (typeof obj == "string") {
return obj;
} else {
return obj.toString();
}
}
function escapeNewLines(str) {
return str.replace(/\r\n|\r|\n/g, "\\r\\n");
}
function urlEncode(str) {
return escape(str).replace(/\+/g, "%2B").replace(/"/g, "%22").replace(/'/g, "%27").replace(/\//g, "%2F");
}
function bool(obj) {
return Boolean(obj);
}
function array_remove(arr, val) {
var index = -1;
for (var i = 0; i < arr.length; i++) {
if (arr[i] === val) {
index = i;
break;
}
}
if (index >= 0) {
arr.splice(index, 1);
return true;
} else {
return false;
}
}
function extractBooleanFromParam(param, defaultValue) {
if (isUndefined(param)) {
return defaultValue;
} else {
return bool(param);
}
}
function extractStringFromParam(param, defaultValue) {
if (isUndefined(param)) {
return defaultValue;
} else {
return String(param);
}
}
function extractIntFromParam(param, defaultValue) {
if (isUndefined(param)) {
return defaultValue;
} else {
try {
var value = parseInt(param, 10);
return isNaN(value) ? defaultValue : value;
} catch (ex) {
logLog.warn("Invalid int param " + param, ex);
return defaultValue;
}
}
}
function extractFunctionFromParam(param, defaultValue) {
if (typeof param == "function") {
return param;
} else {
return defaultValue;
}
}
/* --------------------------------------------------------------------- */
// Simple logging for log4javascript itself
var logLog = {
quietMode: false,
setQuietMode: function(quietMode) {
this.quietMode = bool(quietMode);
},
numberOfErrors: 0,
alertAllErrors: false,
setAlertAllErrors: function(alertAllErrors) {
this.alertAllErrors = alertAllErrors;
},
debug: function(message, exception) {
},
warn: function(message, exception) {
},
error: function(message, exception) {
if (++this.numberOfErrors == 1 || this.alertAllErrors) {
if (!this.quietMode) {
var alertMessage = "log4javascript error: " + message;
if (exception) {
alertMessage += newLine + newLine + "Original error: " + getExceptionStringRep(exception);
}
alert(alertMessage);
}
}
}
};
log4javascript.logLog = logLog;
/* --------------------------------------------------------------------- */
var errorListeners = [];
log4javascript.addErrorListener = function(listener) {
if (typeof listener == "function") {
errorListeners.push(listener);
} else {
handleError("addErrorListener: listener supplied was not a function");
}
};
log4javascript.removeErrorListener = function(listener) {
array_remove(errorListeners, listener);
};
function handleError(message, exception) {
logLog.error(message, exception);
for (var i = 0; i < errorListeners.length; i++) {
errorListeners[i](message, exception);
}
}
/* --------------------------------------------------------------------- */
var enabled = (typeof log4javascript_disabled != "undefined") &&
log4javascript_disabled ? false : true;
log4javascript.setEnabled = function(enable) {
enabled = bool(enable);
};
log4javascript.isEnabled = function() {
return enabled;
};
// This evaluates the given expression in the current scope, thus allowing
// scripts to access private variables. Particularly useful for testing
log4javascript.evalInScope = function(expr) {
return eval(expr);
};
var showStackTraces = false;
log4javascript.setShowStackTraces = function(show) {
showStackTraces = bool(show);
};
/* --------------------------------------------------------------------- */
function Logger(name) {
this.name = name;
var appenders = [];
var loggerLevel = Level.DEBUG;
// Create methods that use the appenders variable in this scope
this.addAppender = function(appender) {
if (appender instanceof log4javascript.Appender) {
appenders.push(appender);
} else {
handleError("Logger.addAppender: appender supplied is not a subclass of Appender");
}
};
this.removeAppender = function(appender) {
array_remove(appenders, appender);
};
this.removeAllAppenders = function(appender) {
appenders.length = 0;
};
this.log = function(level, message, exception) {
if (level.isGreaterOrEqual(loggerLevel)) {
var loggingEvent = new LoggingEvent(
this, new Date(), level, message, exception);
for (var i = 0; i < appenders.length; i++) {
appenders[i].doAppend(loggingEvent);
}
}
};
this.setLevel = function(level) {
loggerLevel = level;
};
this.getLevel = function() {
return loggerLevel;
};
}
Logger.prototype = {
trace: function(message, exception) {
this.log(Level.TRACE, message, exception);
},
debug: function(message, exception) {
this.log(Level.DEBUG, message, exception);
},
info: function(message, exception) {
this.log(Level.INFO, message, exception);
},
warn: function(message, exception) {
this.log(Level.WARN, message, exception);
},
error: function(message, exception) {
this.log(Level.ERROR, message, exception);
},
fatal: function(message, exception) {
this.log(Level.FATAL, message, exception);
}
};
Logger.prototype.trace.isEntryPoint = true;
Logger.prototype.debug.isEntryPoint = true;
Logger.prototype.info.isEntryPoint = true;
Logger.prototype.warn.isEntryPoint = true;
Logger.prototype.error.isEntryPoint = true;
Logger.prototype.fatal.isEntryPoint = true;
/* --------------------------------------------------------------------- */
// Hashtable of loggers keyed by logger name
var loggers = {};
log4javascript.getLogger = function(loggerName) {
// Use default logger if loggerName is not specified or invalid
if (!(typeof loggerName == "string")) {
loggerName = "[anonymous]";
}
// Create the logger for this name if it doesn't already exist
if (!loggers[loggerName]) {
loggers[loggerName] = new Logger(loggerName);
}
return loggers[loggerName];
};
var defaultLogger = null;
log4javascript.getDefaultLogger = function() {
if (!defaultLogger) {
defaultLogger = log4javascript.getLogger("[default]");
var a = new log4javascript.PopUpAppender();
defaultLogger.addAppender(a);
}
return defaultLogger;
};
var nullLogger = null;
log4javascript.getNullLogger = function() {
if (!nullLogger) {
nullLogger = log4javascript.getLogger("[null]");
}
return nullLogger;
};
/* --------------------------------------------------------------------- */
var Level = function(level, name) {
this.level = level;
this.name = name;
};
Level.prototype = {
toString: function() {
return this.name;
},
equals: function(level) {
return this.level == level.level;
},
isGreaterOrEqual: function(level) {
return this.level >= level.level;
}
};
Level.ALL = new Level(Number.MIN_VALUE, "ALL");
Level.TRACE = new Level(10000, "TRACE");
Level.DEBUG = new Level(20000, "DEBUG");
Level.INFO = new Level(30000, "INFO");
Level.WARN = new Level(40000, "WARN");
Level.ERROR = new Level(50000, "ERROR");
Level.FATAL = new Level(60000, "FATAL");
Level.OFF = new Level(Number.MAX_VALUE, "OFF");
log4javascript.Level = Level;
/* --------------------------------------------------------------------- */
var LoggingEvent = function(logger, timeStamp, level, message,
exception) {
this.logger = logger;
this.timeStamp = timeStamp;
this.timeStampInSeconds = Math.floor(timeStamp.getTime() / 1000);
this.level = level;
this.message = message;
this.exception = exception;
};
LoggingEvent.prototype.getThrowableStrRep = function() {
return this.exception ?
getExceptionStringRep(this.exception) : "";
};
log4javascript.LoggingEvent = LoggingEvent;
/* --------------------------------------------------------------------- */
// Layout "abstract class"
var Layout = function() {
};
Layout.prototype = {
defaults: {
loggerKey: "logger",
timeStampKey: "timestamp",
levelKey: "level",
messageKey: "message",
exceptionKey: "exception",
urlKey: "url"
},
loggerKey: "logger",
timeStampKey: "timestamp",
levelKey: "level",
messageKey: "message",
exceptionKey: "exception",
urlKey: "url",
batchHeader: "",
batchFooter: "",
batchSeparator: "",
format: function(loggingEvent) {
handleError("Layout.format: layout supplied has no format() method");
},
ignoresThrowable: function() {
handleError("Layout.ignoresThrowable: layout supplied has no ignoresThrowable() method");
},
getContentType: function() {
return "text/plain";
},
allowBatching: function() {
return true;
},
getDataValues: function(loggingEvent) {
var dataValues = [
[this.loggerKey, loggingEvent.logger.name],
[this.timeStampKey, loggingEvent.timeStampInSeconds],
[this.levelKey, loggingEvent.level.name],
[this.urlKey, window.location.href],
[this.messageKey, loggingEvent.message]
];
if (loggingEvent.exception) {
dataValues.push([this.exceptionKey, getExceptionStringRep(loggingEvent.exception)]);
}
if (this.hasCustomFields()) {
for (var i = 0; i < this.customFields.length; i++) {
dataValues.push([this.customFields[i].name, this.customFields[i].value]);
}
}
return dataValues;
},
setKeys: function(loggerKey, timeStampKey, levelKey, messageKey,
exceptionKey, urlKey) {
this.loggerKey = extractStringFromParam(loggerKey, this.defaults.loggerKey);
this.timeStampKey = extractStringFromParam(timeStampKey, this.defaults.timeStampKey);
this.levelKey = extractStringFromParam(levelKey, this.defaults.levelKey);
this.messageKey = extractStringFromParam(messageKey, this.defaults.messageKey);
this.exceptionKey = extractStringFromParam(exceptionKey, this.defaults.exceptionKey);
this.urlKey = extractStringFromParam(urlKey, this.defaults.urlKey);
},
setCustomField: function(name, value) {
var fieldUpdated = false;
for (var i = 0; i < this.customFields.length; i++) {
if (this.customFields[i].name === name) {
this.customFields[i].value = value;
fieldUpdated = true;
}
}
if (!fieldUpdated) {
this.customFields.push({"name": name, "value": value});
}
},
hasCustomFields: function() {
return (this.customFields.length > 0);
}
};
log4javascript.Layout = Layout;
/* --------------------------------------------------------------------- */
// SimpleLayout
var SimpleLayout = function() {
this.customFields = [];
};
SimpleLayout.prototype = new Layout();
SimpleLayout.prototype.format = function(loggingEvent) {
return loggingEvent.level.name + " - " + loggingEvent.message;
};
SimpleLayout.prototype.ignoresThrowable = function(loggingEvent) {
return true;
};
log4javascript.SimpleLayout = SimpleLayout;
/* --------------------------------------------------------------------- */
// NullLayout
var NullLayout = function() {
this.customFields = [];
};
NullLayout.prototype = new Layout();
NullLayout.prototype.format = function(loggingEvent) {
return loggingEvent.message;
};
NullLayout.prototype.ignoresThrowable = function(loggingEvent) {
return true;
};
log4javascript.NullLayout = NullLayout;
/* --------------------------------------------------------------------- */
// XmlLayout
var XmlLayout = function() {
this.customFields = [];
};
XmlLayout.prototype = new Layout();
XmlLayout.prototype.getContentType = function() {
return "text/xml";
};
XmlLayout.prototype.escapeCdata = function(str) {
return str.replace(/\]\]>/, "]]>]]>" + newLine + "" + newLine;
if (this.hasCustomFields()) {
for (var i = 0; i < this.customFields.length; i++) {
str += "" + newLine;
}
}
if (loggingEvent.exception) {
str += "" + newLine;
}
str += "" + newLine + newLine;
return str;
};
XmlLayout.prototype.ignoresThrowable = function(loggingEvent) {
return false;
};
log4javascript.XmlLayout = XmlLayout;
/* --------------------------------------------------------------------- */
// JsonLayout
var JsonLayout = function(readable, loggerKey, timeStampKey,
levelKey, messageKey, exceptionKey, urlKey) {
this.readable = bool(readable);
this.batchHeader = this.readable ? "[" + newLine : "[";
this.batchFooter = this.readable ? "]" + newLine : "]";
this.batchSeparator = this.readable ? "," + newLine : ",";
this.setKeys(loggerKey, timeStampKey, levelKey, messageKey,
exceptionKey, urlKey);
this.propertySeparator = this.readable ? ", " : ",";
this.colon = this.readable ? ": " : ":";
this.customFields = [];
};
JsonLayout.prototype = new Layout();
JsonLayout.prototype.setReadable = function(readable) {
this.readable = bool(readable);
};
JsonLayout.prototype.isReadable = function() {
return this.readable;
};
JsonLayout.prototype.format = function(loggingEvent) {
var dataValues = this.getDataValues(loggingEvent);
var str = "{";
if (this.readable) {
str += newLine;
}
for (var i = 0; i < dataValues.length; i++) {
if (this.readable) {
str += "\t";
}
// Check the type of the data value to decide whether quotation marks
// are required
var valType = typeof dataValues[i][1];
var val = (valType != "number" && valType != "boolean") ?
"\"" + escapeNewLines(dataValues[i][1].toString().replace(/\"/g, "\\\"")) + "\"" :
dataValues[i][1];
str += "\"" + dataValues[i][0] + "\"" + this.colon + val;
if (i < dataValues.length - 1) {
str += this.propertySeparator;
}
if (this.readable) {
str += newLine;
}
}
str += "}";
if (this.readable) {
str += newLine;
}
return str;
};
JsonLayout.prototype.ignoresThrowable = function(loggingEvent) {
return false;
};
log4javascript.JsonLayout = JsonLayout;
/* --------------------------------------------------------------------- */
// HttpPostDataLayout
var HttpPostDataLayout = function(loggerKey, timeStampKey,
levelKey, messageKey, exceptionKey, urlKey) {
this.setKeys(loggerKey, timeStampKey, levelKey, messageKey,
exceptionKey, urlKey);
this.customFields = [];
};
HttpPostDataLayout.prototype = new Layout();
// Disable batching
HttpPostDataLayout.prototype.allowBatching = function() {
return false;
};
HttpPostDataLayout.prototype.format = function(loggingEvent) {
var dataValues = this.getDataValues(loggingEvent);
var queryBits = [];
for (var i = 0; i < dataValues.length; i++) {
queryBits.push(urlEncode(dataValues[i][0]) + "=" + urlEncode(dataValues[i][1]));
}
return queryBits.join("&");
};
HttpPostDataLayout.prototype.ignoresThrowable = function(loggingEvent) {
return false;
};
log4javascript.HttpPostDataLayout = HttpPostDataLayout;
/* --------------------------------------------------------------------- */
// PatternLayout
var PatternLayout = function(pattern) {
if (pattern) {
this.pattern = pattern;
} else {
this.pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
}
this.customFields = [];
};
PatternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
PatternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n";
PatternLayout.ISO8601_DATEFORMAT = "yyyy-MM-dd HH:mm:ss,SSS";
PatternLayout.DATETIME_DATEFORMAT = "dd MMM yyyy HH:mm:ss,SSS";
PatternLayout.ABSOLUTETIME_DATEFORMAT = "HH:mm:ss,SSS";
PatternLayout.prototype = new Layout();
PatternLayout.prototype.format = function(loggingEvent) {
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdfmMnpr%])(\{([^\}]+)\})?|([^%]+)/;
var formattedString = "";
var result;
var searchString = this.pattern;
// Cannot use regex global flag since it doesn't work with exec in IE5
while ((result = regex.exec(searchString))) {
var matchedString = result[0];
var padding = result[1];
var truncation = result[2];
var conversionCharacter = result[3];
var specifier = result[5];
var text = result[6];
// Check if the pattern matched was just normal text
if (text) {
formattedString += "" + text;
} else {
// Create a raw replacement string based on the conversion
// character and specifier
var replacement = "";
switch(conversionCharacter) {
case "c": // Logger name
var loggerName = loggingEvent.logger.name;
if (specifier) {
var precision = parseInt(specifier, 10);
var loggerNameBits = loggingEvent.logger.name.split(".");
if (precision >= loggerNameBits.length) {
replacement = loggerName;
} else {
replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
}
} else {
replacement = loggerName;
}
break;
case "d": // Date
var dateFormat = PatternLayout.ISO8601_DATEFORMAT;
if (specifier) {
dateFormat = specifier;
// Pick up special cases
if (dateFormat == "ISO8601") {
dateFormat = PatternLayout.ISO8601_DATEFORMAT;
} else if (dateFormat == "ABSOLUTE") {
dateFormat = PatternLayout.ABSOLUTETIME_DATEFORMAT;
} else if (dateFormat == "DATE") {
dateFormat = PatternLayout.DATETIME_DATEFORMAT;
}
}
// Format the date
replacement = (new SimpleDateFormat(dateFormat)).format(loggingEvent.timeStamp);
break;
case "f": // Custom field
if (this.hasCustomFields()) {
var fieldIndex = 0;
if (specifier) {
fieldIndex = parseInt(specifier, 10);
if (isNaN(fieldIndex)) {
handleError("PatternLayout.format: invalid specifier '" +
specifier + "' for conversion character 'f' - should be a number");
} else if (fieldIndex === 0) {
handleError("PatternLayout.format: invalid specifier '" +
specifier + "' for conversion character 'f' - must be greater than zero");
} else if (fieldIndex > this.customFields.length) {
handleError("PatternLayout.format: invalid specifier '" +
specifier + "' for conversion character 'f' - there aren't that many custom fields");
} else {
fieldIndex = fieldIndex - 1;
}
}
replacement = this.customFields[fieldIndex].value;
}
break;
case "m": // Message
if (specifier) {
var depth = parseInt(specifier, 10);
if (isNaN(depth)) {
handleError("PatternLayout.format: invalid specifier '" +
specifier + "' for conversion character 'm' - should be a number");
replacement = loggingEvent.message;
} else {
replacement = formatObjectExpansion(loggingEvent.message, depth);
}
} else {
replacement = loggingEvent.message;
}
break;
case "n": // New line
replacement = newLine;
break;
case "p": // Level
replacement = loggingEvent.level.name;
break;
case "r": // Milliseconds since log4javascript startup
replacement = "" + loggingEvent.timeStamp.getDifference(applicationStartDate);
break;
case "%": // Literal % sign
replacement = "%";
break;
default:
replacement = matchedString;
break;
}
// Format the replacement according to any padding or
// truncation specified
var len;
// First, truncation
if (truncation) {
len = parseInt(truncation.substr(1), 10);
var strLen = replacement.length;
if (len < strLen) {
replacement = replacement.substring(strLen - len, strLen);
}
}
// Next, padding
if (padding) {
if (padding.charAt(0) == "-") {
len = parseInt(padding.substr(1), 10);
// Right pad with spaces
while (replacement.length < len) {
replacement += " ";
}
} else {
len = parseInt(padding, 10);
// Left pad with spaces
while (replacement.length < len) {
replacement = " " + replacement;
}
}
}
formattedString += replacement;
}
searchString = searchString.substr(result.index + result[0].length);
}
return formattedString;
};
PatternLayout.prototype.ignoresThrowable = function(loggingEvent) {
return true;
};
log4javascript.PatternLayout = PatternLayout;
/* --------------------------------------------------------------------- */
// Appender "abstract class"
var Appender = function() {};
// Performs threshold checks before delegating actual logging to the
// subclass's specific append method.
Appender.prototype = {
layout: new PatternLayout(),
threshold: Level.ALL,
doAppend: function(loggingEvent) {
if (enabled && loggingEvent.level.level >= this.threshold.level) {
this.append(loggingEvent);
}
},
append: function(loggingEvent) {},
setLayout: function(layout) {
if (layout instanceof Layout) {
this.layout = layout;
} else {
handleError("Appender.setLayout: layout supplied to " +
this.toString() + " is not a subclass of Layout");
}
},
getLayout: function() {
return this.layout;
},
setThreshold: function(threshold) {
if (threshold instanceof Level) {
this.threshold = threshold;
} else {
handleError("Appender.setThreshold: threshold supplied to " +
this.toString() + " is not a subclass of Level");
}
},
getThreshold: function() {
return this.threshold;
},
toString: function() {
return "[Base Appender]";
}
};
log4javascript.Appender = Appender;
/* --------------------------------------------------------------------- */
// AlertAppender
var AlertAppender = function(layout) {
if (layout) {
this.setLayout(layout);
}
};
AlertAppender.prototype = new Appender();
AlertAppender.prototype.layout = new SimpleLayout();
AlertAppender.prototype.append = function(loggingEvent) {
var formattedMessage = this.getLayout().format(loggingEvent);
if (this.getLayout().ignoresThrowable()) {
formattedMessage += loggingEvent.getThrowableStrRep();
}
alert(formattedMessage);
};
AlertAppender.prototype.toString = function() {
return "[AlertAppender]";
};
log4javascript.AlertAppender = AlertAppender;
/* --------------------------------------------------------------------- */
// AjaxAppender
var AjaxAppender = function(url, layout, timed, waitForResponse,
batchSize, timerInterval, requestSuccessCallback, failCallback) {
var appender = this;
var isSupported = true;
if (!url) {
handleError("AjaxAppender: URL must be specified in constructor");
isSupported = false;
}
timed = extractBooleanFromParam(timed, this.defaults.timed);
waitForResponse = extractBooleanFromParam(waitForResponse, this.defaults.waitForResponse);
batchSize = extractIntFromParam(batchSize, this.defaults.batchSize);
timerInterval = extractIntFromParam(timerInterval, this.defaults.timerInterval);
requestSuccessCallback = extractFunctionFromParam(requestSuccessCallback, this.defaults.requestSuccessCallback);
failCallback = extractFunctionFromParam(failCallback, this.defaults.failCallback);
var sessionId = null;
var queuedLoggingEvents = [];
var queuedRequests = [];
var sending = false;
var initialized = false;
// Configuration methods. The function scope is used to prevent
// direct alteration to the appender configuration properties.
function checkCanConfigure(configOptionName) {
if (initialized) {
handleError("AjaxAppender: configuration option '" + configOptionName + "' may not be set after the appender has been initialized");
return false;
}
return true;
}
this.getSessionId = function() { return sessionId; };
this.setSessionId = function(sessionIdParam) {
sessionId = extractStringFromParam(sessionIdParam, null);
this.layout.setCustomField("sessionid", sessionId);
};
this.setLayout = function(layout) {
if (checkCanConfigure("layout")) {
this.layout = layout;
// Set the session id as a custom field on the layout, if not already present
if (sessionId !== null) {
this.setSessionId(sessionId);
}
}
};
if (layout) {
this.setLayout(layout);
}
this.isTimed = function() { return timed; };
this.setTimed = function(timedParam) {
if (checkCanConfigure("timed")) {
timed = bool(timedParam);
}
};
this.getTimerInterval = function() { return timerInterval; };
this.setTimerInterval = function(timerIntervalParam) {
if (checkCanConfigure("timerInterval")) {
timerInterval = extractIntFromParam(timerIntervalParam, timerInterval);
}
};
this.isWaitForResponse = function() { return waitForResponse; };
this.setWaitForResponse = function(waitForResponseParam) {
if (checkCanConfigure("waitForResponse")) {
waitForResponse = bool(waitForResponseParam);
}
};
this.getBatchSize = function() { return batchSize; };
this.setBatchSize = function(batchSizeParam) {
if (checkCanConfigure("batchSize")) {
batchSize = extractIntFromParam(batchSizeParam, batchSize);
}
};
this.setRequestSuccessCallback = function(requestSuccessCallbackParam) {
requestSuccessCallback = extractFunctionFromParam(requestSuccessCallbackParam, requestSuccessCallback);
};
this.setFailCallback =