Tuesday, June 23, 2009

Error Tracking in asp.net

Application Level Error Handling in ASP.NET

ASP.NET provides many different ways for error handling. Error handling starts on page level with try-catch blocks and Page_Error procedure. To find out more about this see Errors and Exceptions in ASP.NET tutorial. This tutorial goes one step further and explains error handling on Application level. This includes handling errors with Application_Error procedure in Global.asax or by using custom http module. These methods are usually used to log error details in text file, database or Windows EventLog, or to send notification e-mails to administrator.

Handling errors in Application_Error in Global.asax

Global.asax is optional file. Your site can work without it, but it could be very useful. You can have only one Global.asax in the root folder of your web site. Global.asax contains Application_Error procedure which executes whenever some unhandled error occurs. By using Application_Error, you can catch all unhandled errors produced by your web site, and then write a code to save error messages to database, text file or Windows EventLog, send notificationn e-mail, write some message to user, redirect user to other page with Response.Redirect etc. If you used Server.ClearError() in Page_Error procedure, Application_Error in Global.asax will not execute. Implementation code is similar to Page_Error code above:

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();
string ErrorDetails = CurrentException.ToString();433333

// Now do something useful, like write error log
// or redirect a user to other page
...
}

or

protected void Application_Error(Object sender, EventArgs e)
{
HttpContext ctx = HttpContext.Current;

Exception ex = ctx.Server.GetLastError().InnerException;
string errorInfo = "\r\n\r\nTime: " + DateTime.Now.ToString() +
"\r\nOffending URL: " + ctx.Request.Url.ToString() +
"\r\nIP:" + ctx.Request.ServerVariables["remote_addr"] +
"\r\nSource: " + ex.Source +
"\r\nMessage: " + ex.Message +
"\r\nRefer: " + ctx.Request.ServerVariables["http_referer"] +
"\r\nUser Agent: " + ctx.Request.ServerVariables["http_user_agent"] +
"\r\nStack trace:\r\n " + ex.StackTrace;

System.IO.StreamWriter sw = new System.IO.StreamWriter(Application["strErrorLog"].ToString(), true);
sw.Write(errorInfo);
sw.Close();

ctx.Server.ClearError();
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()
Dim ErrorDetails As String = CurrentException.ToString()

' Now do something useful, like write error log
' or redirect a user to other page
...

End Sub

Writing errors to EventLog

One of the ways to track errors is to write them to EventLog. If your web site is on shared hosting you probably can't write to EventLog because of security issues. If you are on dedicated server, you need to enable "Full control" to EventLog for ASPNET account. You can do this in Registry Editor. Go to Start menu -> Run... and type regedt32 or regedit and press Enter. Registry Editor will show. Go to HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog\, right mouse click and select Permissions... from context menu, like in image bellow.

Registry editor
Registry editor

Click on Permissions... item to show a dialog. Give Full Control to your ASP.NET Machine Account, like in next image (include child objects too!).

Registry permissions dialog
Registry permissions dialog

Now ASPNET account has enough rights. To write in EventLog we can use Try...Catch syntax or Page_Error event, but to avoid duplication of code better option is to use Application_Error in Global.asax file. Code inside Application_Event procedure will be executed every time when some error occurs on web site.

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();

// Name of the log
string SiteLogName = "My Web Site Errors";

// Check if this log name already exists
if (EventLog.SourceExists(SiteLogName) == false)
{
EventLog.CreateEventSource(SiteLogName, SiteLogName);
}

// Write new error log
EventLog NewLog = new EventLog();
NewLog.Source = SiteLogName;
NewLog.WriteEntry(CurrentException.ToString(), EventLogEntryType.Error);
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()

' Name of the log
Dim SiteLogName As String = "My Web Site Errors"

' Check if this log name already exists
If EventLog.SourceExists(SiteLogName) = False Then
EventLog.CreateEventSource(SiteLogName, SiteLogName)
End If

' Write new error log
Dim NewLog As EventLog = New EventLog()
NewLog.Source = SiteLogName
NewLog.WriteEntry(CurrentException.ToString(), _
EventLogEntryType.Error)
End Sub

Reading web site error messages from EventLog

After we created procedure for writing site error messages to Windows EventLog, we need to read them too. You can read these messages by using Windows Event Viewer, located in Control Panel. Also, you can show these messages on web page in some kind of report. To read EventLog error messages with ASP.NET and show them on page you can use code like this:

[ C# ]

using System;

// We need these namespace to read EventLog
using System.Diagnostics;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Load errors to EventLog object
EventLog MySiteErrorLogs = new EventLog("My Web Site Errors");

foreach(EventLogEntry SiteErrorLog in MySiteErrorLogs.Entries)
{
// Find when error occured
Response.Write("Time generated: " +
SiteErrorLog.TimeGenerated.ToString() + "<br />");
// Show error details
Response.Write(SiteErrorLog.Message + "<hr />");
}
}
}

[ VB.NET ]

Imports System

' We need these namespace to read EventLog
Imports System.Diagnostics

Partial Class DefaultVB
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' Load errors to EventLog object
Dim MySiteErrorLogs As EventLog = New EventLog("My Web Site Errors")

For Each SiteErrorLog In MySiteErrorLogs.Entries
' Find when error occured
Response.Write("Time generated: " & _
SiteErrorLog.TimeGenerated.ToString() & "<br />")
' Show error details
Response.Write(SiteErrorLog.Message + "<hr />")
Next
End Sub
End Class

