1

Our system has an in-house exception handler that can catch, manage, log and notify us of exceptions. But sometimes not all exceptions should be tracked/notified/e-mailed.

For example, if an exception occurred within a method/class I would want the exception handler to be able to recognise that and be able to handle that exception differently.

Initially I tried to implement it with inheritable attributes, but if the attribute is on a method/class somewhere deeper within the stack trace it won't obviously be picked up.

Since I don't think I can implement this with an attribute (right?), I'm thinking to wrap specific code blocks within a using() block, but I have no idea how to check within the exception handler whether the exception happened inside that block.

Is there any way to accomplish what I need?

Artiom Chilaru
  • 11,811
  • 4
  • 41
  • 52

3 Answers3

1

Edit: this approach may not work for handling exceptions

Upon reflection, the solution I describe below may not work for catching and handling exceptions, at least not without some additional effort. The problem is that if you throw an exception from within the scope of a using statement, and you do not catch the exception within the scope of the using statement (e.g. it is caught further up in the call stack), the using statement will go out of scope before the exception is caught. Thus, the Dispose method in ExceptionManagerContext will be called, and the context popped from the stack, before the exception thrown within the using statement is handled by your exception manager.


Here is your class that catches and logs or otherwise handles exceptions. You can add a Stack of IExceptionManagerContext objects to it, so that you can Push and Pop contexts.

class ExceptionManager
{
    private Stack<IExceptionManagerContext> contexts;

    public ExceptionManager()
    {
        contexts = new Stack<IExceptionManagerContext>();
        PushContext(new DefaultExceptionManagerContext());
    }

    public void PushContext(IExceptionManagerContext context)
    {
        contexts.Push(context);
    }

    public void PopContext()
    {
        contexts.Pop();
    }

    private IExceptionManagerContext CurrentContext
    {
        get { return contexts.Peek(); }
    }

    public void Handle(Exception ex)
    {
        if (CurrentContext.EnableLogging) 
        {
            Log(ex);
        }
        else
        {
            DoSomethingElseWith(ex);
        }
    }
}

The default context at the bottom of the stack can implement some default options, such as enabling logging. You could add any number of other properties to IExceptionManagerContext to make your exception management configurable.

interface IExceptionManagerContext
{
    bool EnableLogging { get; }
}

class DefaultExceptionManagerContext : IExceptionManagerContext
{
    public bool EnableLogging { get { return true; } }
}

A custom context class implements IDisposable so that it can safely push and pop itself on/off the stack of contexts in the exception manager. This class will need some sort of reference to the exception manager, or maybe your manager is a singleton, etc. Note that this is a simplistic implementation of IDisposable, certain usages of this may cause PopContext to be invoked more times than PushContext, so you may want to improve the implementation to be safe.

class ExceptionManagerContext : IExceptionManagerContext, IDisposable
{
    ExceptionManager manager;

    public ExceptionManagerContext(ExceptionManager manager)
    {
        this.manager = manager;
        manager.PushContext(this);
    }

    public bool EnableLogging { get; set; }

    public void Dispose()
    {
        manager.PopContext();
    }
}

Here is your code that wants to define a "context" in which exception handling should behave differently. The using statement will ensure that the altered context is set, and then removed, safely. You can nest such using statements, run this code recursively, etc., and it should do the right thing.

using (new ExceptionManagerContext(exceptionManager) { EnableLogging = false })
{
    DoSomethingThatMayRaiseExceptions();
}
Community
  • 1
  • 1
Mike Mertsock
  • 11,825
  • 7
  • 42
  • 75
1

Here's a different approach. Create a subclass of Exception that marks exceptions that should not be logged (or otherwise should be handled differently by your exception manager):

class UnloggedException : Exception
{
    public UnloggedException(Exception innerException)
        : base(innerException.Message, innerException)
    {   
    }
}

Then, instead of a using statement like in the other answer I posted, use a try/catch block where you want to define a "context" for different exception handling:

try
{
    DoSomethingThatMayRaiseExceptions();
}
catch (Exception ex)
{
    throw new UnloggedException(ex);
}

This will wrap the original exception thrown in the new exception class. Your exception manager can then just check if the exception is of type UnloggedException, and handle it differently. It can still access the original exception that was thrown, with its stack trace, etc., via the InnerException property of the UnloggedException class.

Mike Mertsock
  • 11,825
  • 7
  • 42
  • 75
0

You can use the existing Code Access Security stuff built into the framework, to do the stack walking and attribute checking for you. Just define a custom permission, and check for that permission deep inside your call tree where the error occurs.

Note that neither this method, nor the custom-stack-management suggested in your code and esker's answer, will work if the exception handler is in the caller rather than the callee, since stack frames and using blocks are already unwound. First-chance exception handling works around this, but isn't available in C# code. .NET does support it though, via either C++/CLI or VB.NET (and maybe some third-party languages).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720