Click here to Skip to main content
15,867,594 members
Articles / Web Development / ASP.NET

Inserting loggers in your JavaScript with JSNLog

Rate me:
Please Sign up or sign in to vote.
4.93/5 (9 votes)
4 Apr 2014CPOL13 min read 28.5K   21  
Shows how to insert loggers in your JavaScript with JSNLog, a JavaScript logging library that integrates well with your .Net site

Shows how to insert loggers in your JavaScript with JSNLog, a JavaScript logging library that is well integrated with .Net. It lets you configure loggers in your web.config. And it lets you receive log messages from the client and store them on the server, without any coding.

Contents of this series

Contents of this article

JL Namespace

Logger Object

Exception Object

JL Namespace

Lets you create loggers and configure the JSNLog library itself.

JL Method

Creates and retrieves loggers

Definition

JL(loggerName?: string): Logger
Parameters
loggerName
Name of the logger, or empty for the root logger.
Return Value

The logger with the given name, or the root logger if no name is given. If the logger didn't exist, it will be created.

Remarks

In your server side logs, the root logger is called ClientRoot.

Examples

This returns the logger with name "a.b".

var logger = JL('a.b');

This returns the root logger.

var rootlogger = JL();

setOptions Method

Sets library wide options

Definition

setOptions(options: any): void
Parameters
options
A JSON object with options. See the Remarks sections below.
Return Value

The itself.

Remarks

The JSON object can have the following fields:

Field Type Default Description
enabled
optional
bool true If false, all loggers are disabled.
maxMessages
optional
number no maximum Limits total number of messages sent to the server. See remarks below.
requestId
optional
string (empty) Sent with all log messages to the server, so make it easier to identify all log messages for a given request. Reported via the %requestId placeholder of the serverSideMessageFormat attribute of the <jsnlog> Element.
maxMessages and buffering

You use maxMessages to limit the number of messages sent to the server. When you set maxMessages via a call to setOptions, a counter is set to maxMessages. Each time messages are sent to the server, that counter is decremented by the number of messages sent. When the counter gets to zero or below, no more messages will be sent.

However, this is affected by batching and buffering.

Take a situation where maxMessages is set to 5 and 2 messages have already been sent - so the message counter is now 3. If 8 messages had been stored in a buffer and those messages are now sent, they will be all sent. That means the server will receive a total of 2 + 8 = 10 messages. After this, no more messages will be sent, because the number of messages sent (10) exceeds maxMessages (5).

This means that maxMessages is not a precise limit on the number of messages sent to the server. On the other hand, buffered messages are sent together in a single request to the server, minimizing bandwidth. And buffered messages are often useful in solving exceptions, so there is value in receiving them.

Examples

This sets the requestId to be sent with all log messages to the server.

JL.setOptions({
    "requestId": "35F7416D-86F1-47FA-A9EC-547FFF510086"
});

Logger Object

trace Method

Creates a log item with severity TRACE

Definition

trace<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity TRACE.

JL().trace<text>("log message");</text>

debug Method

Creates a log item with severity DEBUG

Definition

debug<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity DEBUG.

JL().debug<text>("log message");</text>

info Method

Creates a log item with severity INFO

Definition

info<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity INFO.

JL().info<text>("log message");</text>

warn Method

Creates a log item with severity WARN

Definition

warn<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity WARN.

JL().warn<text>("log message");</text>

error Method

Creates a log item with severity ERROR

Definition

error<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity ERROR.

JL().error<text>("log message");</text>

fatal Method

Creates a log item with severity FATAL

Definition

fatal<text>(logObject: any): Logger</text>
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
Return Value

The Logger itself.

Examples

This creates a log message "log message" with severity FATAL.

JL().fatal<text>("log message");</text>

fatalException Method

Creates a log item with severity FATAL containing a message and an exception

Definition

fatalException(logObject: any, e: any): Logger
Parameters
logObject
String or object to be logged, or a function that returns the string or object to be logged (details).
e
Exception that will be logged along with the logObject. On Chrome, Firefox and IE10 and higher, a stack trace will be logged as well. However, see the Remarks below.
Return Value

The Logger itself.

Remarks

