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 1.1 version. There is also a .NET 2 version, which is more powerful and flexible.
To use it just add the following code to your Windows Forms app, before the call to Application.Run().
ExceptionLogger logger = new ExceptionLogger();
Set the LogType property to set to a different type of logging.
Logging to a website requires setting the Url and QueryString properties, which would be set to something like "http://www.yourwebsite.com/reportbug.aspx" and "bug={0}" respectively. Data is posted via a HTTP POST request, so if your website is using ASP.NET, the exception data can be accessed via the request's Params property.
Oh yes, almost forgot, this was inspired by the original Delphi version by Madshi, which is much cleverer than my version.
using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Net; using System.Reflection; using System.Runtime.Remoting.Messaging; using System.Text; using System.Threading; using System.Web; using System.Windows.Forms; using System.Globalization; namespace FreeFlow.WinFormsControls.HelperClasses { /// <summary>Where exceptions will be logged to</summary> public enum ExceptionLogType { /// <summary>Log to text file</summary> TextFile, /// <summary>Log to event log</summary> EventLog, /// <summary>Log to a website</summary> WebSite } /// <summary> /// Class used to log unhandled exceptions. /// </summary> public class ExceptionLogger { /// <summary> /// Initializes a new instance of the <see cref="ExceptionLogger"/> class. /// Typically this will be one of the first things to be created in the application's Main() method. /// </summary> public ExceptionLogger() { Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(OnThreadException); } private string[] LoadedAssembliesAsViewableList() { ArrayList assemblies = new ArrayList(); AppDomain currentDomain = AppDomain.CurrentDomain; Assembly[] assems = currentDomain.GetAssemblies(); foreach (Assembly assem in assems) { AssemblyName assName = assem.GetName(); assemblies.Add(assName.Name + " " + assName.Version.ToString()); } assemblies.Sort(); return (string[])assemblies.ToArray(typeof(string)); } private string GetExceptionStack(Exception e) { StringBuilder message = new StringBuilder(); message.Append(e.Message); while (e.InnerException != null) { e = e.InnerException; message.Append(Environment.NewLine); message.Append(e.Message); } return message.ToString(); } delegate void LogExceptionDelegate(Exception e); // Event handler that will be called when an unhandled // exception is caught private void OnThreadException(object sender, ThreadExceptionEventArgs e) { switch (logType) { case ExceptionLogType.WebSite : if (MessageBox.Show("An unexpected error occurred - " + e.Exception.Message + ". Do you want to submit an error report?", "Error", MessageBoxButtons.YesNo) == DialogResult.No) return; break; case ExceptionLogType.EventLog : MessageBox.Show("An unexpected error occurred - " + e.Exception.Message + ". It will be logged to the event log", "Error"); break; case ExceptionLogType.TextFile : MessageBox.Show("An unexpected error occurred - " + e.Exception.Message + ". It will be logged to the text file 'BugReport.txt'", "Error"); break; default : Debug.Assert(false, "Unrecognised log type - " + logType.ToString()); break; } // Log the exception LogExceptionDelegate logDelegate = new LogExceptionDelegate(LogException); logDelegate.BeginInvoke(e.Exception, new AsyncCallback(LogCallBack), null); } private void LogCallBack(IAsyncResult result) { LogExceptionDelegate logDelegate = (LogExceptionDelegate)((AsyncResult)result).AsyncDelegate; logDelegate.EndInvoke(result); } private ExceptionLogType logType; /// <summary> /// Gets or sets the type of logging that will occur. /// </summary> public ExceptionLogType LogType { get { return logType; } set { logType = value; } } /// <summary> /// writes exception details to a log file /// </summary> private void LogException(Exception exception) { StringBuilder error = new StringBuilder(); 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 (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); error.Append("Exception class: " + exception.GetType().ToString() + Environment.NewLine); error.Append("Exception message: " + GetExceptionStack(exception) + Environment.NewLine); error.Append(Environment.NewLine); error.Append("Stack Trace:"); error.Append(Environment.NewLine); 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); switch (logType) { case ExceptionLogType.TextFile : LogToFile(error.ToString()); break; case ExceptionLogType.EventLog : LogToEventLog(error.ToString()); break; case ExceptionLogType.WebSite : LogToWebsite(error.ToString()); break; default : Debug.Assert(false); break; } } private void LogToEventLog(string error) { EventLog log = new EventLog("Application"); log.Source = Assembly.GetExecutingAssembly().ToString(); log.WriteEntry(error, EventLogEntryType.Error); } private void LogToFile(string error) { string filename = Path.GetDirectoryName(Application.ExecutablePath); filename += "\\BugReport.txt"; ArrayList data = new ArrayList(); lock (this) { if (File.Exists(filename)) { using (StreamReader reader = new StreamReader(filename)) { string line = null; do { line = reader.ReadLine(); data.Add(line); } while (line != null); } } // truncate the file if it's too long int writeStart = 0; if (data.Count > 500) writeStart = data.Count - 500; using (StreamWriter stream = new StreamWriter(filename, false)) { for (int i = writeStart; i < data.Count; i++) { stream.WriteLine(data[i]); } stream.Write(error); } } } private string url; /// <summary> /// Gets or sets the URL that will be used when posting an error to a website. /// </summary> public string Url { get { return url; } set { url = value; } } private string queryString; /// <summary> /// Gets or sets the format of the query string that will be used when posting an error to a website. /// e.g error={0} /// </summary> public string QueryString { get { return queryString; } set { queryString = value; } } private void LogToWebsite(string error) { Uri uri = new Uri(url); HttpWebRequest httpWebRequest = (HttpWebRequest) WebRequest.Create(uri); httpWebRequest.Method = "POST"; httpWebRequest.ContentType = "application/x-www-form-urlencoded"; Encoding encoding = Encoding.Default; string parameters = string.Format(queryString, HttpUtility.UrlEncode(error)); // get length of request (may well be a better way to do this) MemoryStream memStream = new MemoryStream(); StreamWriter streamWriter = new StreamWriter(memStream, encoding); streamWriter.Write(parameters); streamWriter.Flush(); httpWebRequest.ContentLength = memStream.Length; streamWriter.Close(); Stream stream = httpWebRequest.GetRequestStream(); streamWriter = new StreamWriter(stream, encoding); streamWriter.Write(parameters); streamWriter.Close(); using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { streamReader.ReadToEnd(); } } } }