2

I have a multicast OnExceptionAspect from Postsharp which is applied on the assembly level. This naturally means that all methods, upon throwing an exception, will invoke the Aspect.

Within the Aspect I'm logging the exception details including the values of the parameters passed when the exception occured, this is working properly.

However because this is applied to all methods in the assembly a log entry is created for each method in the stack as the exception bubbles up through each.

I'm out of ideas on how to prevent this, initially I was going to compare the exception (to see if it's the same one) but this just seems messy. Someone must have had this problem before, any ideas?

m.edmondson
  • 30,382
  • 27
  • 123
  • 206
  • What is the intended behavior? You're unclear on that point. – Gael Fraiteur Apr 25 '12 at 11:36
  • Apologies. I want to be able to log the stack trace and parameters of the method at the point of the exception. But I also want it to propagate up the stack like normal (without subsequently logging the exception again). – m.edmondson Apr 25 '12 at 13:14

3 Answers3

4

There are two solutions to this problem.

A. Use a thread-static field to store any exception that has already been logged.

[Serializable] 
public class MyAspect : OnExceptionAspect 
{ 
    [ThreadStatic]
    private static Exception lastException;

    public override void OnException(MethodExecutionArgs args) 
    { 
      if(args.Exception != lastException) 
      { 
        string msg = string.Format("{0} had an error @ {1}: {2}\n{3}",  
            args.Method.Name, DateTime.Now,  
            args.Exception.Message, args.Exception.StackTrace); 

        Trace.WriteLine(msg); 
        lastException = args.Exception;
      } 

    }  
}

B. Add a tag to the Exception object.

[Serializable] 
public class MyAspect : OnExceptionAspect 
{ 
    private static object marker = new object();

    public override void OnException(MethodExecutionArgs args) 
    { 
      if(!args.Exception.Data.Contains(marker)) 
      { 
        string msg = string.Format("{0} had an error @ {1}: {2}\n{3}",  
            args.Method.Name, DateTime.Now,  
            args.Exception.Message, args.Exception.StackTrace); 

        Trace.WriteLine(msg); 
        args.Exception.Data.Add(marker, marker);
      } 

    }  
}
Gael Fraiteur
  • 6,759
  • 2
  • 24
  • 31
3

FYI--Gael is a PostSharp guru because he is employed there...just so you are aware.

For what it is worth you can always tell where the exception originated by examining the StackTrace. The StackTrace is made available via args.Exception.StackTrace. You may try what Dustin Davis (another PostSharp employee) recommends here: PostSharp - OnExceptionAspect - Get line number of exception

Parse the StackTrace (via the method outlined here: How to split a stacktrace line into namespace, class, method file and line number?) then compare the args.Method.Name with the parsed results. If your args.Method.Name is the same as the originating method (found via parsing the StackTrace) then you know you should log it otherwise ignore.

Here is some code to make my solution more concrete (building on the prior two solutions cited):

[Serializable]
public class ExceptionWrapper : OnExceptionAspect
{
    public override void OnException(MethodExecutionArgs args)
    {
        var st = new StackTrace(args.Exception, true);
        var frame = st.GetFrame(0);
        var lineNumber = frame.GetFileLineNumber();
        var methodName = frame.GetMethod().Name;
        if(methodName.Equals(args.Method.Name))
        {
            string msg = string.Format("{0} had an error @ {1}: {2}\n{3}", 
                args.Method.Name, DateTime.Now, 
                args.Exception.Message, args.Exception.StackTrace);

            Trace.WriteLine(msg);
        }
    } 
}

(Or, honestly, you could just use one of Gael's recommended solutions.)

Greg Mason
  • 753
  • 1
  • 9
  • 23
TWright
  • 1,853
  • 18
  • 25
0

One way i could see this being done would be to define a custom exception and just throw that one in your aspect. then also in your aspect check the exception before loggin, if it's not your custom exception log it, otherwise don't log it and (re-throw?).

That's what the example code would look like:

[Serializable]
public class DatabaseExceptionWrapper : OnExceptionAspect
{
    public override void OnException(MethodExecutionArgs args)
    {
      if(!(args.Exception is CustomException))
      {
        string msg = string.Format("{0} had an error @ {1}: {2}\n{3}", 
            args.Method.Name, DateTime.Now, 
            args.Exception.Message, args.Exception.StackTrace);

        Trace.WriteLine(msg);
      }

      throw new CustomException("There was a problem");
    } 
}

Of course you'd still have to define that exception and everything. :)

shriek
  • 5,157
  • 2
  • 36
  • 42
  • Good idea, however how would I be able to tell if the exception originated in that method, or is being bubbled up from below? – m.edmondson Apr 26 '12 at 07:46
  • @m.edmondson: Would it matter? You know that the custom exception was thrown by the aspect and the you already logged it, doesn't really matter how deep down it was thrown, was it? – shriek Apr 26 '12 at 10:06
  • 1
    I suppose not, but I just don't want to fill up my logs with information which doesn't help - just hinder. – m.edmondson Apr 26 '12 at 10:32
  • @m.edmondson: You wouldn't because you'd know not to log the exception that was thrown in your aspect. That's also what my sample does. – shriek Apr 26 '12 at 10:41