The fatalException function will log the stack trace of the exception, showing where in your code the exception occurred) under these circumstances:

  • The exception was thrown by the browser, such as when trying to read an undefined variable:
    try {
        // Browser throws exception
        i.dont.exist = 666;
    } catch(e) {
        // Logs stack trace
        JL().fatalException("Exception was thrown!", e);
    }
  • You threw an Error Object or an Exception Object<text>:
    try {
        // Throwing Error object
        throw new Error("Whoops!");
    } catch(e) {
        // Logs stack trace
        JL().fatalException("Exception was thrown!", e);
    }

The fatalException function will not log the stack trace if you throw something that is not an Error Object or Exception Object:

try {
    // Throwing something that is not an Error Object or 
    // Exception Object (in this case a string)
    throw "Whoops!";
} catch(e) {
    // Does not log stack trace
    JL().fatalException("Exception was thrown!", e);
}

The Exception Object is built into JSNLog. It opens the door to much better exception handling. The details are here<text>.

Examples

This code catches any exceptions and logs them.

try {
    ...
}
catch (e) {
    JL().fatalException("Exception was thrown!", e);
}

This code catches any exceptions and logs them along with the values of some variables to make debugging easier.

function f1(i, j) {
    try {
        ...
    }
    catch (e) {
        JL('f1').fatalException({ "i": i, "j": j}, e);
    }
}

setOptions Method

Sets options for a logger

Definition

setOptions(options: any): Logger
Parameters
options
A JSON object with options. See the Remarks sections below.
Return Value

The Logger itself.

Remarks

The JSON object can have the following fields:

Field Type Default Description
level
optional
number (inherited from parent logger) Only log messages with a severity equal or higher than this can be sent to the server.
userAgentRegex
optional
regular expression (inherited from parent logger) If not empty, log messages only get processed if this regular expression matches the user agent string of the browser.
ipRegex
optional
regular expression (inherited from parent logger) If not empty, log messages only get processed if this regular expression matches the IP address of the browser. If you use this, be sure to set the IP address via the setOptions method of the JL object.
disallow
optional
regular expression (inherited from parent logger) If not empty, log messages are suppressed if they match this regular expression. If an object is being logged, it is converted to a JSON string, which is then matched.
appenders
optional
array of appenders (inherited from parent logger) One or more appenders for the logger to send its log messages to. See the examples.
onceOnly
optional
array of strings (inherited from parent logger) One or more regular expressions. When a message matches a regular expression, then any subsequent messages matching that same regular expression will be suppressed. See the remarks and examples.
Logger names and option inheritance

Loggers not only get their options through the setOptions method, but also through inheritance. This is based on the name of each logger.

Assume that you have a method "method1" in a namespace "namespace1". Then it would make sense to use a naming scheme for you loggers like this: "namespace1.method1.logger1", "namespace1.method1.logger2", etc. This way, there are no name clashes and it makes keeping track of your loggers easy.

Just as a namespace may contain methods, and a method may contain loggers, so you can think of these logger names as making up a hierarchy:

  • The parent of "namespace1.method1.logger1" is "namespace1.method1";
  • The parent of "namespace1.method1" is "namespace1";
  • The parent of "namespace1" is the root logger (the logger without a name).

You're not limited to just 3 levels, you can have as many as want.

If you don't set an option using the setOptions method<text>, the logger inherits that option from its parent. If you do not use setOptions method at all, every logger will have the same options as the root logger.

Root logger and default appender

When the library loads, it creates the root logger. It also creates a default appender for use by the root logger.

Because every logger inherits from the root logger (unless you override this with the setOptions method), you can start logging right away without having to create an appender.

The root logger is created with these options:

Option Default Value
level DEBUG
userAgentRegex (empty)
ipRegex (empty)
disallow (empty)
appenders (default appender)

Note that because the default level for root logger is DEBUG, by default only log messages with severity DEBUG or higher get processed.

You can change the options used with the root logger in the same way as any other logger, using the setOptions method. See the examples below.

Option Default Value
level TRACE
userAgentRegex (empty)
ipRegex (empty)
disallow (empty)
storeInBufferLevel ALL
sendWithBufferLevel OFF
bufferSize 0
batchSize 1
url jsnlog.logger
Suppressing duplicate messages with onceOnly

