2015-02-12 6 views
0

У нас есть OnExceptionAspect PostSharp, применяемый к каждому методу нашего проекта, который искажает номера строк, указанные в трассировке стека: номер строки внутреннего стека больше не указывает на строку, где исключение произошло, но к закрывающей скобке метода, в котором произошло исключение.PostSharp's OnExceptionAspect искажает строки строк трассировки стека

Это, по-видимому, известное ограничение Windows, которое при реорганизации исключения сбрасывает начало трассировки стека (см. Incorrect stacktrace by rethrow).

Вы можете воспроизвести эту проблему с этим кодом (вам нужно PostSharp установлен):

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) 
     { 
     } 
    } 
} 

Выполнение этого кода дает следующую трассировку стека:

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 не является throw new Exception("Invalid value.");, но закрытие скобка private static void Foo(int value).

Решение состоит в том, чтобы обернуть исключение в новой и повторно выдать его внутри метода OnException в 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); 
     } 
    } 
} 

Это дает правильные номера строк (throw new Exception("Invalid value."); является по линии 37 в настоящее время):

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 

Однако это решение добавляет мусор следов стеки (System.Exception - Foo записи не должна действительно существует), и для нас делает их практически бесполезно (помните, что аспект применяются к каждым в нашем проекте: так, если исключение вытесняет двадцать методов, у нас есть двадцать новых вложенных исключений, добавленных в трассировку стека).

Учитывая, что мы не можем - кашляем кашель PHB - избавиться от аспект, какие альтернативы у нас должны иметь правильные номера строк и читаемые следы стека?

+2

postsharp - отличный инструмент. любой, кто использует его, значительно выше уровня PHB – Batavia

+0

@Batavia это не инструмент - это то, как вы его используете. – Albireo

ответ

3

Я один из разработчиков PostSharp. Это известная проблема (или, скорее, функция) инструкции CLR rethrow. Для краткости он изменяет трассировку стека на основе точки последовательности этой команды. То же самое происходит, если вы используете throw; в выводе catch, но это более очевидно, поскольку вы видите утверждение, которое его вызывает.

Мы работаем над серьезными изменениями, которые должны обеспечить исправление этого поведения и, надеюсь, будут выпущены в будущей версии (я не могу сказать, какой из них на данный момент). Я боюсь, что до тех пор, пока он не будет выпущен, обходной путь, который вы используете (или аналогичный), является единственным возможным решением.

+0

Я боялся этого после декомпиляции IL и поиска http://stackoverflow.com/q/4217616/91696. В настоящий момент мы работали над этим, создавая настраиваемое исключение и обертывая исходное, когда аспект вызывается в первом стеке стека, последующие вызовы аспект не переписывают исключение из-за проверки типа исключения. Это уродливо, но это работает ... – Albireo

2

Как stated by Daniel Balas и Incorrect stacktrace by rethrow, из-за «функции» CLR невозможно сохранить исходную трассировку стека при перестройке исключения, поднятого в том же методе.

Следующие примеры показывают, как мы реализовали работу, обернув исходное исключение в первом стеке стека (как указано в my comment).

Инструкция, вызывающая исключение - throw new Exception("Invalid value."); - находится в строке 41 в обоих примерах.

До:

[assembly: Sandbox.TrapExceptionAspect] 

namespace Sandbox 
{ 
    using System; 
    using System.Runtime.Serialization; 

    using PostSharp.Aspects; 

    public static class Program 
    { 
     public static void Main() 
     { 
      try 
      { 
       Foo(2); 
      } 
      catch (Exception exception) 
      { 
       while (exception != null) 
       { 
        Console.WriteLine(exception.Message); 
        Console.WriteLine(exception.StackTrace); 

        exception = exception.InnerException; 
       } 
      } 

      Console.ReadKey(true); 
     } 

     private static void Foo(int value) 
     { 
      Bar(value); 
     } 

     private static void Bar(int value) 
     { 
      if (value % 2 == 0) 
      { 
       throw new Exception("Invalid value."); 
      } 

      Console.WriteLine("Hello, world."); 
     } 
    } 

    [Serializable] 
    public class TrapExceptionAspect : OnExceptionAspect 
    { 
     public override void OnException(MethodExecutionArgs args) 
     { 
     } 
    } 
} 

Стек след:

Invalid value. 
    at Sandbox.Program.Bar(Int32 value) in …\Sandbox\Sandbox\Program.cs:line 45 
    at Sandbox.Program.Foo(Int32 value) in …\Sandbox\Sandbox\Program.cs:line 35 
    at Sandbox.Program.Main() in …\Sandbox\Sandbox\Program.cs:line 16 

После:

[assembly: Sandbox.TrapExceptionAspect] 

namespace Sandbox 
{ 
    using System; 
    using System.Runtime.Serialization; 

    using PostSharp.Aspects; 

    public static class Program 
    { 
     public static void Main() 
     { 
      try 
      { 
       Foo(2); 
      } 
      catch (Exception exception) 
      { 
       while (exception != null) 
       { 
        Console.WriteLine(exception.Message); 
        Console.WriteLine(exception.StackTrace); 

        exception = exception.InnerException; 
       } 
      } 

      Console.ReadKey(true); 
     } 

     private static void Foo(int value) 
     { 
      Bar(value); 
     } 

     private static void Bar(int value) 
     { 
      if (value % 2 == 0) 
      { 
       throw new Exception("Invalid value."); 
      } 

      Console.WriteLine("Hello, world."); 
     } 
    } 

    [Serializable] 
    public class TrapExceptionAspect : OnExceptionAspect 
    { 
     public override void OnException(MethodExecutionArgs args) 
     { 
      if (args.Exception is CustomException) 
      { 
       return; 
      } 

      args.FlowBehavior = FlowBehavior.ThrowException; 

      args.Exception = new CustomException("Unhandled exception.", args.Exception); 
     } 
    } 

    [Serializable] 
    public class CustomException : Exception 
    { 
     public CustomException() : base() 
     { 
     } 

     public CustomException(string message) : base(message) 
     { 
     } 

     public CustomException(string message, Exception innerException) : base(message, innerException) 
     { 
     } 

     public CustomException(SerializationInfo info, StreamingContext context) : base(info, context) 
     { 
     } 
    } 
} 

Стек след:

Unhandled exception. 
    at Sandbox.Program.Bar(Int32 value) in …\Sandbox\Sandbox\Program.cs:line 45 
    at Sandbox.Program.Foo(Int32 value) in …\Sandbox\Sandbox\Program.cs:line 35 
    at Sandbox.Program.Main() in …\Sandbox\Sandbox\Program.cs:line 16 
Invalid value. 
    at Sandbox.Program.Bar(Int32 value) in …\Sandbox\Sandbox\Program.cs:line 41 
Смежные вопросы