Skip navigation

Making the Most of ELMAH ASP.NET Error Logging

ELMAH, short for Error Logging Modules and Handlers, is an error logging solution that can be easily added into ASP.NET applications in order to trap and report on unhandled exceptions. It's a fantastic solution that all ASP.NET web developers should know about, and this article will cover some easy ways to extend it so that it can be used for more than just unhandled exceptions. For more information on ASP.NET, see "HTML5 for the ASP.NET Developer" and "How to Use ASP.NET MVC 4 Display Modes in Mobile Web Apps."

ELMAH

I won't go into too much praise of ELMAH in this article, because others have done so elsewhere. What I love most about it though, of course, is that it tracks all exceptions and displays them in a great dashboard that you can actively work with and query to learn more about the kinds of problems your site is bumping in to—as shown in Figure 1.

ELMAH ASPNET error handling_Fig1_0

Figure 1: ELMAH's dashboard display of errors and details, one of its best features

Suffice it to say, however, that I use ELMAH in all of my sites and wouldn't dream of deploying them without it—as it's such a powerful way of making sure that you're keeping tabs on all of those potential un-thought-of problems that can occur with a site once it's up and running.

As Scott Hanselman outlines, the easiest way to install ELMAH is to use Nuget to drop it into a site. (To do this, just make sure you've upgraded NuGet on your workstation, and then you'll want to install the ELMAH 1.2 package (not the ELMAH Core Library that doesn't have any configuration options) to get in-memory storage and reporting. Then, if you want the ability to persist trapped errors, you'll want to install an ELMAH on SQL Server or ELMAH on XML or whatever package as an additional, separate, addition.)

Otherwise, when it comes to dropping ELMAH into MVC applications I also always go into my Global.asax and add a new route exclusion (typically right under/around the existing .axd exclusion):

routes.IgnoreRoute("favicon.ico");

This exclusion, in turn, just tells my application to ignore requests to favicon.ico—instead of trying to find a controller for it. Likewise, if you don't have a favicon.ico living in the root of your site, you'll want to add one in—otherwise once you add ELMAH you'll start getting reports of unhandled FileNotFound exceptions.

The key thing to note about these potential exceptions or errors that you'll bump into when dealing with favicon.ico is that they're NOT something caused by ELMAH. Instead, they're a perfect example of how a site that seems to be running just fine was actually throwing unhandled exceptions—which ELMAH exposes once it's deployed.

ELMAH and Handled Exceptions in MVC Applications

As phenomenal as ELMAH is at trapping unhandled exceptions, I like to make a couple of changes to my MVC applications that allow them to log certain kinds of handled exceptions into ELMAH as well. And the reason for this is that ELMAH works so insanely well as a reporting mechanism for exceptions encountered on the site that it only makes sense (to me) to use it for certain, ugly, exceptions that I may have anticipated and gracefully handled—but still wish to know about.

For example, assume that you have an MVC application where you're requiring payment as part of the signup process. In such an application you'd likely have code to trap or handle exceptions that occurred when communicating with the payment gateway or when trying to persist the results of a successful signup to the database and so on. And while it would be a best practice to make sure that those exceptions were trapped and gracefully handled (to provide for a better user experience AND to avoid information disclosure), simply swallowing these exceptions after handling them could be very costly. Whereas gracefully handling them and then routing them into ELMAH means that they'll be reported and you can even optionally set up alerts via email to be notified if/when the occur. Such is the power of ELMAH—if you route certain types of handled exceptions into it.

To meet this need, I've taken the approach of implementing a very simple interface in my MVC applications. (More specifically, I define this interface down at the level of my 'core' libraries so that I can access this interface from within Repositories, Services, and other assemblies that my MVC site might depend upon.)

As you can see, this interface is insanely simple:

public interface IErrorLogger
{
    void LogException(Exception ex);
}

Then, in terms of implementation, I typically have my MVC site implement an ElmahLogger class (that I typically drop into my \Services\Logging\ folder) that looks something like this:

public class ElmahLogger : IErrorLogger
{
    public void LogException(Exception ex)
    {
        try
        {
            var context = HttpContext.Current;
            if (context == null)
                return;
            var signal = ErrorSignal.FromContext(context);
            if (signal == null)
                return;
            signal.Raise(ex);
        }
        catch (Exception)
        {
            // swallow or handle with something else...
        }
    }
} 

What this allows, in turn, is for the option to take any exception desired and pass it into IErrorLogger.LogException() in order to have it logged, and then available for display from within ELMAH's dashboard. With this approach (and some dependency injection), my MVC application is able to locate my ErrorLogger instance, pass the exception into Elmah, where ELMAH is then able to add the exception into its own handlers as if this had been any other error that ELMAH was configured to trap.

Of course, while this approach lets me call IErrorLogger's .LogException() method from anywhere, the most logical place for this functionality is within a try-catch block. For example, here's a mock-up of the signup mentioned earlier:

[HttpPost]
public ActionResult Signup(Signup signup, string returnUrl)
{
    try
    {
        if (ModelState.IsValid)
        {
            signup.Validate(); 
           Subscription result =
                this.PaymentService.AddSubscriber(signup);
            this.UserRepository.SaveSignup(signup, result);
            this.EmailService.SendWelcomeMessage(signup, result);
            this.FormsService.SignIn(signup.EmailAddress, false);
            TempData.Add("Message", "Signup Complete/Successful.");
            if (returnUrl != null)
                TempData["RedirectUrl"] = returnUrl;
            return RedirectToAction("SignupComplete");
        }
    }
    catch (Exception ex)
    {
        this.ErrorLogger.LogException(ex);
        // other stuff to gracefully handle exception
        // etc.
    }
    return View(signup);
}

That said, in practice, what I typically do is create a custom base controller called SiteController that implements a helper method like so:

protected void HandleException(Exception ex)
{
    if (ex is ValidationException)
    {
        ModelState.ImportValidationErrors(ex as ValidationException);
        TempData[SiteKeys.TempData.Error] = Errors.Messages.ValidationErrors;
        return;
    }
    // any exceptions 'below' this one are logged
    this.ErrorLogger.LogException(ex);
    if (ex is PersistenceException)
    {
        TempData["Error"] = string.Format("Unable to Save Changes.");
        return;
    }
    // unhandled error
    TempData["Error"] = "Oops. Unexpected error.";
}

With this approach (or example), Validation Exceptions are simply copied into TempData and control is then just returned back to the Controller Action in question—as there's no need to log validation problems. Anything else, though, is expected by this sample code to be a big enough issue that it should be logged to ELMAH, and is done via the call to ErrorLogger.LogException(); right in the 'middle' of the helper method.

This, in turn means that my catch block ends up being much simpler, as it's just a call to the base controller's .HandleException() helper—method:

public class AccountController : SiteController
{
    // ctor/etc...
    [HttpPost]
    public ActionResult Signup(Signup signup, string returnUrl)
    {
        try
        {
            if (ModelState.IsValid)
            {
                // do stuff...
                // that may cause real errors
                // or just validation errors/etc
            }
        }
        catch (Exception ex)
        {
            base.HandleException(ex);
        }
        return View(signup);
    }
}

The key point, though, is that ELMAH is now able to not only trap unhandled exceptions, but is now also able to track and report on handled exceptions that I wish to log. (Another approach to determining what kinds of exceptions to log or not might be to build an overload of .HandleException() that took in a Boolean parameter to 'log' the exception or not.)

Securely Exposing ELMAH to Remote Users

Of course, once you have this kind of functionality in place, you'll likely want some way to access this information without having to log into your web server to get at it.

This, of course, presents a potential security problem if you think about it—because something that both tracks and displays ALL unhandled exceptions (along with any exceptions you now also want to be notified of) represents a hacker's dream in terms of information disclosure.

Happily, though, ELMAH accounts for this out of the box and does NOT allow remote requests by default. To configure it to allow remote connections you need to do TWO things.

First, you need to visit ELMAH's documentation on Securing Error Log Pages and enable the ability to allow remote access within your web.config.

Second, you then need to read the rest of the documentation on that page and secure access to the ELMAH handler via ASP.NET Authorization. Doing this is really pretty trivial—and ensures that only authenticated users are able to see the errors on your site.

Personally, I always take an additional step as well—which is to change the path to elmah.axd—as a properly secured path to http://yoursite.com/elmah.axd will disclose to hackers that ELMAH is installed by redirecting them to a login page. As such, I usually just change the path to my ELMAH handler with something like the following:

 


  
    
      
        
        
      
    
  
  
  
    
  
 
 
    
      
      
      
    
    
      
    
  

  
    
      
      
      
    
    
      
      
    
    
  

Conclusion

Needless to say, if you're not already using ELMAH, you really need to look into it—it really is all it's cracked up to be. Likewise, if you're just using ELMAH to trap unhandled exceptions (which is a great start), you really should look into it as a means for logging and reporting on 'big' or 'ugly' exceptions that you'd want to know about as well. Doing so can make a big impact on your ability to respond to unanticipated problems and issues—and provide a better, overall, experience for your users.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish