Couple of months ago, I needed to use ELMAH in one of my project (in case you haven’t heard about it, you can see what it can do for you here), but the component out of the box had some problem in my opinion. for example when an error occurs, it goes ahead and log the error, but if you refresh the page for example 20 times in row, the it is going to log that error that many times. We all know that it happens in an application when the user thinks if he/she refresh the page, the page might work, so this can cause a lot of unnecessary record in our database (assuming we’ve connected it to a database). Another potential problem can happen when you set it up to send an email to you when an error occurs, this is one those times when you might suffer from a lot of those self-inflicted spam emails, and your inbox will be flooded by the emails related to the same error, this can happen for various reason, for example a robot try to search your site for specific URL, like WordPress login page, and this robot enumerate hundreds of pages to see that you have it, and every single time ELMAH throws a 404 error, so you will get one hundred and fifty 404 errors.
In this post, I’m going to show you how you can customize the ELMAH to avoid these kinds of issues, back then when I searched the web for easy solution for this problem. I haven’t find one, so in this post I use some nugget packages and tools that makes the use of it easier and use them to restrict the it to our liking.
Install and configuring ELMAH to log the errors to database and sending email
Before nugget packages such as Elmah.MVC existed, you had to add the ELMAH dll and set up the configuration yourself, these days all you have to do is to add the Elmah.MVC nugget package and it takes care of all the plumbing work for you, also if you want to log your error to the database, add the package Elmah.SqlServer.EFInitializer to your project, this package adds the ElmahInitializer class to your project and in this class set up an initializer to create the necessary database for you, all you need to do is to have a connection string, you specify your connection string in the xml section:
<elmah> <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="myDb" /> </elmah>
If you also want to send an email, you need to set up a mail setting section in web.Config, it will automatically pick it up and uses it to send an email.
<system.net> <mailSettings> <smtp from="Elmah self-spammer<[email protected]>"> <network host="mail.site.com" port="25" userName="[email protected]"
password="password" /> </smtp> </mailSettings> </system.net>
And in the ELMAH section of web.Config, you use the node blow to say that you want the ELMAH to send the logged errors to your email:
<elmah> <errorMail from=" Elmah self-spammer<[email protected]>" to="[email protected]"
async="true" /> </elmah>
Restricting ELMAH form sending unnecessary emails and logging the same error in the database
Preventing ELMAH from sending email when the 404 error occurs
First thing you may want to do when restricting it is to dismiss the 404 errors, you’ll get tons of 404 errors every day and if you were to receive an email for every one of them, your mail box is going to explode, you can prevent ELMAH from sending these kinds of emails by adding a filter to the ELMAH section of your xml:
<elmah> <security allowRemoteAccess="yes" /> <errorFilter> <test> <and> <equal binding="HttpStatusCode" value="404" type="Int32" /> <regex binding="FilterSourceType.Name" pattern="mail" /> </and> </test> </errorFilter> </elmah>
You also might want to restrict the access to the ELMAH to the authenticated users with the role of admin, you can do this by adding this to your appSettings section of your web.Config:
<appSettings> <add key="elmah.mvc.disableHandler" value="false" /> <add key="elmah.mvc.disableHandleErrorFilter" value="false" /> <add key="elmah.mvc.requiresAuthentication" value="true" /> <add key="elmah.mvc.IgnoreDefaultRoute" value="false" /> <add key="elmah.mvc.allowedRoles" value="admin" /> <add key="elmah.mvc.allowedUsers" value="*" /> <add key="elmah.mvc.route" value="elmah" /> <add key="elmah.mvc.UserAuthCaseSensitive" value="true" /> </appSettings>
Preventing ELMAH form logging the same error multiple of times on refresh
ELMAH by default provide some events that we can use to change the workflow of its error logging, some of those events are ErrorLog_Filtering and ErrorMail_Filtering, we can use these events in global.asax file, here is what I came up with for restricting the ELMAH to only log the error if it wasn’t the same error and it had occurred more than ten minutes ago, also I dismiss the errors that are of type HttpRequestValidationException or FileNotFoundException and prevent them from going into database or end up in my email box:
private bool _sendEmail; void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e) { using (var context = new ElmahContext()) { //if the exception had the same message and was from //the last 10 min it means it's the same we dismiss it _sendEmail = true; var lastErr = context.ELMAH_Errors .OrderByDescending(m => m.TimeUtc).Take(1) .SingleOrDefault(); if (lastErr != null && (e.Exception.Message == lastErr.Message && lastErr.TimeUtc > DateTime.UtcNow.AddMinutes(-10))) { e.Dismiss(); _sendEmail = false; } } if (e.Exception.GetBaseException() is HttpRequestValidationException) e.Dismiss(); } void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e) { if (_sendEmail == false) { e.Dismiss(); } if (e.Exception.GetBaseException() is FileNotFoundException) e.Dismiss(); }
Deleting the old and unneeded ELMAH logs from the database to save database space
If you want to do something every time an error logged, you can do this in ErrorLog_Logged, here I wrote some code to check if there is any error older than 60 days in my database and if there is, I’ll delete them:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args) { //keep the log form the last 60 days and delete the rest using (var context = new ElmahContext()) { var baseLineDate = DateTime.UtcNow.AddDays(-60); var model = context.ELMAH_Errors.Where(p => p.TimeUtc < baseLineDate); foreach (var item in model) { context.ELMAH_Errors.Remove(item); } context.SaveChanges(); } }
This was my approach for customizing the ELMAH, I’m sure there is other ways to do the same things that I did here, if you have any question or problem, let me know in the comments section.