C# Exception Logger (.NET 2)



It happens to the best of us, our code throws an unexpected exception. This little class will catch any unhandled exceptions in a WinForms app and log them to a text file, event log or website, along with some useful information such as the call stack and loaded DLLs. When logging to a text file, it also ensures the file doesn't get too big.

This is the .NET 2 version. There is also a .NET 1.1 version, which is simpler and doesn't provide as many features.

To use it add the following code to your Windows Forms app, before the call to Application.Run().

ExceptionLogger logger = new ExceptionLogger();

You then need to add one or more of the following logger implementations to do the logging via the ExceptionLogger.Add() method

The class can also be used in console apps. It will behave differently because although unhandled exceptions will be logged, the exception won't be caught so your application will still crash. It may also be used in ASP.NET applications although depending on the trust level your code is running in exceptions may be thrown.

Oh yes, almost forgot, this was inspired by the original Delphi version by Madshi, which is much cleverer than my version. Thanks must also go to Nathan Anderson for the code he has contributed.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace Utilities
{
  /// <summary>
  /// 
  /// </summary>
  public abstract class LoggerImplementation
  {
    /// <summary>Logs the specified error.</summary>
    /// <param name="error">The error to log.</param>
    public abstract void LogError(string error);
  }

  /// <summary>
  /// Class to log unhandled exceptions
  /// </summary>
  public class ExceptionLogger
  {
    /// <summary>
    /// Creates a new instance of the ExceptionLogger class
    /// </summary>
    public ExceptionLogger()
    {
      Application.ThreadException +=
        new System.Threading.ThreadExceptionEventHandler(OnThreadException);
      AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
      loggers = new List<LoggerImplementation>();
    }

    private List<LoggerImplementation> loggers;
    /// <summary>
    /// Adds a logger implementation to the list of used loggers.
    /// </summary>
    /// <param name="logger">The logger to add.</param>
    public void AddLogger(LoggerImplementation logger)
    {
      loggers.Add(logger);
    }

    delegate void LogExceptionDelegate(Exception e);

    private void HandleException(Exception e)
    {
      if (MessageBox.Show("An unexpected error occurred - " + e.Message +
        ". Do you wish to log the error?", "Error", MessageBoxButtons.YesNo) == DialogResult.No)
        return;

      LogExceptionDelegate logDelegate = new LogExceptionDelegate(LogException);
      logDelegate.BeginInvoke(e, new AsyncCallback(LogCallBack), null);
    }

    // Event handler that will be called when an unhandled
    // exception is caught
    private void OnThreadException(object sender, ThreadExceptionEventArgs e)
    {
      // Log the exception to a file
      HandleException(e.Exception);
    }

    private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
      HandleException((Exception)e.ExceptionObject);
    }

    private void LogCallBack(IAsyncResult result)
    {
      AsyncResult asyncResult = (AsyncResult)result;
      LogExceptionDelegate logDelegate = (LogExceptionDelegate)asyncResult.AsyncDelegate;
      if (!asyncResult.EndInvokeCalled)
      {
        logDelegate.EndInvoke(result);
      }
    }

    private string GetExceptionTypeStack(Exception e)
    {
      if (e.InnerException != null)
      {
        StringBuilder message = new StringBuilder();
        message.Append(GetExceptionTypeStack(e.InnerException));
        message.Append(Environment.NewLine);
        return (message.ToString());
      }
      else
      {
        return ("   " + e.GetType().ToString());
      }
    }

    private string GetExceptionMessageStack(Exception e)
    {
      if (e.InnerException != null)
      {
        StringBuilder message = new StringBuilder();
        message.Append(GetExceptionMessageStack(e.InnerException));
        message.Append(Environment.NewLine);
        return (message.ToString());
      }
      else
      {
        return ("   " + e.Message);
      }
    }

    private string GetExceptionCallStack(Exception e)
    {
      if (e.InnerException != null)
      {
        StringBuilder message = new StringBuilder();
        message.Append(GetExceptionCallStack(e.InnerException));
        message.Append(Environment.NewLine);
        message.Append("--- Next Call Stack:");
        message.Append(Environment.NewLine);
        return (message.ToString());
      }
      else
      {
        return (e.StackTrace);
      }
    }

    /// 
    /// writes exception details to the registered loggers
    /// 
    private void LogException(Exception exception)
    {
      DateTime now = System.DateTime.Now;
      StringBuilder error = new StringBuilder();

      error.Append("Application:       " + Application.ProductName
        + Environment.NewLine);
      error.Append("Version:           " +
        Application.ProductVersion + Environment.NewLine);
      error.Append("Date:              " +
        System.DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + Environment.NewLine);
      error.Append("Computer name:     " +
        SystemInformation.ComputerName + Environment.NewLine);
      error.Append("User name:         " +
        SystemInformation.UserName + Environment.NewLine);
      error.Append("OS:                " +
        System.Environment.OSVersion.ToString() + Environment.NewLine);
      error.Append("Culture:           " +
        CultureInfo.CurrentCulture.Name + Environment.NewLine);
      error.Append("Resolution:        " +
        SystemInformation.PrimaryMonitorSize.ToString() + Environment.NewLine);
      Process[] systemProcesses = Process.GetProcessesByName("System");
      if (System.Environment.OSVersion.Version.Major < 6)
      {
        if (systemProcesses.Length > 0)
          error.Append("System up time:    " +
            (DateTime.Now - systemProcesses[0].StartTime).ToString() + Environment.NewLine);
      }
      error.Append("App up time:       " +
        (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString() + Environment.NewLine + Environment.NewLine);
      error.Append("Exception classes:   " + Environment.NewLine);
      error.Append(GetExceptionTypeStack(exception));
      error.Append("   " + exception.GetType().ToString() +
        Environment.NewLine + Environment.NewLine);

      error.Append("Exception messages: " + Environment.NewLine);
      error.Append(GetExceptionMessageStack(exception));
      error.Append("   " + exception.Message + Environment.NewLine);

      error.Append(Environment.NewLine);
      error.Append("Stack Traces:");
      error.Append(Environment.NewLine);
      error.Append(this.GetExceptionCallStack(exception));
      error.Append(exception.StackTrace);
      error.Append(Environment.NewLine);
      error.Append(Environment.NewLine);
      error.Append("Loaded Modules:");
      error.Append(Environment.NewLine);
      Process thisProcess = Process.GetCurrentProcess();
      foreach (ProcessModule module in thisProcess.Modules)
      {
        error.Append(module.FileName + " " + module.FileVersionInfo.FileVersion);
        error.Append(Environment.NewLine);
      }
      error.Append(Environment.NewLine);
      error.Append(Environment.NewLine);
      error.Append(Environment.NewLine);


      for (int i = 0; i < loggers.Count; i++)
      {
        loggers[i].LogError(error.ToString());
      }
    }
  }
}