Exception reporting from a WPF Application

2019-03-15 06:23发布

So lets say I happen to have an unhandled exception in my application or it crashes for some reason. Is there some way for me to capture the output and show an error report dialog when the application crashes.

What I'm thinking is having a small program running in the background, which only job is to listen for an abnormal exit of the main application and then show the 'report' dialog where the user could choose to email me the output of the error.

Not really sure how to implement this, or if this is the right way to do it.

Reporting the error message would be an easy task, but I have no idea how to capture the output of an unhandled exception or grab the exit codes (I'm assuming the program would give an exit code other then 0 when it crashes).

Would be great to have a nudge in the right direction.

4条回答
虎瘦雄心在
2楼-- · 2019-03-15 06:32

You should handle the Application.ThreadException event.

查看更多
三岁会撩人
3楼-- · 2019-03-15 06:35

You can use NBug library for that (also you can use the nuget package here for easy installation). Simply install the NuGet package and configure it like below:

NBug.Settings.Destination1 =
  "Type=Mail;From=me@mycompany.com;To=bugtracker@mycompany.com;SmtpServer=smtp.mycompany.com;";
AppDomain.CurrentDomain.UnhandledException += NBug.Handler.UnhandledException;
Application.Current.DispatcherUnhandledException += NBug.Handler.DispatcherUnhandledException;

Now all unhandled exceptions will be nicely caught and giftwrapped for you and you'll get full details of the exception via email. Use the configurator tool to make sure that your configuration is correct.

查看更多
男人必须洒脱
4楼-- · 2019-03-15 06:37

Your best chance is inside the application. There are two hooks:

The proper place to 'catch-all' depends on your application semantics and is difficult to say where you should put it w/o knowing your application. Application need to also set the Application.SetUnhandledExceptionMode.

Having an external watch dog is less useful because it cannot give any meaningful information why did the application crash. By the time it detect an 'unexpected' exit (how does it knows is 'unexpected'?) is way too late to collect any useful information. With an inside handler you can collect the exception and the stack and submit them to an analysis service like bugcollect.com and then you'll have a leg ahead in understanding now only what happened, but also how often it happens and which deployment are affected (where it happens). There are other similar services like exceptioneer.com or the Windows Error Reporting (this one requires your code to be signed by a trusted authority certificate like Verisign). Relying on a service for collection of incidents is far superior to sending mail, you don't want to wake up and find 2k incident emails in your inbox and start sifting through them to understand what happened.

And a final world: don't reinvent the wheel: there are already many frameworks to collect and log exceptions, like log4net and elmah.

查看更多
神经病院院长
5楼-- · 2019-03-15 06:51

The answers involving the AppDomain.UnhandledException event probably make an implicit assumption any unhandled exception is raised on WPF's UI thread. This means that, while they'll often work, they're not formally safe for use with operations on other threads. A more reliable option is Application.DispatcherUnhandledException, which WPF provides for applications to implement custom reporting of unhandled exceptions across the main UI thread, background UI threads, and BackgroundWorker instances.

A likely failure point with AppDomain.UnhandledException is the report dialog probably requires a single-threaded apartment (STA) since both WPF and Windows Forms are STA. As thread pool threads default to multithreaded apartments, an unhandled exception from an asynchronous operation under Task.Run(), ThreadPool.QueueUserWorkItem(), IProgress<T>.Report(), or the many similar APIs will result upon instantiation of a report dialog failing with something like the exception below. This causes an application crash without a chance to prompt the user to report the underlying problem.

System.InvalidOperationException: The calling thread must be STA, because many UI components require this.
   at System.Windows.Input.InputManager..ctor()
   at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
   at System.Windows.Input.KeyboardNavigation..ctor()
   at System.Windows.FrameworkElement.FrameworkServices..ctor()
   at System.Windows.FrameworkElement.EnsureFrameworkServices()
   at System.Windows.FrameworkElement..ctor()
   at System.Windows.Controls.Control..ctor()
   at System.Windows.Window..ctor()

My experience of Application.DispatcherUnhandledException is it's robust in combination with the TPL as Task and its associated classes facilitate propagation of exceptions back to the caller. To summarize the TPL's exception handling, Wait() and await rethrow automatically and callers using other synchronization methods should check Task.Exception.

However, as Application.DispatcherUnhandledException's documentation points out, other cases require WPF's caller to implement exception propagation. Perhaps the most common of these is WPF's Progress<T>, which strangely lacks support for propagating exceptions from within its implementation of IProgress<T>.Report() even though its sole purpose is moving progress information from worker threads back to the UI. One workaround is to wrap the progress update handler using an approach similar to the example below. This is just a sketch; more frequent polling of the Exception property may be valuable in halting on errors, the stronger semantics of IDisposable may be preferable to End(), and it might be useful to handle cases where a backlog of updates fail concurrently differently.

public class ExceptionPropagatingProgress<TProgress>
{
    private readonly Action<TProgress> onProgressUpdateCore;
    private readonly IProgress<TProgress> progress;

    public Exception Exception { get; private set; }

    public ExceptionPropagatingProgress(Action<TProgress> handler)
    {
        this.Exception = null;
        this.onProgressUpdateCore = handler ?? throw new ArgumentNullException(nameof(handler));
        this.progress = new Progress<TProgress>(this.OnProgressUpdate);
    }

    public void End()
    {
        if (this.Exception != null)
        {
            throw new AggregateException(this.Exception);
        }
    }

    private void OnProgressUpdate(TProgress value)
    {
        try
        {
            this.onProgressUpdateCore(value);
        }
        catch (Exception exception)
        {
            lock (this.onProgressUpdateCore)
            {
                if (this.Exception == null)
                {
                    this.Exception = exception;
                }
                else
                {
                    this.Exception = new AggregateException(this.Exception, exception);
                }
            }
        }
    }

    public void QueueProgressUpdate(TProgress value)
    {
        if (this.Exception != null)
        {
            throw new AggregateException(this.Exception);
        }

        this.progress.Report(value);
    }
}
查看更多
登录 后发表回答