Многоадресная рассылка OnExceptionAspect регистрирует всплывающие исключения

У меня есть многоадресная рассылка OnExceptionAspect из Postsharp, который применяется на уровне сборки. Это естественно означает, что все методы при выдаче исключения будут вызывать Aspect.

В аспекте я регистрирую сведения об исключении, включая значения параметров, переданных при возникновении исключения, это работает правильно.

Однако, поскольку это применяется ко всем методам в сборке, запись в журнале создается для каждого метода в стеке, поскольку исключение всплывает через каждый.

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


person m.edmondson    schedule 25.04.2012    source источник
comment
Каково предполагаемое поведение? Вы неясны в этом вопросе.   -  person Gael Fraiteur    schedule 25.04.2012
comment
Извинения. Я хочу иметь возможность регистрировать трассировку стека и параметры метода в момент исключения. Но я также хочу, чтобы он распространялся вверх по стеку, как обычно (без последующей повторной регистрации исключения).   -  person m.edmondson    schedule 25.04.2012


Ответы (3)


Есть два решения этой проблемы.

A. Используйте статическое поле потока для хранения любого исключения, которое уже было зарегистрировано.

[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. Добавьте тег к объекту Exception.

[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);
      } 

    }  
}
person Gael Fraiteur    schedule 26.04.2012

К вашему сведению: Гаэль является гуру PostSharp, потому что он там работает... просто чтобы вы знали.

Что бы это ни стоило, вы всегда можете сказать, откуда возникло исключение, изучив StackTrace. StackTrace доступен через args.Exception.StackTrace. Вы можете попробовать то, что рекомендует Дастин Дэвис (другой сотрудник PostSharp) здесь: Получить номер строки исключения

Проанализируйте StackTrace (с помощью метода, описанного здесь: Как разделить строку трассировки стека на пространство имен, класс, файл метода и номер строки?), а затем сравнить args.Method.Name с проанализированными результатами. Если ваш args.Method.Name совпадает с исходным методом (найденным в результате синтаксического анализа StackTrace), то вы знаете, что должны зарегистрировать его, иначе проигнорируйте.

Вот некоторый код, чтобы сделать мое решение более конкретным (на основе двух предыдущих решений):

[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);
        }
    } 
}

(Или, честно говоря, вы могли бы просто использовать одно из рекомендованных Гаэлем решений.)

person TWright    schedule 12.09.2012

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

Вот как будет выглядеть код примера:

[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");
    } 
}

Конечно, вам все равно придется определить это исключение и все такое. :)

person shriek    schedule 25.04.2012
comment
Хорошая идея, однако как я могу определить, возникло ли исключение в этом методе или оно всплывает снизу? - person m.edmondson; 26.04.2012
comment
@m.edmondson: Это имеет значение? Вы знаете, что пользовательское исключение было сгенерировано аспектом, и вы уже зарегистрировали его, не имеет значения, насколько глубоко оно было сгенерировано, не так ли? - person shriek; 26.04.2012
comment
Наверное, нет, но я просто не хочу забивать свои логи информацией, которая не помогает, а только мешает. - person m.edmondson; 26.04.2012
comment
@m.edmondson: Вы бы этого не сделали, потому что знали бы, что не следует регистрировать исключение, которое было выдано в вашем аспекте. Это также то, что делает мой образец. - person shriek; 26.04.2012