Hey :)

Uhm, I just finished a program today, and I was wondering, how i should retrieve the errors that the user may encounter :S Ive seen the logo { sa } somewhere, what does it stand for? Or what should i do about this :)
Cheers
-Jazerix

Recommended Answers

All 9 Replies

First you must catch the error (that seems obvious)
Then I normally write a plain text file with all the relevant info for the error like:
*Point in the program.class.function where the error occured
*Description of the error, ie exception class, exception message and exception stack
*Variables affected and their current content

Then I name the file with the application name, username, date and time (using microseconds) and the extension of log.

This file can be written in a predefined place in the user PC (preferred) or in a predefined place in a server (you can have communication and/or permissions problems here).

If you write it in the user's pc, you can ask the user to send the textfile by mail to you.

Hope this helps

Logging is the most effective method as lola pointed out. To get access to the Microsoft bug reports you need to pay them a yearly fee.

Sounds like i should try it out :), however, how can i get it to tell me what line it bugged at?

I suggest an existing logging implementation called log4net it is a very complete and awesome logging framework.

i found a way to get the line number, however, is there a way to do the "catch" of the whole program and not just every event?

Yes, but I suggest not doing so and therefore will not say.

"Catching" your entire program is ridiculously bad design and implementation. Fix your bugs, don't hide them.

IE: You have a program that opens a file, reads it into a buffer, copies the buffer contents into a richtextbox, higlights the word 'World' if any, and wais for the user to exit it.

If you do a global try catch probably you will have a generic Exception like: Null reference found.

The stack trace, maybe is not enough to determine the right place where this occurs.

Instead, if you do a try catch for every relevant action, you can determine the kind of exception and the action to do in this specific case. IE: determine if the open file function files is due to a bad name or a lack of permissions; in the first case, you can interactively ask to the user for a well formed file name.

As I mentioned before, you will need to have information about the global and local variables content to 'clarify' the origin of the error. In a global try catrch you will loose the info for local variables in other modules, or inside functions or methods.

