Logging Errors with ELMAH in ASP.NET MVC 3 – Part 5 – (JavaScript)

Tags: ASP.NET, MVC, Logging, Error Handler, Elmah, javascript

Now we need a way to track those pesky client side error messages. Tracking the server side errors is the easy part, but what about those browser errors that go unnoticed and unfixed?

First, create the Server Side Handler

To handle and log JavaScript errors, we'll first need to setup some server side code. The client (browser) will pass these errors to the server, which will hand them off to ELMAH for tracking. So let's start by creating a new Exception type for our JavaScript exceptions. This will allow us to differentiate the server errors from the JavaScript errors more easily. It doesn't need much so this should be fine...

public class JavaScriptException : Exception
{
    public JavaScriptException(string message) : base(message)
    {
    }
}

We are also going to need a Controller to receive the exception and hand it off to ELMAH.

public class ErrorController : Controller
{
    public void LogJavaScriptError(string message)
    {
        ErrorSignal
            .FromCurrentContext()
            .Raise(new JavaScriptException(message));
    }
}

That's it for the server side changes. Next we need to setup the client (browser) to actually handle these errors and pass them onto the controller.

JavaScript stack trace

First we need to implement a JavaScript Stack Trace. This will help us get more debugging information and context surrounding the errors. So copy and paste this stacktrace.js file to your project...

// FROM: http://helephant.com/2007/05/diy-javascript-stack-trace
 
function logError(ex, stack) {
    if (ex == null) return;
    if (logErrorUrl == null) {
        alert('logErrorUrl must be defined.');
        return;
    }
 
    var url = ex.fileName != null ? ex.fileName : document.location;
    if (stack == null && ex.stack != null) stack = ex.stack;
 
    // format output
    var out = ex.message != null ? ex.name + ": " + ex.message : ex;
    out += ": at document path '" + url + "'.";
    if (stack != null) out += "\n  at " + stack.join("\n  at ");
 
    // send error message
    $.ajax({
        type: 'POST',
        url: logErrorUrl,
        data: { message: out }
    });
}
 
Function.prototype.trace = function()
{
    var trace = [];
    var current = this;
    while(current)
    {
        trace.push(current.signature());
        current = current.caller;
    }
    return trace;
}
 
Function.prototype.signature = function()
{
    var signature = {
        name: this.getName(),
        params: [],
        toString: function()
        {
            var params = this.params.length > 0 ?
                "'" + this.params.join("', '") + "'" : "";
            return this.name + "(" + params + ")"
        }
    };
    if (this.arguments)
    {
        for(var x=0; x < this.arguments.length; x++)
            signature.params.push(this.arguments[x]);
    }
    return signature;
}
 
Function.prototype.getName = function()
{
    if (this.name)
        return this.name;
    var definition = this.toString().split("\n")[0];
    var exp = /^function ([^\s(]+).+/;
    if (exp.test(definition))
        return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
    return "anonymous";
}

window.onerror = function (msg, url, line) {
    if (arguments != null && arguments.callee != null && arguments.callee.trace)
        logError(msg, arguments.callee.trace());
}

Finally, add the Client Side Handlers

Now we need add a reference the script in the template file and also initialize the JavaScript variable 'logErrorUrl' so errorhandler.js knows where to post the error details to.

<script type="text/javascript">
  var logErrorUrl = '@Url.Action("LogJavaScriptError", "Error")';
</script>
<script src="@Url.Content("~/Scripts/errorhandler.js")" type="text/javascript"></script>

That should be everything. All JavaScript errors should now be tracked via ELMAH.

Testing the Error Handler

If you want to test this, just to make sure it all works (which of course you do), then you can use this code to run some tests...

<script type="text/javascript">
  function getPropertyData(value) {
    var x = value.property["data"];
  }

  function testCapturedError() {
    var x = getPropertyDataCaptured(null);
  }

  function getPropertyDataCaptured(value) {
    try {
      var x = value.property["data"];
    } catch (err) {
      if (arguments != null && arguments.callee != null && arguments.callee.trace)
          logError(err, arguments.callee.trace());
    }
  }
</script>

<ul>
<li><a href="javascript:getPropertyData(null);">execute getPropertyData(null) method.</a></li>
<li><a href="javascript:testCapturedError();">execute testCapturedError() method.</a></li>
</ul>

This page contains two exception tests. The first test will execute an unhandled exception. The second test will demonstrate a captured error (which you should always be doing) and stack trace and log it to ELMAH. Now when you check your ELMAH page, you should see the JavaScript exceptions show up like this...

Elmah JavaScript Error

Download the full project:

ElmahAndMvc3.zip

This Post is Part of a Multi-Part Series

Add a Comment