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 {
i.dont.exist = 666;
} catch(e) {
JL().fatalException("Exception was thrown!", e);
}
- You threw an
Error Object
or an Exception Object<text>:
try {
throw new Error("Whoops!");
} catch(e) {
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 {
throw "Whoops!";
} catch(e) {
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 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.