You may have loggers inside code that gets called multiple times. As a result, you may get a series of messages that are essentially the same. Using onceOnly, you can suppress the duplicate messages, so only the first message is sent to the server.

This works by setting one or more regular expressions. When a log message matches one of the regular expressions, the logger remembers that there has been a message that matched that regular expression. Then when another message arrives that matches that same regular expression, it is suppressed.

For example, if you receive these messages:

Parameter x too high - x = 5
Parameter x too high - x = 6
Parameter x too high - x = 7
...
Parameter x too high - x = 49
Parameter x too high - x = 50

Then you can use the regular expression:

Parameter x too high - x =

To only receive the very first message:

Parameter x too high - x = 5

See the examples for how to set the regular expression.

You can set multiple regular expressions. These work independently. So if a message matches the first regular expression, then if a second message matches the second regular expression but not the first, then the second message gets through because it is not a duplicate of the first message.

As shown here, you can log not only strings but also objects. If you log an object, the object is translated to a JSON string. That string is than matched against the regular expressions.

Similar to other attributes, loggers inherit onceOnly from their parents. However, this is all or nothing. If you set onceOnly regular expressions for a logger, than any onceOnly regular expressions that its parent may have had are disregarded.

Examples

This sets the level of logger "a.b" to 3000.

var logger = JL("a.b");
logger.setOptions({
    "level": 3000
});

This sets the level of the root logger.

var rootlogger = JL();
rootlogger.setOptions({
    "level": 3000
});

This sets the level of logger "a.b" to INFO (which is the same as setting it to 3000). This code shows you don't need to use the variable logger.

JL("a.b").setOptions({
    "level": JL.getInfoLevel()
});

This sets the level of logger "a.b" to OFF, so it is completely switched off.

JL("a.b").setOptions({
    "level": JL.getOffLevel()
});

This sets the level to 4000. It also disables the logger for all browsers, except those whose user agent string contains MSIE 7|MSIE 8 (that is, it is version 7 or 8 of Internet Explorer).

var logger = JL("a.b");
logger.setOptions({
    "level": 4000,
    "userAgentRegex": "MSIE 7|MSIE 8"
});

This creates an appender "appender" and then tells the logger "a.b" to send all log messages to it.

var appender=JL.createAjaxAppender('appender');
var logger = JL("a.b");
logger.setOptions({
    "appenders": [appender]
});

This creates an AjaxAppender and a ConsoleAppender and then tells the logger "mylogger" to send all log messages to them both.

var ajaxAppender=JL.createAjaxAppender('ajaxAppender');
var consoleAppender=JL.createConsoleAppender('consoleAppender');
JL("mylogger").setOptions({"appenders": [ajaxAppender,consoleAppender]});

Suppress duplicate messages that match the regular expression "Parameter x too high - x =".

JL("a").setOptions({
    "onceOnly": [ "Parameter x too high - x =" ]
});

Suppress duplicate messages that match the regular expression "Parameter x too high - x =". Also suppress duplicate messages that match "x = \d+ and y = \d+".

JL("a").setOptions({
    "onceOnly": [ "Parameter x too high - x =", "x = \\d+ and y = \\d+" ]
});

Loggers inherit onceOnly from their parents. Assume you have a logger "a.b" whose parent "a" suppresses duplicates, but you want logger "a.b" to not suppress duplicates. To make that happen, give it a onceOnly collection without any regular expressions:

JL("a.b").setOptions({
    "onceOnly": [ ]
});

log Method

Creates a log item

Definition

log(level: number, logObject: any): Logger

Parameters

level
Severity of the message to be logged.
logObject
String or object to be logged, or a function that returns the string or object to be logged. See remarks.

Return Value

The Logger itself.

Remarks

You are not restricted to simply logging strings. You can log objects, arrays, dates, numbers, booleans and even regular expressions. These are all converted to a string before being logged.

If producing the log information is expensive, than you will only want to do this if the information will actually be logged - that is, the severity of the logged information exceeds the logger's level, etc.

To solve this, you can pass in a function rather than the information itself. This function has to return the information. It will only be called if the log information will actually be logged.

