18

In .NET, the default exception handler will let the user continue running the program. However, I'd like to have a global exception handler that saves the stack trace to an "errorlog.txt" file so the user can send it to me and doesn't have to remember to click "Details" and copy it out of the dialog (and remove all the useless crap about loaded assemblies and such). But when I do this, the code doesn't know how to continue, so all I can do is exit the app. Is there any way to have the best of both worlds? (Yes, I know what I'm asking for is essentially "On Error Resume Next" with logging, but I really think it would be useful!)

ekolis
  • 6,270
  • 12
  • 50
  • 101
  • 7
    What kind of application are you working with? WinForms, WPF, ASP.NET, etc.? – Justin Niessner Sep 24 '13 at 14:32
  • 2
    http://msdn.microsoft.com/en-us/library/system.windows.forms.application.threadexception.aspx. – SLaks Sep 24 '13 at 14:32
  • 3
    How would your your app continue running after a thread abort or access violation exception? – asawyer Sep 24 '13 at 14:33
  • 3
    @asawyer Or, maybe a Stack Overflow? ;) – Kendall Frey Sep 24 '13 at 14:34
  • possible duplicate of [Is it possible to continue running code from the point of failure?](http://stackoverflow.com/questions/18595060/is-it-possible-to-continue-running-code-from-the-point-of-failure) – Daniel A. White Sep 24 '13 at 14:35
  • It's a WinForms app. If there is a critical error that can't be caught, it might as well crash, but I'd like to at least catch the ones that I can. – ekolis Sep 25 '13 at 17:45

8 Answers8

34
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

If you bind yourself to this event when the application starts, you should be able to catch any Unhandled Exception your application throws, and save it to a file. (Use the Exception object part of the UnhandledExceptionEventArgs. I do not believe it is possible to resume from where the error Occurred.

Sam
  • 7,252
  • 16
  • 46
  • 65
Samuel
  • 710
  • 5
  • 8
  • Thanks, but I've already got what amounts to a try/catch in the Main method, which does the same thing as what you suggested, if I'm not mistaken... – ekolis Sep 25 '13 at 17:50
  • This is NOT what OP asked for... "global exception handler" not "global unhandled exception handler"... – Yousha Aleayoub Dec 02 '19 at 13:02
3

You can write a global exception handler method that you call in every catch block, which writes the stack trace where ever you want to save it. But you'd need to write try . . . catch blocks for every operation that needs them and call the exception handler in each.

You can also call that global exception handler method in the MyApplication.UnhandledException handler for all unhandled events. But when control gets to that method in that case, the program is not going to continue running.

Tony Vitabile
  • 8,298
  • 15
  • 67
  • 123
  • Too bad C# doesn't have checked exceptions so I can't miss any... that feature is actually somewhat useful in Java, if annoying at times! – ekolis Sep 25 '13 at 17:51
  • 1
    Just to note, you can continue execution by setting "Handled" to true on the event arg. Your app may be in a very weird state, of course, but you could in theory recover. – Egor Apr 10 '14 at 23:02
2

In a Winform app you can attach an handler to the Application.ThreadException event (make sure you do before calling Application.Run()). This will get rid of the standard exception dialog (the one with the "details" button) and give you the power to display / log anything you want.

However, remember that this will work ONLY for exceptions thrown within the UI thread. Exceptions raised from a background thread won't be reachable from your handler. These can still be caught by AppDomain.UnhandledException handler, though.

Liam
  • 27,717
  • 28
  • 128
  • 190
Crono
  • 10,211
  • 6
  • 43
  • 75
  • 1
    Some advice, now: even though this IMHO does answer your question, I don't recommend that you do that. You really should make sure your app will end on an unhandled exception, by using Application.SetUnhandledExceptionMode method with ThrowException mode. This will disable Application.ThreadException event and the default exception dialog with details, but it will STILL allow AppDomain.UnhandledException to be raised. From there you can display something to your user (watch out for threading issues) and log the exception right before the app ends. This is the way to go, again IMHO. – Crono Sep 25 '13 at 13:49
2

in main constructor, put this and will do it globally:

AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
   MessageBox.Show(eventArgs.Exception.ToString());
};

(Note: as said in comments, this might not be thread-safe).

T.Todua
  • 53,146
  • 19
  • 236
  • 237
1

No that does not exist, exceptions are a flow control construct, so On Error Resume Next is not possible.

You could do your operation in a loop and on an exception, retry your logic.

KandallFrey is right however, you shouldn't use exceptions as flow control, use them only in exceptional cases.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
0

I use My.Application.UnhandledException to open a dialog form where the user can save every information about the exception.

When the form is closed I call e.ExitApplication = False.

tezzo
  • 10,858
  • 1
  • 25
  • 48
  • 1
    I think this is for Visual Basic assemblies only (http://msdn.microsoft.com/en-us/library/3a02k5s0%28v=vs.90%29.aspx). – Surfbutler Sep 24 '13 at 14:57
  • Yes, you're right. For C# users I found this: http://msdn.microsoft.com/it-it/library/microsoft.visualbasic.applicationservices.windowsformsapplicationbase.unhandledexception.aspx – tezzo Sep 24 '13 at 15:12
0

At my firm, I've built an exception handling framework which has served me well for 7 years now. Every assembly references the DLL, and every method in each assembly has a try-catch block. In the catch, I basically have to make one decision based on the question "where do I want my exception handling framework to intervene, i.e. to log the exception data externally and inform the user of the problem?". In most cases, the answer to this question is that I want it to intervene in cases where the method is called externally, e.g. if it's inside an event handler or other delegate. So here is some example code:

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    Try
        Method1()
    Catch ex As Exception
        BSExceptionHandler.ProcessException(BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName))
    End Try
End Sub

Private Sub Method1()
    Try
        method2()
    Catch ex As Exception
        Throw BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName)
    End Try