And yes, it is a lot of work. :(

Hope this helps

Yes, but I suggest not doing so and therefore will not say.

"Catching" your entire program is ridiculously bad design and implementation. Fix your bugs, don't hide them.

I wholeheartedly disagree. Per Microsoft if you want your software to be logo certified (Certified for Windows 7, etc) you are not supposed to catch all unhandled exceptions but having your own internal bug reporting mechanism is far better than dealing with Microsoft's WinQual.

However you shouldn't just ignored WER (Windows Error Reporting) as some exceptions you'll never have an opportunity to collect but will wind up on a WinQual. If you have a code signing certificate and sign your assemblies then check out WinQual.

The user interface is somewhat cumbersome to deal with but they expose web APIs to gather WER data. Check out StackHash. Its free and syncs with WER to grab CAB files and other crash data.

As far as handling application crashes internally here is what I do. There is a lot of code below but the two main "catch all" event subscriptions you care about would be Application.ThreadException and AppDomain.CurrentDomain.UnhandledException Here is a typical program startup:

sealed class Program : IApplicationExceptionProvider
  {
    Program() { }
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      DebugHelper.Configure();
      Program prog = new Program();
      if (!Vea.Extensions.Forms.Log4NetConfigure.ConfigureLog4Net(args))
        return;

      var reporter = ExceptionReporter.WireUpForUnhandledExceptions(prog);
      //continues
    }

    #region IApplicationExceptionProvider Members
    string IApplicationExceptionProvider.FormSkinName
    {
      get { return CFG.FormSkinName; }
    }
    bool IApplicationExceptionProvider.FormSkinsEnabled
    {
      get { return CFG.FormSkinsEnabled; }
    }
    Vea.Extensions.LK.SensourceLicense IApplicationExceptionProvider.LicenseKey
    {
      get { return CFG.Key; }
    }
    string IApplicationExceptionProvider.LastConnectionString
    {
      get { return Vea.Data.QueryHistory.LastConnStr; }
    }
    string IApplicationExceptionProvider.LastQuery
    {
      get { return Vea.Data.QueryHistory.LastQuery; }
    }
    #endregion

Then for reporting (there was a bit of code removed explaining why so many empty catches -- it has a few different paths):

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Vea.Extensions;
using Vea.Extensions.LK;

namespace Vea.Extensions.Forms
{
  public sealed class ExceptionReporter : IDisposable
  {
    //
    readonly IApplicationExceptionProvider provider;
    readonly log4net.ILog log;
    bool m_enabled;
    public bool Enabled
    {
      get { return m_enabled; }
      private set { m_enabled = value; }
    }
    /* -------------------------------------------------------------------- */    
    ExceptionReporter()
      : base()
    {
      this.Enabled = false;
    }
    /* -------------------------------------------------------------------- */
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
    ExceptionReporter(IApplicationExceptionProvider provider)
      :this()
    {
      if (provider == null)
        throw new ArgumentNullException("provider");
      this.provider = provider;
      log = log4net.LogManager.GetLogger(provider.GetType());
      WireUp();
    }
    void WireUp()
    {
#if !DEBUG
      m_enabled = true;
      if (m_enabled)
      {
        Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
      }
#endif
    }
    /* -------------------------------------------------------------------- */
    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
      Exception ex = e.ExceptionObject as Exception;
      if (e.IsTerminating)
      {
        if (log.IsFatalEnabled) log.Fatal("Unhandled application exception (terminating)", ex);
      }
      else
      {
        if (log.IsErrorEnabled) log.Error("Unhandled application exception (non-terminating)", ex);
      }
      
      if (ex != null)
      {
        WriteExceptionRunTime(ex, false);
      }
    }
    /* -------------------------------------------------------------------- */
    #region IDisposable Members
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
    public void Dispose()
    {
      if (m_enabled)
      {
        Application.ThreadException -= new ThreadExceptionEventHandler(ThreadException);
        AppDomain.CurrentDomain.UnhandledException -= new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
      }
    }
    #endregion
    /* -------------------------------------------------------------------- */
    void ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {
      WriteExceptionRunTime(e.Exception, true);
    }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
    void WriteExceptionRunTime(Exception error, bool showMessage)
    {
      if (log.IsFatalEnabled) log.Fatal("Unhandled application exception", error);
      try
      {
        string msg =
          "date/time         : " + DateTime.Now.ToString() + Environment.NewLine +
          "computer name     : " + Environment.MachineName + Environment.NewLine +
          "user name         : " + Environment.UserName + Environment.NewLine +
          "operating system  : " + Environment.OSVersion.VersionString + Environment.NewLine +
          "domain user name  : " + Environment.UserDomainName + Environment.NewLine +
          "system up time    : " + VeaUtils.GetSystemUptime() + Environment.NewLine +
          "program up time   : " + VeaUtils.GetProgramUptime() + Environment.NewLine +
          "allocated memory  : " + (System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString() + " MB" + Environment.NewLine +
          "physical memory   : " + VeaUtils.GetTotalMemory() + Environment.NewLine +
          "processor         : " + VeaUtils.GetCpuInformation() + Environment.NewLine +
          "display mode      : " + SystemInformation.PrimaryMonitorSize + Environment.NewLine +
          "executable        : " + System.IO.Path.GetFileName(Application.ExecutablePath) + Environment.NewLine +
          "exec. date/time   : " + System.Diagnostics.Process.GetCurrentProcess().StartTime.ToString() + Environment.NewLine +
          "path              : " + Application.ExecutablePath + Environment.NewLine +
          "version           : " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + Environment.NewLine +
          "exception class   : " + error.GetType() + Environment.NewLine +
          "exception message : " + error.Message + Environment.NewLine +
          "framework ver     : " + Environment.Version + Environment.NewLine +
          "skin name         : " + provider.FormSkinName + Environment.NewLine +
          "form skins enabled: " + (provider.FormSkinsEnabled ? "Yes" : "No") + Environment.NewLine +
          "Company           : " + (provider.LicenseKey != null ? provider.LicenseKey.Company : "Unknown?") + Environment.NewLine +
          Environment.NewLine +
          "Stack Trace: " + Environment.NewLine +
          error.GetErrorText() + Environment.NewLine +
          Environment.NewLine +
          "Environment Stack Trace: " + Environment.NewLine +
          Environment.StackTrace + Environment.NewLine +
          Environment.NewLine +
          "Processes: " + Environment.NewLine +
          VeaUtils.GetProcessList() + Environment.NewLine +
          Environment.NewLine +
          "Referenced Assemblies: " + Environment.NewLine +
          VeaUtils.GetReferencedAssemblies() + Environment.NewLine +
          Environment.NewLine +
          "Connection String: " + provider.LastConnectionString + Environment.NewLine +
          "Last Query: " + Environment.NewLine +
          provider.LastQuery;

        try
        {
          Vea.Extensions.VeaUtils.PostRpt(msg);
        }
        catch { } //May consider rethrowing the exception so it is submitted to WinQual

        try
        {
          string bugRptfile = Vea.Extensions.Forms.Functions.GetBugRptFileName();
          using (StreamWriter writer = new StreamWriter(bugRptfile))
          {
            writer.WriteLine(msg);
            writer.Close();
          }
        }
        catch { }
      }
      catch { }

      if (showMessage)
      {
        DevExpress.XtraEditors.XtraMessageBox.Show("An unhandled exception has occured and the application must be restarted." + Environment.NewLine + error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        Application.Exit();
      }
    }
    /* -------------------------------------------------------------------- */
    public static ExceptionReporter WireUpForUnhandledExceptions(IApplicationExceptionProvider exceptionProvider)
    {
      if (exceptionProvider == null)
        throw new ArgumentNullException("ExceptionProvider");

      return new ExceptionReporter(exceptionProvider);
    }
    /* -------------------------------------------------------------------- */
  }
}

In another unit I handle submitting the message back to a bug server and I have a separate server side component for accepting inbound bug reports then logging them to an SQL database and one last client application to view bug report feedback.

Its optional and the user can elect whether to submit reports back or not.

This is a controversial topic and I agree its not the "best thing" to do but it works 98% of the time and for the %2 of failures (ex OutOfMemoryException) it will probably wind up on winqual anyway.

commented: Thanks a lot! +3

Thanks a lot! I will have to look into it in the morning, as it getting late here in denmark, how ever thanks! :D

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.