JavaScript and ULS logging

JavaScript and ULS logging header image

Sometimes your applications rely heavily on using JavaScript or jQuery (or ofcourse some of the other cool libraries that are out there). Now that might be a good thing, all that JavaScript allows you to achieve pretty neat interfaces and a great experience for your users, however if something would fail somewhere along the line you might not know that it is failing. Ending up with users that are not experiencing the full monty having the risk of losing their interest. If you are writing your own services or own logic that provides the data you should be able to catch any exceptions from that code, but what if something is failing due to a certain browser, or for some other reason that will not show up in the usual logs, in the end JavaScript is all client side.

So what if you end up willing to be able to log JavaScript errors to the ULS log allowing you to see the errors from the context of a call. Recently I was working on a project where we decided we wanted to log all JavaScript errors during the test period (on either Development or automated test). So we needed a solution to catch JavaScript errors and push them to the ULS. Depending on the setup we also wanted to be able to turn it on and off somehow allowing us to have some freedom on when we wanted to catch errors.

Catching errors in JavaScript seemed pretty easy after diving into it a bit.If you would like to catch specific errors you can use the default try catch blocks and handle your errors yourself, however in our case we would like to catch any and all errors that might pop-up. To do so we can use the window.onerror and extend that. Lucky for us the window.onerror has the message, url and linenumber of where it all went wrong, However it is lacking the fulls tacktrace so that’s a small caveat. Also keep in mind that if you are working with IE 9 you might encounter some issues if you are running the debugger against your scripts, as you can find on this blogpost about IE 9 and the Debugger.

Given that we wanted to log all errors extending the window.onerror object is fairly easy, we only have to make sure it’s the first thing that gets registered to make sure sure all errors are catched, so within your masterpage make sure the first custom script that is loaded contains your window.onerror extension. (You do not want to load it before SharePoint scripts as we may assume Microsoft has tested those for us and we would like to use some of its goodness). We also want to use jQuery so keep your script below that as well, and then it’s all fairly easy. So create a new script file that is loaded after jQuery:

//#####################################################
// Catch all errors that we throw or are not catched,
// if you use try {} catches {} in code Please rethrow the error or we cannot log it!
//#####################################################
window.onerror = function (msg, url, linenumber) {
    // Log the error to our service
    logError(msg, url, linenumber);

    ExecuteOrDelayUntilScriptLoaded(RenderJavascriptError, 'sp.js');
    // By returning false instead of true we can skip the suppressing all warnings for users
    return true;
}

//#####################################################
// Calls httpHandler to log errors to the ULS log
//#####################################################
function logError(msg, url, linenumber) {
    var jqxhr = jQuery.ajax({
        type: 'Post',
        url: '/_layouts/Handler/javascriptlogger.ashx',
        data: { error: msg, lineNumber: linenumber, fileName: url, userAgent: navigator.userAgent },
        dataType: "json"
    });
    // we could forget this one and do a fire and forget
    jqxhr.fail(function () {
        ExecuteOrDelayUntilScriptLoaded(RenderLoggingError, 'sp.js');
    });
}

//#####################################################
// Renders a message for the user that there was a JS error
//#####################################################
function RenderJavascriptError() {
    notifyId = SP.UI.Notify.addNotification("Some JavaScript error on the page, we try to log it!", false);
}

//#####################################################
// Renders a message for the user that there an error while logging
//#####################################################
function RenderLoggingError() {
    notifyId = SP.UI.Notify.addNotification("Some error while posting to our logservice", false);
}

Then all we have to do is create a handler that can actually catch these errors and log them to ULS. Assuming that you have some sort of logging already, I am using the Microsoft Patterns & Practices just create an empty handler and add the following code

try
{
    // Only process post requests, get can be ignored
    if (context.Request.RequestType == "POST")
    {
        // retrieve error properties
        string error = context.Request.Form["error"];
        string lineNumber = context.Request.Form["lineNumber"];
        string fileName = context.Request.Form["fileName"];
        string userAgent = context.Request.Form["userAgent"];
        string userName = SPContext.Current.Web.CurrentUser.LoginName;

        // format error to decent readable string
        string formattedError = string.Format("Javascript error: '{0}', file: '{1}' line: '{2}' for user '{3}' with agent: '{4}'", error, fileName, lineNumber, userName, userAgent);

        // Do not log posts that did not contain an errormessage
        if (!string.IsNullOrEmpty(error))
        {
            Logger.LogException(new HttpCompileException(formattedError), Logger.GenericLogSeverity.Warning, "MyCategory/Javascript");
        }
    }
}
catch (Exception ex)
{
    // Its an issue if we cant log ..
    Logger.LogException(ex, Logger.GenericLogSeverity.Error, Settings.LoggingCategory);
}

Now if you execute your page and encounters an JavaScript error it will show up in the ULS logs. If you would like to make it a bit more flexible you could of course stuff it all into a feature so that you can turn it on / off depending on when you need it.

Loading comments…