我有一个来自Postsharp的多播 OnExceptionAspect ,它应用于程序集级别。这自然意味着所有方法在抛出异常时都会调用 Aspect。
在方面中,我正在记录异常详细信息,包括发生异常时传递的参数值,这可以正常工作。
但是,由于这适用于程序集中的所有方法,因此会为堆栈中的每个方法创建一个日志条目,因为每个方法都会出现异常。
我对如何防止这种情况一无所知,最初我打算比较异常(看看它是否相同),但这似乎很混乱。以前一定有人遇到过这个问题,有什么想法吗?
我有一个来自Postsharp的多播 OnExceptionAspect ,它应用于程序集级别。这自然意味着所有方法在抛出异常时都会调用 Aspect。
在方面中,我正在记录异常详细信息,包括发生异常时传递的参数值,这可以正常工作。
但是,由于这适用于程序集中的所有方法,因此会为堆栈中的每个方法创建一个日志条目,因为每个方法都会出现异常。
我对如何防止这种情况一无所知,最初我打算比较异常(看看它是否相同),但这似乎很混乱。以前一定有人遇到过这个问题,有什么想法吗?
这个问题有两种解决方案。
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);
}
}
}
仅供参考——Gael 是 PostSharp 大师,因为他在那里受雇……所以你知道。
对于它的价值,您始终可以通过检查 StackTrace 来判断异常源自何处。StackTrace 通过 args.Exception.StackTrace 提供。您可以在这里尝试 Dustin Davis(另一位 PostSharp 员工)推荐的方法:PostSharp - OnExceptionAspect - Get line number of exception
解析 StackTrace(通过此处概述的方法:How to split a stacktrace line into namespace, class, method file and line number?)然后将 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);
}
}
}
(或者,老实说,您可以只使用 Gael 推荐的解决方案之一。)
我可以看到完成此操作的一种方法是定义一个自定义异常,然后在您的方面抛出该异常。然后在您的方面也在登录之前检查异常,如果它不是您的自定义异常记录它,否则不要记录它并(重新抛出?)。
这就是示例代码的样子:
[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");
}
}
当然,您仍然必须定义该异常和所有内容。:)