Logging of unhandled errors to text file or database

On the same way, we can use Application_Error in Global.asax to write error message to text file or in some table in database. In this example, Application_Error procedure contains a code that writes error information to .txt file.

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException;
CurrentException = Server.GetLastError();

// Write error to text file
try
{
string LogFilePath = Server.MapPath("ErrorLog.txt");
StreamWriter sw = new StreamWriter(LogFilePath);
// Write error to text file
sw.WriteLine(CurrentException.ToString());
sw.Close();
}
catch (Exception ex)
{
// There could be a problem when writing to text file
}
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

' Get current exception
Dim CurrentException As Exception
CurrentException = Server.GetLastError()

' Write error to text file
Try

Dim LogFilePath As String = Server.MapPath("ErrorLog.txt")

Dim sw As System.IO.StreamWriter = _
New System.IO.StreamWriter(LogFilePath)
' Write error to text file
sw.WriteLine(CurrentException.ToString())
sw.Close()
Catch ex As Exception
' There could be a problem when writing to text file
End Try
End Sub

Better error logging with Log4Net

If you simply write error details to text file you will get simple, but not scalable solution. In case of large number of concurrent users, text file could be locked for writing and you will get another error. Log4Net is more scalable solution for this problem. To find out how to use Log4Net see Using NHibernate and Log4Net in ASP.NET 2.0 applications tutorial.

How to send e-mail with error details



You can place a procedure for sending an email inside Application_Error, try-catch block or any other error handling code. Function to send email is pretty simple. In this example, code for sending notification e-mails is located in Application_Error procedure in Global.asax file. On this way e-mail will be sent whenever some undhandled error occurs.

[ C# ]

<%@ Application Language="C#" %>

<%
-- We need these namespaces to send e-mail --%>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Mail" %>

<script runat="server">

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();
string ErrorDetails = CurrentException.ToString();

// Send notification e-mail
MailMessage Email =
new MailMessage("admin@yoursite.com",
"admin@yoursite.com");
Email.IsBodyHtml = false;
Email.Subject = "WEB SITE ERROR";
Email.Body = ErrorDetails;
Email.Priority = MailPriority.High;
SmtpClient sc = new SmtpClient("localhost");
sc.Credentials =
new NetworkCredential("EMailAccount", "Password");
sc.Send(Email);
}

</script>

[ VB.NET ]

<%@ Application Language="VB" %>

<%-- We need these namespaces to send e-mail --%>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Mail" %>

<script runat="server">

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()
Dim ErrorDetails As String = CurrentException.ToString()

' Send notification e-mail
Dim Email As MailMessage = _
New MailMessage("admin@yoursite.com", _
"admin@yoursite.com")
Email.IsBodyHtml = False
Email.Subject = "WEB SITE ERROR"
Email.Body = ErrorDetails
Email.Priority = MailPriority.High
Dim sc As SmtpClient = New SmtpClient("localhost")
sc.Credentials = _
New NetworkCredential("EMailAccount", "Password")
sc.Send(Email)
End Sub

</script>

Handling errors with custom HttpModule

Custom Http module advantage is that module doesn't require changes in your web application code. Http module will catch the same event like Application_Error procedure. To create custom Http Module, start new project in Visual Studio. Project type should be Class Library. Add code like this:

[ C# ]

using System;
using System.Web;

namespace CommonModules
{
public class ErrorHandlingModule : IHttpModule
{

public void Init(HttpApplication app)
{
app.Error += new EventHandler(this.HandleErrors);
}

public void HandleErrors(object o, EventArgs e)
{
// Get current exception
Exception CurrentException = ((HttpApplication)o).Server.GetLastError();

// Now do something with exception with code like in examples before,
// send e-mail to administrator,
// write to windows EventLog, database or text file
}

public void Dispose()
{
// We must have this procedure to implement IHttpModule interface
}
}
}

[ VB.NET ]

Imports System
Imports System.Web

Namespace CommonModules
Public Class ErrorHandlingModule
Implements IHttpModule

Public Sub Init(ByVal app As HttpApplication)
app.Error += New EventHandler(this.HandleErrors)
End Sub

Public Sub HandleErrors(ByVal o As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = (CType(o, _
HttpApplication)).Server.GetLastError()

' Now do something with exception with code like in examples before,
' send e-mail to administrator,
' write to windows EventLog, database or text file
End Sub

Public Sub Dispose()
' We must have this procedure to implement IHttpModule interface
End Sub
End Class
End Namespace

To use the module in web application, you need to add few lines in Web.Config file.

<httpModules>
<add name="ErrorHandlingModule" type="CommonModules.ErrorHandlingModule, CommonModules "/>
</httpModules>

More about how to create and implement custom Http modules in ASP.NET see in How To Create Your Own Http Module tutorial.

ASP.NET Error Handling Remarks

Be careful to handle possible errors in your Application_Error procedure. This procedure is executed when every error occurs, so Application_Error will call itself and it is possible to go in to infinite loop.

Writing error logs to text file can cause a problem with concurrency if a lot of errors need to be logged simultaneously. To make this task easier, you can use Log4Net. Also, there is pretty nice complete solution called Elmah now available at Google code.

3 comments:

Anonymous said...

Great writing! I wish you could follow up on this topic :P

-Warmest Regards
Beverly

Anonymous said...

This is the most interesting paper that I have read all year?

Anonymous said...

Could be the BEST read I read this week!?

-Thank You
Clair

Popular Posts