Friday, May 28, 2010

Exception/Error Handling and Logging

How difficult it is to implement a good logging and error handling in your web application. Sometimes it’s hard to decide on which is the best way to implement error handling in the application. In this article I have throw light on one of third party component (dll) which provides you lots of good feature and makes error handling easy to implement.

I am talking about Log4Net dll, which is free to download and plug into your application.
Lets look into step by step implementation.


1) Create your website application or you can use your any existing site. Download the DLL file from Apache web site (http://logging.apache.org/log4net/download.html) After downloading extract the file. Now, you need to add Log4net DLL reference to your web site, right click in your application and choose add reference...


2) Now let's configure our application by adding some tags to web.config file: Inside <configSections> tag, add this tag:
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
And also add log4net tag below </configSections> i.e.
<log4net>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="C:\Temp\Log"/> <!-- path + file name -->
<staticLogFileName value="false"/>
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="_yyyy_MM_dd".html"" /> <!-- suffix of filename & extention -->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%m" />
</layout>
</appender>
<!-- Add below appender only if you want to sent error mail too -->
<appender name="SmtpAppender" type="StoreManager.Utilities.EmailService"><!—- namespace of wrapper class -->
<to value="abc@xyz.com,efg@xyz.com" />
<from value="StoreManager@xyz.com" />
<subject value="Unhandled Exception: Store Manager" />
<smtpHost value="smtpHostName"/>
<lossy value="false"/>
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="ERROR"/>
</evaluator>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%m" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingFile" />
<appender-ref ref="SmtpAppender" />
</root>
</log4net>


3) Create a Enum for log level

namespace StoreManager.DataModels
{
public class Constants
{
public enum LogLevel
{
DEBUG = 1,
ERROR,
FATAL,
INFO,
WARN
}
}
}


4) Create a logger class

using System;
using log4net;
using log4net.Config;
using System.Web;
using System.Text;
using System.Diagnostics;

namespace StoreManager.Utilities
{
public static class Logger
{
#region Members
private static readonly ILog logger = LogManager.GetLogger(typeof(Logger));
#endregion

#region Constructors
static Logger()
{
XmlConfigurator.Configure();
}
#endregion

#region Methods
public static void WriteLog(Constants.LogLevel logLevel, String log)
{
switch (logLevel)
{
case Constants.LogLevel.DEBUG:
default:
logger.Debug(log);
break;
case Constants.LogLevel.ERROR:
logger.Error(log);
break;
case Constants.LogLevel.FATAL:
logger.Fatal(log);
break;
case Constants.LogLevel.INFO:
logger.Info(log);
break;
case Constants.LogLevel.WARN:
logger.Warn(log);
break;
}
}

public static string GenerateLogInfo(Exception ex)
{
StringBuilder sbBody = new StringBuilder();
string sURL = HttpContext.Current.Request.Url.ToString();
if (sURL.IndexOf("error.aspx") < 0)
{
string Stack = "";
StackTrace Trace = new StackTrace();
int FrameCount = Trace.FrameCount;
// We skip frame 1 on the stack since that is always the current method //
for (int index = 1; index < FrameCount; index++)
{
if (index > 1)
{
Stack += " --> ";
}
Stack += Trace.GetFrame(index).GetMethod().ToString();
}
Stack = (Stack != "" ? Stack : ex.StackTrace);
Stack = (ex.InnerException != null ? ex.InnerException.StackTrace + "\n" : "") + Stack;
if (Stack == "")
{
Stack = "Stack trace not available.";
}
string exceptionMessage = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
sbBody.Append("<br><table border='1'><tr><td>Time: " + System.DateTime.Now.ToString() + "</tr></td>");
sbBody.Append("<tr><td>URL: " + HttpContext.Current.Request.Url + "</tr></td>");
sbBody.Append("<tr><td>User IP: " + HttpContext.Current.Request.UserHostAddress + "</tr></td>");
sbBody.Append("<tr><td>Error Message: " + exceptionMessage + "</tr></td>");
sbBody.Append("<tr><td>Stack: \n" + Stack.Replace(" ", " ") + "</tr></td></table>");
}
return sbBody.ToString();
}
#endregion
}
}


5) Smtp appender wrapper – Use this class if required

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using log4net.Appender;
using System.Net;
using System.Net.Mail;

namespace StoreManager.Utilities
{
/// <summary>Sends an HTML email when logging event occurs</summary>
public class EmailService : SmtpAppender
{
/// <summary>Sends an email message</summary>
protected override void SendEmail(string body)
{
SmtpClient smtpClient = new SmtpClient();
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(From);
mailMessage.To.Add(To);
mailMessage.Subject = Subject;
mailMessage.Body = body;
mailMessage.Priority = Priority;
mailMessage.IsBodyHtml = true;

smtpClient.Host = SmtpHost;
smtpClient.Send(mailMessage);
}
}

}


6) Add below code in Global.asax.cs for global error handling.

protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
HttpException httpException = exception as HttpException;
Response.Clear();

if (httpException != null)
{
Logger.WriteLog(Constants.LogLevel.ERROR, Logger.GenerateLogInfo(exception));
Server.ClearError();
Response.Redirect("~/error.aspx");
}

}

Now we are done with all and this will generate day wise log of errors in html, and also same log will be mailed to listed persons in html format.

This will generate email and log somewhat as below:

No comments:

Post a Comment