We have a PostSharp's OnExceptionAspect applied to every method of our project which is corrupting the line numbers reported in the stack traces: the inner stack frame line number is no longer pointing to the line where the exception happened but to the closing brace of the method where the exception happened.
This seems to be a known limitation of Windows which, when rethrowing an exception, resets the stack trace origin (see Incorrect stacktrace by rethrow).
You can reproduce this issue with this code (you need PostSharp installed):
namespace ConsoleApplication1
{
using System;
using PostSharp.Aspects;
public static class Program
{
public static void Main()
{
try
{
Foo(2);
}
catch (Exception exception)
{
var type = exception.GetType();
Console.Write(type.FullName);
Console.Write(" - ");
Console.WriteLine(exception.Message);
Console.WriteLine(exception.StackTrace);
}
}
private static void Foo(int value)
{
if (value % 2 == 0)
{
throw new Exception("Invalid value.");
}
Console.WriteLine("Hello, world.");
}
}
[Serializable]
public class LogExceptionAspect : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs methodExecutionArgs)
{
}
}
}
Executing this code gives the following stack trace:
System.Exception - Invalid value.
at ConsoleApplication1.Program.Foo(Int32 value) in …\Program.cs:line 36
at ConsoleApplication1.Program.Main() in …\Program.cs:line 15
Line 36 is not throw new Exception("Invalid value.");
but the closing brace of private static void Foo(int value)
.
A solution is to wrap the exception in a new one and rethrow it inside the OnException method of the OnExceptionAspect:
[assembly: ConsoleApplication1.LogExceptionAspect]
namespace ConsoleApplication1
{
using System;
using PostSharp.Aspects;
public static class Program
{
public static void Main()
{
try
{
Foo(2);
}
catch (Exception exception)
{
while (exception != null)
{
var type = exception.GetType();
Console.Write(type.FullName);
Console.Write(" - ");
Console.WriteLine(exception.Message);
Console.WriteLine(exception.StackTrace);
exception = exception.InnerException;
}
}
}
private static void Foo(int value)
{
if (value % 2 == 0)
{
throw new Exception("Invalid value.");
}
Console.WriteLine("Hello, world.");
}
}
[Serializable]
public class LogExceptionAspect : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs methodExecutionArgs)
{
throw new Exception("Foo", methodExecutionArgs.Exception);
}
}
}
This gives the correct line numbers (throw new Exception("Invalid value.");
is on line 37 now):
System.Exception - Foo
at ConsoleApplication1.LogExceptionAspect.OnException(MethodExecutionArgs methodExecutionArgs) in …\Program.cs:line 49
at ConsoleApplication1.Program.Foo(Int32 value) in …\Program.cs:line 41
at ConsoleApplication1.Program.Main() in …\Program.cs:line 15
System.Exception - Invalid value.
at ConsoleApplication1.Program.Foo(Int32 value) in …\Program.cs:line 37
However this solution is adding garbage to the stack traces (the System.Exception - Foo
entry should not really exist) and for us is making them nearly useless (remember that the aspect is applied to every method in our project: so if an exception bubbles up twenty methods we have twenty new nested exceptions added to the stack trace).
Given that we can't — cough PHB cough — get rid of the aspect, which alternatives do we have to have correct line numbers and readable stack traces?