Contents tagged with Logging

  • 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

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 4 - (HandleErrorAttribute)

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

    Creating a custom HandleErrorAttribute for ELMAH (Optional)

    Remember when we commented out that line from our Global.asax.cs in Part 1? Well, building our own HandleErrorAttribute will allow us to put it back or more importantly prevent the HandleErrorAttribute from overriding our ELMAH behavior. Currently the HandleError attribute will prevent any errors from bubbling up to ELMAH and we can't guarantee down the line, someone won't use a HandleError attribute like this...

    [HandleError]
    public class MyClass
    {
        // This error won't be handled by ELMAH
        public MyMethod() {
            throw new Exception("oops");
        }
    }
    

    The solution to this problem is to implement our own custom HandleErrorAttribute. Add the following class to your project...

    using System;
    using System.Web;
    using System.Web.Mvc;
    using Elmah;
    
    namespace ElmahAndMvc3
    {
        public class ElmahHandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
        {
            public override void OnException(ExceptionContext context)
            {
                base.OnException(context);
    
                var e = context.Exception;
                if (!context.ExceptionHandled   // if unhandled, will be logged anyhow
                    || RaiseErrorSignal(e)      // prefer signaling, if possible
                    || IsFiltered(context))     // filtered?
                    return;
    
                LogException(e);
            }
    
            private static bool RaiseErrorSignal(Exception e)
            {
                var context = HttpContext.Current;
                if (context == null)
                    return false;
                var signal = ErrorSignal.FromContext(context);
                if (signal == null)
                    return false;
                signal.Raise(e, context);
                return true;
            }
    
            private static bool IsFiltered(ExceptionContext context)
            {
                var config = context.HttpContext.GetSection("elmah/errorFilter")
                             as ErrorFilterConfiguration;
    
                if (config == null)
                    return false;
    
                var testContext = new ErrorFilterModule.AssertionHelperContext(
                                          context.Exception, HttpContext.Current);
    
                return config.Assertion.Test(testContext);
            }
    
            private static void LogException(Exception e)
            {
                var context = HttpContext.Current;
                ErrorLog.GetDefault(context).Log(new Error(e, context));
            }
        }
    }
    

    And modify the RegisterGlobalFilters in your Global.asax.cs...

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ElmahHandleErrorAttribute());
    }
    

    Now you have no worries about [HandleError] intercepting errors on your site.

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

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 3 – (Filtering)

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

    Filtering Unwanted Errors

    Ok, the reason I really like receiving an email for the every error message is because it will get annoying really fast. This results in me fixing the errors really fast. But there are just some errors that you just can't (or don't want to) "fix". If you noticed in Part 1, the error page contained some 404 errors for /favicon.ico. Errors like these will get annoying really quickly, and fortunately there's an easy way to filter them out. This chunk of code will filter out all 404 errors with a URL matching /favicon.ico.

    <system.web>
      <httpModules>
        <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
      </httpModules>
    </system.web>
    
    <system.webServer>
      <modules>
        <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
      </modules>
    </system.webServer>
    
    <elmah>
      <errorFilter>
        <test> <!-- do not log favicon.ico 404's -->
          <and>
            <equal binding="HttpStatusCode" value="404" type="Int32" />
            <regex binding="Context.Request.ServerVariables['URL']"
                   pattern="/favicon\.ico(\z|\?)" />
          </and>
        </test>
      </errorFilter>
    </elmah>
    

    Filtering 404 errors out of the ErrorMailModule

    Some errors are less important than others, for example 404 errors. I want to log all the 404's my site may receive, but I do not want an email every time someone mistypes a url on my site. To keep logging all errors (including 404's), but filter some from being sent via email, we can add this method to our Global.asax.cs file.

    public void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e)
    {
        var httpException = e.Exception as HttpException;
        if (httpException != null && httpException.GetHttpCode() == 404)
        {
            e.Dismiss();
        }
    }
    

    The ELMAH filtering can do a lot of cool stuff, you can even write scripts in JScript, but for the most part, the example above will be enough to do what you want. For more on filtering go to the wiki page.

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 4 – (HandleErrorAttribute) »

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 2 – (Notifications)

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

    Setup Notifications for ELMAH Errors

    Now that ELMAH is setup, you need a way to be notified when an error occurs. Though you can just subscribe to your ELMAH RSS feed at /elmah.axd/rss, I prefer to receive an email notification as soon as an error happens. To do this simply merge the following changes into your web.config's <configuration> section.

    <system.web>
      <httpModules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      </httpModules>
    </system.web>
    </pre>
    
    <system.webServer>
      <modules>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      </modules>
    </system.webServer>
    
    <elmah>
      <errorMail from="e@mail.address"
                 to="e@mail.address"
                 subject="elmah error mail" />
    </elmah>
    

    There's a lot more configurability, but those are the basics. Here's a good article showing more options. Also if you haven't already done it, set your smtp settings too...

    <system.net>
      <mailSettings>
        <smtp>
          <network host="localhost" />
        </smtp>
      </mailSettings>  
    </system.net>
    

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 3 – (Filtering) »

    This Post is Part of a Multi-Part Series

  • Logging Errors with ELMAH in ASP.NET MVC 3 – Part 1 – (Setup)

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

    Add the ELMAH binary to the project

    To install ELMAH using NuGet In Visual Studio 2010 (recommended), go to Tools >> Library Package Manager >> Add Library Package Reference… Select Online from the left search for Elmah and click Install.

    Alternatively you can, download ELMAH from here and add a reference to the Elmah library.

    Configure the ELMAH basics

    Before we get all fancy, let’s just get it running. To do that, you need to merge the following changes into your web.config. NuGet should have made these additions already for you… but let’s double check it just in case.

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
     
      <configSections>
        <sectionGroup name="elmah">
          <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
          <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
          <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
          <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
        </sectionGroup>
      </configSections>
     
      <elmah>
        <security allowRemoteAccess="yes" />
      </elmah>
     
      <system.web>
        <httpModules>
          <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
        </httpModules>
        <httpHandlers>
          <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
        </httpHandlers>
      </system.web>
     
      <system.webServer>
        <modules>
          <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
        </modules>
        <handlers>
          <add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
        </handlers>
      </system.webServer>
     
    </configuration>
    
    

    Also comment out this line from your Global.asax file as it interferes with ELMAH.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute());
    }
    

    Note: The HandleErrorAttribute captures any unhandled errors and displays the Error view when custom errors are turned on. We’ll re-enable this later in this series when we further extend ELMAH, but for now, just comment it out.

    Verify ELMAH is setup

    You should now be able to run your project, go to /elmah.axd and see a report similar to this one...

    Elmah Default Install

    Well, that was all pretty simple. Just a little bit of Copypasta and you’ve got the basics of ELMAH up and running. This is confirmed to work on DiscountASP.net shared hosting environment.

    Next Step: Logging Errors with ELMAH in ASP.NET MVC 3 – Part 2 – (Notifications) »

    This Post is Part of a Multi-Part Series