///
import JSNLogAppender = JL.JSNLogAppender
import JSNLogAppenderOptions = JL.JSNLogAppenderOptions
import JSNLogAjaxAppender = JL.JSNLogAjaxAppender
import JSNLogAjaxAppenderOptions = JL.JSNLogAjaxAppenderOptions
import JSNLogConsoleAppender = JL.JSNLogConsoleAppender
import JSNLogFilterOptions = JL.JSNLogFilterOptions
import JSNLogLogger = JL.JSNLogLogger
import JSNLogLoggerOptions = JL.JSNLogLoggerOptions
import JSNLogOptions = JL.JSNLogOptions
function JL(loggerName?: string): JSNLogLogger
{
// If name is empty, return the root logger
if (!loggerName)
{
return JL.__;
}
// Implements Array.reduce. JSNLog supports IE8+ and reduce is not supported in that browser.
// Same interface as the standard reduce, except that
if (!Array.prototype.reduce)
{
Array.prototype.reduce = function (callback: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue?: any)
{
var previousValue = initialValue;
for (var i = 0; i < this.length; i++)
{
previousValue = callback(previousValue, this[i], i, this);
}
return previousValue;
};
}
var accumulatedLoggerName = '';
var logger: JL.Logger = ('.' + loggerName).split('.').reduce(
function (prev: JL.Logger, curr: string, idx: number, arr: string[])
{
// if loggername is a.b.c, than currentLogger will be set to the loggers
// root (prev: JL, curr: '')
// a (prev: JL.__, curr: 'a')
// a.b (prev: JL.__.__a, curr: 'b')
// a.b.c (prev: JL.__.__a.__a.b, curr: 'c')
// Note that when a new logger name is encountered (such as 'a.b.c'),
// a new logger object is created and added as a property to the parent ('a.b').
// The root logger is added as a property of the JL object itself.
// It is essential that the name of the property containing the child logger
// contains the full 'path' name of the child logger ('a.b.c') instead of
// just the bit after the last period ('c').
// This is because the parent inherits properties from its ancestors.
// So if the root has a child logger 'c' (stored in a property 'c' of the root logger),
// then logger 'a.b' has that same property 'c' through inheritance.
// The names of the logger properties start with __, so the root logger
// (which has name ''), has a nice property name '__'.
// accumulatedLoggerName evaluates false ('' is falsy) in first iteration when prev is the root logger.
// accumulatedLoggerName will be the logger name corresponding with the logger in currentLogger.
// Keep in mind that the currentLogger may not be defined yet, so can't get the name from
// the currentLogger object itself.
if (accumulatedLoggerName)
{
accumulatedLoggerName += '.' + curr;
} else
{
accumulatedLoggerName = curr;
}
var currentLogger = prev['__' + accumulatedLoggerName];
// If the currentLogger (or the actual logger being sought) does not yet exist,
// create it now.
if (currentLogger === undefined)
{
// Set the prototype of the Logger constructor function to the parent of the logger
// to be created. This way, __proto of the new logger object will point at the parent.
// When logger.level is evaluated and is not present, the JavaScript runtime will
// walk down the prototype chain to find the first ancestor with a level property.
//
// Note that prev at this point refers to the parent logger.
JL.Logger.prototype = prev;
currentLogger = new JL.Logger(accumulatedLoggerName);
prev['__' + accumulatedLoggerName] = currentLogger;
}
return currentLogger;
}, JL.__);
return logger;
}
module JL
{
export var enabled: boolean;
export var maxMessages: number;
export var defaultAjaxUrl: string;
export var clientIP: string;
export var defaultBeforeSend: any;
export var serialize: any;
// Initialise requestId to empty string. If you don't do this and the user
// does not set it via setOptions, then the JSNLog-RequestId header will
// have value "undefined", which doesn't look good in a log.
//
// Note that you always want to send a requestId as part of log requests,
// otherwise the server side component doesn't know this is a log request
// and may create a new request id for the log request, causing confusion
// in the log.
export var requestId: string = '';
// Number uniquely identifying every log entry within the request.
export var entryId: number = 0;
// Allow property injection of these classes, to enable unit testing
export var _createXMLHttpRequest = function () { return new XMLHttpRequest(); };
export var _getTime = function () { return (new Date).getTime(); };
export var _console = console;
// ----- private variables
export var _appenderNames: string[] = [];
/**
Copies the value of a property from one object to the other.
This is used to copy property values as part of setOption for loggers and appenders.
Because loggers inherit property values from their parents, it is important never to
create a property on a logger if the intent is to inherit from the parent.
Copying rules:
1) if the from property is undefined (for example, not mentioned in a JSON object), the
to property is not affected at all.
2) if the from property is null, the to property is deleted (so the logger will inherit from
its parent).
3) Otherwise, the from property is copied to the to property.
*/
function copyProperty(propertyName: string, from: any, to: any): void
{
if (from[propertyName] === undefined) { return; }
if (from[propertyName] === null) { delete to[propertyName]; return; }
to[propertyName] = from[propertyName];
}
/**
Returns true if a log should go ahead.
Does not check level.
@param filters
Filters that determine whether a log can go ahead.
*/
function allow(filters: JSNLogFilterOptions): boolean
{
// If enabled is not null or undefined, then if it is false, then return false
// Note that undefined==null (!)
if (!(JL.enabled == null))
{
if (!JL.enabled) { return false; }
}
// If the regex contains a bug, that will throw an exception.
// Ignore this, and pass the log item (better too much than too little).
try
{
if (filters.userAgentRegex)
{
if (!new RegExp(filters.userAgentRegex).test(navigator.userAgent)) { return false; }
}
} catch (e) { }
try
{
if (filters.ipRegex && JL.clientIP)
{
if (!new RegExp(filters.ipRegex).test(JL.clientIP)) { return false; }
}
} catch (e) { }
return true;
}
/**
Returns true if a log should go ahead, based on the message.
@param filters
Filters that determine whether a log can go ahead.
@param message
Message to be logged.
*/
function allowMessage(filters: JSNLogFilterOptions, message: string): boolean
{
// If the regex contains a bug, that will throw an exception.
// Ignore this, and pass the log item (better too much than too little).
try
{
if (filters.disallow)
{
if (new RegExp(filters.disallow).test(message)) { return false; }
}
} catch (e) { }
return true;
}
// If logObject is a function, the function is evaluated (without parameters)
// and the result returned.
// Otherwise, logObject itself is returned.
function stringifyLogObjectFunction(logObject: any): any
{
if (typeof logObject == "function")
{
if (logObject instanceof RegExp)
{
return logObject.toString();
}
else
{
return logObject();
}
}
return logObject;
}
class StringifiedLogObject
{
// * msg -
// if the logObject is a scalar (after possible function evaluation), this is set to
// string representing the scalar. Otherwise it is left undefined.
// * meta -
// if the logObject is an object (after possible function evaluation), this is set to
// that object. Otherwise it is left undefined.
// * finalString -
// This is set to the string representation of logObject (after possible function evaluation),
// regardless of whether it is an scalar or an object. An object is stringified to a JSON string.
// Note that you can't call this field "final", because as some point this was a reserved
// JavaScript keyword and using final trips up some minifiers.
constructor(public msg?: string, public meta?: any, public finalString?: string) { }
}
// Takes a logObject, which can be
// * a scalar
// * an object
// * a parameterless function, which returns the scalar or object to log.
// Returns a stringifiedLogObject
function stringifyLogObject(logObject: any): StringifiedLogObject
{
// Note that this works if logObject is null.
// typeof null is object.
// JSON.stringify(null) returns "null".
var actualLogObject = stringifyLogObjectFunction(logObject);
var finalString;
// Note that typeof actualLogObject should not be "function", because that has
// been resolved with stringifyLogObjectFunction.
switch (typeof actualLogObject)
{
case "string":
return new StringifiedLogObject(actualLogObject, null, actualLogObject);
case "number":
finalString = actualLogObject.toString();
return new StringifiedLogObject(finalString, null, finalString);
case "boolean":
finalString = actualLogObject.toString();
return new StringifiedLogObject(finalString, null, finalString);
case "undefined":
return new StringifiedLogObject("undefined", null, "undefined");
case "object":
if ((actualLogObject instanceof RegExp) ||
(actualLogObject instanceof String) ||
(actualLogObject instanceof Number) ||
(actualLogObject instanceof Boolean))
{
finalString = actualLogObject.toString();
return new StringifiedLogObject(finalString, null, finalString);
}
else
{
if (typeof JL.serialize === 'function') {
finalString = JL.serialize.call(this, actualLogObject);
} else {
finalString = JSON.stringify(actualLogObject);
}
// Set the msg field to "" instead of null. Some Winston transports
// assume that the msg field is not null.
return new StringifiedLogObject("", actualLogObject, finalString);
}
default:
return new StringifiedLogObject("unknown", null, "unknown");
}
}
export function setOptions(options: JSNLogOptions): void
{
copyProperty("enabled", options, this);
copyProperty("maxMessages", options, this);
copyProperty("defaultAjaxUrl", options, this);
copyProperty("clientIP", options, this);
copyProperty("requestId", options, this);
copyProperty("defaultBeforeSend", options, this);
copyProperty("serialize", options, this);
return this;
}
export function getAllLevel(): number { return -2147483648; }
export function getTraceLevel(): number { return 1000; }
export function getDebugLevel(): number { return 2000; }
export function getInfoLevel(): number { return 3000; }
export function getWarnLevel(): number { return 4000; }
export function getErrorLevel(): number { return 5000; }
export function getFatalLevel(): number { return 6000; }
export function getOffLevel(): number { return 2147483647; }
function levelToString(level: number): string
{
if (level <= 1000) { return "trace"; }
if (level <= 2000) { return "debug"; }
if (level <= 3000) { return "info"; }
if (level <= 4000) { return "warn"; }
if (level <= 5000) { return "error"; }
return "fatal";
}
// ---------------------
export class Exception
{
public name: string;
public message: string;
// data replaces message. It takes not just strings, but also objects and functions, just like the log function.
// internally, the string representation is stored in the message property (inherited from Error)
//
// inner: inner exception. Can be null or undefined.
constructor(data: any, public inner?: any)
{
this.name = "JL.Exception";
this.message = stringifyLogObject(data).finalString;
}
}
// Derive Exception from Error (a Host object), so browsers
// are more likely to produce a stack trace for it in their console.
//
// Note that instanceof against an object created with this constructor
// will return true in these cases:
//