You can even have the function return another function that returns the actual information - or another function, etc. Do make sure there are no circular references in this chain, otherwise you will get a stack overflow.

Examples

This creates a log message "log message" with severity 2500.

JL().log(2500, "log message");

This logs an object.

var obj = {"f1": "v1", "f2": "v2"};
JL().log(2500, obj);

This passes in a function that produces the log information. The function is only called if the log information will actually be logged.

JL().log(2500, function() { return "log message"; });

Exception Object

Allows you to create custom exceptions that hold a JSON object and an inner exception.

Remarks

Similar to the standard Error object, the Exception object allows you to throw a custom exception when things go wrong:

function f(i, j) {
    if (i < 10) { 
        throw new JL.Exception("i is too small!");
    }
    ...
}

Add information to aid in debugging

However, instead of a string, you can also pass in a JSON object with more information, to help you fix the issue:

function f(i, j) {
    if (i < 10) { 
        throw new JL.Exception({
            "message": "i is too small!",
            "i": i,
            "j": j    
        });
    }
    ...
}

Add inner exceptions

The Exception object supports inner exceptions - essentially storing one exception within another. To see how this works, consider this code:

function f2(v) {
    var x, y;
    ... some code
    if (somethingWentWrong) {
        throw new JL.Exception({ "x": x, "y": y, "v": v });
    }
}

function f1(anArray) {
    var i;    
    for(i = 0; i < anArray.length; i++) {
        f2(anArray[i]);
    }
}

try {
    f1([1, 2, 3]);
} catch(e) {
    JL().fatalException("Exception was thrown!", e);
}

Function f2 can throw an exception. If it does, it puts all relevant information in the exception to make fixing the problem easier.

However, function f1 also has information that might be relevant, such as the value of the index i. It would be good to somehow add that information to the exception.

It would be possible to catch the exception in function f1 and then throw a new exception that contains all the information stored by function f2 plus the index i. But that would be complicated, and we'd lose the stack trace of the original exception.

The easiest solution is to catch the exception in function f2, and then throw a new one that contains the original exception, plus any additional information. That way, we get to keep all the information stored in the original exception, including its stack trace. The changes are below in red:

function f2(v) {
    var x, y;
    ... some code
    if (somethingWentWrong) {
        throw new JL.Exception({ "x": x, "y": y, "v": v });
    }
}

function f1(anArray) {
    var i;    
                try {
        for(i = 0; i < anArray.length; i++) {
            f2(anArray[i]);
        }
                } catch(e) {
        // Throw new exception that contains the original one
        throw new JL.Exception({ "i": i }, e);
    }
}

try {
    f1([1, 2, 3]);
} catch(e) {
    JL().fatalException("Exception was thrown!", e);
}

The fatalException function knows how to read the additional information and the inner exception from the Exception object, and will log it all.

If you want to, there is nothing stopping you from having an inner exception that itself has an inner exception. You can go as deep as you want.

Next part

In this part 2 about JSNLog, you saw in detail all JavaScript functions provided by JSNLog. The next part, will show all elements and attributes you can use in your web.config to configure your JavaScript loggers.

If you like this article, please vote for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Australia Australia
Twitter: @MattPerdeck
LinkedIn: au.linkedin.com/in/mattperdeck
Current project: JSNLog JavaScript Logging Package

Matt has over 9 years .NET and SQL Server development experience. Before getting into .Net, he worked on a number of systems, ranging from the largest ATM network in The Netherlands to embedded software in advanced Wide Area Networks and the largest ticketing web site in Australia. He has lived and worked in Australia, The Netherlands, Slovakia and Thailand.

He is the author of the book ASP.NET Performance Secrets (www.amazon.com/ASP-NET-Site-Performance-Secrets-Perdeck/dp/1849690685) in which he shows in clear and practical terms how to quickly find the biggest bottlenecks holding back the performance of your web site, and how to then remove those bottlenecks. The book deals with all environments affecting a web site - the web server, the database server and the browser.

Matt currently lives in Sydney, Australia. He recently worked at Readify and the global professional services company PwC. He now works at SP Health, a global provider of weight loss web sites such at CSIRO's TotalWellBeingDiet.com and BiggestLoserClub.com.

Comments and Discussions

 
-- There are no messages in this forum --