31

I have a website built in C#.NET that tends to produce a fairly steady stream of SQL timeouts from various user controls and I want to easily pop some code in to catch all unhandled exceptions and send them to something that can log them and display a friendly message to the user.

How do I, through minimal effort, catch all unhandled exceptions?

this question seems to say it's impossible, but that doesn't make sense to me (and it's about .NET 1.1 in windows apps):

Community
  • 1
  • 1
adambox
  • 24,261
  • 9
  • 32
  • 34
  • 4
    Isn't that treating the symptoms, and not the cause? i.e. shouldn't you be looking at the cause of the SQL timeouts by running a SQL Profiler trace on the database server? – Mitch Wheat Nov 17 '08 at 14:58
  • 1
    I think you need to clarify what you mean by "catch": As in try..catch where you can handle the exception, or if you merely want to be informed an unhandled exception occurred, and log where/when (e.g. ELMAH) – Robert Paulson Jul 29 '09 at 06:05
  • Can you mark one of the answers as the correct one? – ceztko Aug 20 '18 at 16:15

13 Answers13

28

All unhandled exceptions finally passed through Application_Error in global.asax. So, to give general exception message or do logging operations, see Application_Error.

Ali Ersöz
  • 15,860
  • 11
  • 50
  • 64
  • 10
    look into the the first sentence; "I have a website built in C#.NET that tends..." – Ali Ersöz Nov 17 '08 at 22:03
  • 3
    To anyone trying to figure out what the previous comment is saying - I assume it's answering a now deleted comment. (Hint: see who wrote the answer and who wrote the comment.) – ispiro May 25 '14 at 13:39
  • You may need to set `` in web.config. Without this entry, it wasn't working on production server for me. – ghord Nov 26 '14 at 07:05
16

If you need to catch exeptions in all threads the best aproach is to implement UnhandledExceptionModule and add it to you application look here for an example

MichaelT
  • 7,574
  • 8
  • 34
  • 47
15

Use the Application_Error method in your Global.asax file. Inside your Application_Error method implementation call Server.GetLastError(), log the details of the exception returned by Server.GetLastError() however you wish.

e.g.

void Application_Error(object sender, EventArgs e) 
{ 
    // Code that runs when an unhandled error occurs
    log4net.ILog log = log4net.LogManager.GetLogger(typeof(object));
    using (log4net.NDC.Push(this.User.Identity.Name))
    {
        log.Fatal("Unhandled Exception", Server.GetLastError());
    }
}

Don't pay too much attention to the log4net stuff, Server.GetLastError() is the most useful bit, log the details however you prefer.

Dan Powley
  • 743
  • 4
  • 11
  • For me, I also had to set to customErrors mode to Off in my Web.config. Otherwise it would still not work. Unfortunately, I did not realize this was required. Something tells me I need to find and start reading documentation more carefully. – DankMemester 'Andrew Servania' Dec 04 '18 at 11:41
8

The ELMAH project sounds worth a try, its list of features include:

ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.

  • Logging of nearly all unhandled exceptions.
  • A web page to remotely view the entire log of recoded exceptions.
  • A web page to remotely view the full details of any one logged exception.
  • In many cases, you can review the original yellow screen of death that ASP.NET generated for a given exception, even with customErrors mode turned off.
  • An e-mail notification of each error at the time it occurs.
  • An RSS feed of the last 15 errors from the log.
  • A number of backing storage implementations for the log

More on using ELMAH from dotnetslackers

Dan Powley
  • 743
  • 4
  • 11
7

You can subscribe to the AppDomain.CurrentDomain.UnhandledException event.

P Daddy
  • 28,912
  • 9
  • 68
  • 92
  • As of 2.00 this doesn't catch the exception, just gives you an opportunity to log it. However it did back in 1.0 -> 1.1 but that was a bug fixed in 2.00. I think you also need Application.ThreadException or something like that too for full coverage. – Quibblesome Nov 17 '08 at 15:36
  • Agreed, but the OP's intention seems to be for logging and redirecting, not trapping. And Application.ThreadException is for WinForms, not WebForms. – P Daddy Nov 17 '08 at 15:38
  • I believe that Quarrelsome is correct because the OP's question refers to Windows apps rather than web apps. – HTTP 410 Nov 17 '08 at 17:12
  • 4
    Um, "I have a website built in C#.NET". – P Daddy Nov 17 '08 at 19:55
  • 2
    I am indeed talking about a web app – adambox Nov 17 '08 at 21:04
3

It's probably important to note that you are not supposed to catch unhandled exceptions. If you are having SQL timeout issues, you should specifically catch those.

Adam Tegen
  • 25,378
  • 33
  • 125
  • 153
  • Your argument "It's probably important to note that you are not supposed to catch unhandled exceptions." is completely illogical(no references, reasons, etc). Also, have you heard of the `throw` statement? – Kelly Elton Oct 30 '12 at 20:29
  • I'll provide more information. Microsoft doesn't want you to catch(Exception). You will fail your windows logo certification. You aren't supposed to throw new Exception either, you are typically supposed to throw an ApplicationException. You are always supposed to catch the most specific exception that makes sense. – Adam Tegen Oct 31 '12 at 22:42
  • 1
    Catching any exception, logging it, then throwing it is useful. I think the worst thing people can do is surround all their code in try-catch's...or even worse dictation flow based on try-catches. For example I have a program that has a delegate attached to UnhandledException, so whenever anything is unhandled, I get the exception. Which I then submit to a web service, and let my software crash. How else are you going to figure out what went wrong? – Kelly Elton Nov 01 '12 at 00:37
  • My apologies, I should have completed my thought. You are fine as long as you rethrow it. catch(exception e){ log(e); } is bad. catch(exception e){ log(e); throw; } is fine. – Adam Tegen Nov 02 '12 at 02:11
2

Do you mean handling it in all threads, including ones created by third-party code? Within "known" threads just catch Exception at the top of the stack.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

Timeout errors typically occur if you are not forcefully closing your sqlconnections.

so if you had a

try {
    conn.Open();
    cmd.ExecuteReader();
    conn.Close();
} catch (SqlException ex) {
    //do whatever
}

If anything goes wrong with that ExecuteReader your connection will not be closed. Always add a finally block.

try {
    conn.Open();
    cmd.ExecuteReader();
    conn.Close();
} catch (SqlException ex) {
    //do whatever
} finally {
    if(conn.State != ConnectionState.Closed)
       conn.Close();
}
Robert Paulson
  • 17,603
  • 5
  • 34
  • 53
Jack Marchetti
  • 15,536
  • 14
  • 81
  • 117
  • 1
    -1: You're four eight months late, and you don't implement `using` blocks, which are the right way to avoid the problem you're talking about. – John Saunders Jul 29 '09 at 01:09
  • aren't using blocks doing the same thing s finally? – Jack Marchetti Jul 29 '09 at 01:18
  • btw from C# Reference: The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object. You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler. – Jack Marchetti Jul 29 '09 at 01:22
  • 2
    You can acheive the same thing, but the next person coming along will mess it up. Consistent use of `using` blocks whenever you instantiate a class implementing IDisposable is the best way to prevent those resource leaks. And it's more important on a site like this, where less-experienced developers may copy-and-paste code "examples" from those of us who should know better. Let me know if you want me either to clean yours up, or to create a separate answer, clean, but referring to yours. – John Saunders Jul 29 '09 at 02:20
  • While @John Saunders is right about using blocks, you still need to wrap cmd.ExecuteReader in try..catch if you want to handle the exception (even if it's just for logging). – Robert Paulson Jul 29 '09 at 05:54
  • In the finally block, You shouldn't need to check conn.State. The IDisposable guidelines indicate Dispose() (or Close() in this case) should be able to be called more than once without side-effect or exceptions. http://msdn.microsoft.com/en-us/magazine/cc163392.aspx and http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae – Robert Paulson Jul 29 '09 at 05:57
1

One short answer is to use (Anonymous) delegate methods with common handling code when the delegate is invoked.

Background: If you have targeted the weak points, or have some boilerplate error handling code you need to universally apply to a particular class of problem, and you don't want to write the same try..catch for every invocation location, (such as updating a specific control on every page, etc).

Case study: A pain point is web forms and saving data to the database. We have a control that displays the saved status to the user, and we wanted to have common error handling code as well as common display without copy-pasting-reuse in every page. Also, each page did it's own thing in it's own way, so the only really common part of the code was the error handling and display.

Now, before being slammed, this is no replacement for a data-access layer and data access code. That's all still assumed to exist, good n-tier separation, etc. This code is UI-layer specific to allow us to write clean UI code and not repeat ourselves. We're big believers in not quashing exceptions, but certain exceptions shouldn't necessitate the user getting a generic error page and losing their work. There will be sql timeouts, servers go down, deadlocks, etc.

A Solution: The way we did it was to pass an anonymous delegate to a method on a custom control and essentially inject the try block using anonymous delegates.

// normal form code.
private void Save()
{
    // you can do stuff before and after. normal scoping rules apply
    saveControl.InvokeSave(
        delegate
        {
            // everywhere the save control is used, this code is different
            // but the class of errors and the stage we are catching them at
            // is the same
            DataContext.SomeStoredProcedure();
            DataContext.SomeOtherStoredProcedure();
            DataContext.SubmitChanges();
        });
}

The SaveControl itself has the method like:

public delegate void SaveControlDelegate();

public void InvokeSave(SaveControlDelegate saveControlDelegate)
{        
    // I've changed the code from our code. 
    // You'll have to make up your own logic.
    // this just gives an idea of common handling.
    retryButton.Visible = false;
    try
    {
        saveControlDelegate.Invoke();
    }
    catch (SqlTimeoutException ex)
    {
        // perform other logic here.
        statusLabel.Text = "The server took too long to respond.";
        retryButton.Visible = true;
        LogSqlTimeoutOnSave(ex);
    }
    // catch other exceptions as necessary. i.e.
    // detect deadlocks
    catch (Exception ex)
    {
        statusLabel.Text = "An unknown Error occurred";
        LogGenericExceptionOnSave(ex);
    }
    SetSavedStatus();
}

  • There are other ways to achieve this (e.g. common base class, intefaces), but in our case this had the best fit.
  • This isn't a replacement to a great tool such as Elmah for logging all unhandled exceptions. This is a targeted approach to handling certain exceptions in a standard manner.
Robert Paulson
  • 17,603
  • 5
  • 34
  • 53
1

I'd recommend looking at log4net and seeing if that's suitable for the logging part of the question.

harriyott
  • 10,505
  • 10
  • 64
  • 103
1

If using .net 2.0 framework, I use the built in Health Monitoring services. There's a nice article describing this method here: https://web.archive.org/web/20210305134220/https://aspnet.4guysfromrolla.com/articles/031407-1.aspx

If you're stuck with the 1.0 framework, I would use ELMAH: http://msdn.microsoft.com/en-us/library/aa479332.aspx

hope this helps

1

There are 2 parts to this problem handling & identifying.

Identifying

This is what you do when the exception is finally caught, not necessarily where it is thrown. So the exception at that stage must have enough context information for you to idenitfy what the problem was

Handling

For handling, you can a) add a HttpModeule. See http://www.eggheadcafe.com/articles/20060305.asp I would suggest this approach only when there is absolutely no context informaatn available and there might be issuus wiih IIS/aspnet, In short for catastrophic situations

b) Create a abstract class called AbstractBasePage which derives from Page class and have all your codebehind classes derive from AbstractBasePage

The AbstractBasePage can implement that Page.Error delegate so that all exceptions which percolate up through the n-tier architecture can be caught here(and possibly logged)

I would suggest this cause for the kind of exceptions you are talking about (SQlException) there is enough context information for you to identify that it was a timeout and take possible action. This action might include redirecting user to a custom error page with appropriate message for each different kind of exception (Sql, webservice, async call timeouts etc).

Thanks RVZ

user35559
  • 1,018
  • 3
  • 11
  • 21
0

This is old question, but the best method (for me) is not listed here. So here we are:

ExceptionFilterAttribute is nice and easy solution for me. Source: http://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling.

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var exception = context.Exception;
        if(exception is SqlTimeoutException)
        {
            //do some handling for this type of exception
        }
    }
}

And attach it to f.e. HomeController:

[ExceptionHandling]
public class HomeController: Controller
{

}
Michael
  • 1,067
  • 8
  • 13