Monday, March 3, 2008

Error logging

I'm currently working on hardening an ASP.NET web site, so I'll be posting on error-handling and security issues for a little while.

One of the basics for this task is good error handling, so I'll start with event logging. You don't ever want your production web site to post error data (not even in comments, so don't think about hiding them in your HTML source, either). So what to do? Use the Windows event logs.

Here's a simple event logger that does the job. Put it in your App_Code directory.


public class Logger
{
/// <summary>
/// Application name
/// </summary>
private static string LogName = "MyApp";
/// <summary>
/// Default constructor.
/// </summary>
public Logger()
{
}

/// <summary>
/// Set up EventSource for logging
/// </summary>
public static void InitLogger()
{
if (!EventLog.Exists(LogName))
{
EventLog.CreateEventSource("MyApp Web Tools", LogName);
}
}

/// <summary>
/// Log any error data sent this way.
/// </summary>
/// <param name="LogData">Exception to be logged.</param>
public static void Log(Exception LogData)
{
string LogMessage = LogData.GetType().ToString() + ": " +
LogData.Message + System.Environment.NewLine +
LogData.StackTrace;
EventLog.WriteEntry(LogName, LogMessage, EventLogEntryType.Error);
}

/// <summary>
/// Log an exception, plus additional data
/// </summary>
/// <param name="LogData">Exception to be logged</param>
/// <param name="LogInfo">Dictionary of string-string name-value pairs to
/// be included in the log entry</param>
public static void Log(Exception LogData, Dictionary<string,> LogInfo)
{
StringBuilder _LogText = new StringBuilder();
_LogText.Append(LogData.GetType().ToString() + ": ");
_LogText.Append(LogData.Message + System.Environment.NewLine);
foreach(string Name in LogInfo.Keys)
{
_LogText.Append(String.Format("Name: {0}, Value{1}{2}",
Name, LogInfo[Name], System.Environment.NewLine));
}
_LogText.Append(LogData.StackTrace + System.Environment.NewLine);
EventLog.WriteEntry(LogName, _LogText.ToString(), EventLogEntryType.Error);
}
}

Note that the logger takes an exception as a parameter. There are a couple of nice things about this. One is that the exception contains a call stack, so your log can point you to the place where the exception occurred. Second, you can set up your application to catch unhandled exceptions, and log those, too, so you can get logs on errors that you didn't anticipate. (My next post will cover unhandled exceptions in detail.)

Using this logging class is very easy. Somewhere in your initialization code, you need to call

Logger.InitLogger();

Then, to log an event, throw an exception, catch it, and log it.

try
{
throw new Exception("Surprise!");
}
catch (Exception ex)
{
Logger.Log(ex);
}

How hard is that? You can find your log using either the Windows Event Viewer (in Computer -> Manage) or in the Server Explorer in Visual Studio.

Also note that the Log method is overloaded. The second version adds a dictionary as a second parameter; you can load up the dictionary with name-value pairs (both as strings) so your event log will contain data values that you'd like to save with the exception message and call stack. Make your logs as informative as possible!