End Sub

Private Sub method2()
    Try
        Dim x As Integer = CInt("x") 'exception thrown here.
    Catch ex As Exception
        Throw BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName)
    End Try
End Sub

When Throw BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName) is called for the first time in Method2(), the original exception is wrapped inside a BSException, and the original method name is stored as well (more on that later). Any subsequent calls to BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName), e.g. in Method1(), will recognize that the exception is already of type BSException and just throw that instead of re-wrapping it. Ultimately, it reaches the "top" of the call stack (not really the top, just the point at which I've decided I want to process the exception through my framework -- i.e. log it, inform the user, etc.) -- in this case, in Button1_Click -- and then BSExceptionHandler.ProcessException(BSExceptionHandler.GetBSException(ex, BSExceptionHandler.GetThisMethodName)) is called.

What my framework does in ProcessException is to determine the type of reporting configured on the system. This is defaulted to "simple", which means the user gets a very general and minimally-intrusive dialog box indicating a problem has occurred and instructing them to contact the IT department. However, there is an optional registry setting which will set the reporting type to either "verbose" or to "email". I use "verbose" on my development machine, because it includes all the exception info in the dialog box so I don't have to go look at the log. The "email" option is used on servers where I have applications running when there is no user logged on; in that case, the error information is emailed to the IT department to alert them of the issue.

The other thing that happens from here, regardless of the reporting type, is that the error information is recorded to the Windows event log. Here is an example:

SILENT: No

ROOT ASSEMBLY: C:\Users\roryap\AppData\Local\Temporary Projects\WindowsApplication1\bin\Debug\WindowsApplication1.exe

DESCRIPTION: BromsunExceptionHandling.BSException: Calling Method 'WindowsApplication1.Form1.method2()' produced 'System.FormatException' exception with message 'Conversion from string "x" to type 'Integer' is not valid.'.

CALLING METHOD: WindowsApplication1.Form1.method2()

STACK TRACE:    at Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)
   at WindowsApplication1.Form1.method2() in C:\Users\roryap\AppData\Local\Temporary Projects\WindowsApplication1\Form1.vb:line 32

CALL STACK: WindowsApplication1.Form1.method2()
WindowsApplication1.Form1.Method1()
WindowsApplication1.Form1.Button1_Click(sender As System.Object, e As System.EventArgs)

SOURCE: Microsoft.VisualBasic

TARGET SITE: Int32 ToInteger(System.String)

EXTRA INFO: 

When the user reports the issue to IT, all IT has to do is check their event log to get the error info. Furthermore, the application does not exit; it continues running because it never allows the exception to bubble up outside of the point where you've chosen to process the exception with the handling framework. Once it's handled by the framework, it's done.

While there are a few downsides to this approach, I have found it to be an extremely robust system and it has saved me many many hours of blind troubleshooting over the years. I have created snippets so that all I have to do is create a try-catch block then type the snippet shortcut -- e.g. "pbs" or "tbs" -- into the catch-block and hit the tab key to populate the appropriate exception handling method. Therefore, it's quite painless to use this framework in every method. I've even modified my VS project templates to always reference and include the framework so I don't have to do that for every new project.

So, regarding the GetThisMethodName() function: this method uses System.Diagnostics.StackTrace, System.Diagnostics.StackFrame, and System.Reflection.MethodBase to figure out the name of the method where the original exception was wrapped inside the BSException.

rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • 4
    That sounds... complicated. – ekolis Sep 25 '13 at 17:48
  • 2
    I would not recommend this approach. Only catch exceptions that you are actually going to handle, ie respond to with some logic action, eg retry the save to database or attempt to open the locked file again. If all you are doing is logging the exception then have a global exception handler and log it there (maybe use log4net). You may even make a decision to start handling some of these previously unhandled exceptions – SleepyBoBos Jan 17 '14 at 06:09
  • @SleepyBoBos -- You said, "Only catch exceptions that you are actually going to handle": do you have anything substantive that can back up this claim? – rory.ap Jan 17 '14 at 13:49
  • @SleepyBoBos -- Also, the accepted answer is basically suggesting the same approach, just with a lot less detail. – rory.ap Jan 17 '14 at 13:51
  • SleepyBoBos is absolutely right. Catching every possible `Exception` regardless of its nature means that object instances may remain in a corrupted state, potentially causing others, MUCH harder errors to debug and - worst of all - data loss caused on inaccurate check conditions (caused by state corruption). – Crono Sep 25 '18 at 15:25
  • Backing up the claim is [this article](https://msdn.microsoft.com/en-us/magazine/mt620018.aspx?f=255&MSPPError=-2147217396) from Visual Studio Magazine. – Crono Sep 25 '18 at 15:26
  • Some pointers from the aforementioned article: *AVOID catching exceptions that you’re unable to handle fully.* (...) *Rarely use System.Exception and general catch blocks—except to log the exception before shutting down the application.* – Crono Sep 25 '18 at 15:28
  • I no longer use this approach. What I do now is similar to Samuel's answer here. https://stackoverflow.com/a/18984584/2704659 – rory.ap Sep 25 '18 at 15:47
0

For me works this solution:

  1. In WebApiConfig.cs add:
public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
            ...
        }
  1. Create new file UnhandledExceptionLogger.cs:
    public class UnhandledExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            File.AppendAllText("log.txt", context.Exception.ToString());
        }
    }
Kraken
  • 39
  • 5