我希望通过对异常进行分组来改进我们的.NET应用程序日志记录。理想情况下,我们会按代码中出现的位置进行分组,以及它们是什么类型的异常。所以基本上我们最终会得到每个异常的出现列表(具有不同的日期,服务器变量等)。
例如,如果我的寄存器使用页面上有一个方法已经引发了5次空引用异常,那么我将列出该异常一次,并且计数为5次。另一个页面上抛出的另一个空引用异常将单独列出。
是否可以使用堆栈跟踪来可靠地识别异常,还是有其他我错过的东西?
答案 0 :(得分:4)
如果捕获异常然后重新抛出,则其堆栈跟踪将显示为在重新抛出点而不是原始位置发生异常。希望您的代码中没有太多内容。如果没有,那么单独的堆栈跟踪应该足以区分异常位置。
另一个问题是堆栈跟踪是否相同,因此当它们真的相同时,它们似乎不是两个不同的异常。这也应该不是问题,因为根据我的经验,堆栈跟踪是相同的。 (通常,异常对象上的ToString()结果不会相同。)
<强>已更新强>
有一些关于Exception.StackTrace
将被破坏的确切情况的讨论。基本规则是任何时候
throw *expression*;
执行,然后Exception
标识的*expression*
对象将设置其StackTrace
属性。如果省略*expression*
throw;
然后StackTrace将不受影响。
我找不到这两种形式的任何术语,所以我将分别称它们为“显式”和“隐式”。请注意,*expression*
解析为的异常对象是new
还是已存在的对象无关紧要。
这是一个程序来说明这种行为:
using System;
namespace FunWithExceptions
{
class Program
{
static void Main(string[] args)
{
try { Replace(); }
catch (InvalidOperationException ex) { DisplayResult("Replace resulted in", ex); }
try { RethrowExplicit(); }
catch (InvalidOperationException ex) { DisplayResult("RethrowExplicit resulted in", ex); }
try { RethrowImplicit(); }
catch (InvalidOperationException ex) { DisplayResult("RethrowImplicit resulted in", ex); }
InvalidOperationException myException = new InvalidOperationException();
DisplayResult("myException starts with", myException);
try { throw myException; }
catch (InvalidOperationException) { }
DisplayResult("myException changes to", myException);
Console.ReadLine();
}
static void ThrowAnException()
{ throw new InvalidOperationException("You messed up!"); }
static void Replace()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("Replace caught", ex);
throw new InvalidOperationException("Another mistake.");
}
}
static void RethrowExplicit()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("RethrowExplicit caught", ex);
throw ex;
}
}
static void RethrowImplicit()
{
try { ThrowAnException(); }
catch (InvalidOperationException ex)
{
DisplayResult("RethrowImplicit caught", ex);
throw;
}
}
static void DisplayResult(string context, Exception ex)
{
Console.WriteLine("{0} exception thrown at {1}", context, FirstMethodName(ex.StackTrace));
}
private const string methodNamePrefix = " at FunWithExceptions.Program.";
private static object FirstMethodName(string stackTrace)
{
stackTrace = stackTrace ?? string.Empty;
if (stackTrace.StartsWith(methodNamePrefix))
stackTrace = stackTrace.Substring(methodNamePrefix.Length);
int methodNameEndIndex = stackTrace.IndexOf(')');
if (methodNameEndIndex != -1)
stackTrace = stackTrace.Substring(0, methodNameEndIndex + 1);
if (stackTrace.Length > 0)
return stackTrace;
else
return "--empty--";
}
}
}
此程序产生以下输出:
替换ThrowAnException()
时抛出的捕获异常替换导致在Replace()
中抛出异常RethrowExplicit捕获了ThrowAnException()
抛出的异常RethrowExplicit导致RethrowExplicit()
抛出异常在ThrowAnException()
中抛出了RethrowImplicit异常RethrowImplicit导致ThrowAnException()
抛出异常myException从--empty -
抛出的异常开始myException更改为Main(String [] args)
抛出的异常
第六行是有趣的一行。
在那里,我现在已经完全击败了这一点。 : - )
答案 1 :(得分:1)
只要您不吞咽异常然后抛出新的异常(如下面的错误示例),您应该没问题。
try
{
... your code
}
catch (ExceptionA exA)
{
... some error handling
throw new ExceptionZ();
}
catch (ExceptionB exB)
{
... some error handling
throw new ExceptionZ();
}
此代码中的代码很糟糕,因为抛出异常会替换原始异常中的堆栈跟踪。一般来说,这不是一般的好习惯,但在这种情况下,它会阻止您唯一地查找和记录您的异常。
答案 2 :(得分:1)
好的,所以你基本上想要可靠地识别异常的异常PLUS位置。 堆栈跟踪对我来说似乎很好。 但您也可以使用方法名称
来确定您的位置标识System.Reflection.MethodBase.GetCurrentMethod()
您可以从将在构造函数中调用GetCurrentMethod()的基本异常派生所有异常类型,并通过readonly属性公开方法名称,这样您就知道在要捕获的任何位置创建异常的位置在哪里例外情况。
示例:
public class BaseException : ApplicationException {
public BaseException() {
_originalMethod = System.Reflection.MethodBase.GetCurrentMethod().Name;
}
private string _originalMethod;
public string OriginalMethod { get { return _originalMethod; } }
}
//now, create tons of custom exceptions:
public class MyException1 : BaseException {
public MyException1()
: base() {
}
}
//create more custom exceptions...
public class MyExceptionNNN : BaseException {
public MyExceptionNNN()
: base() {
}
}
答案 3 :(得分:0)
在您的示例中(在大多数情况下,实际上),我会抛出两个不同的自定义异常类型,具体取决于null值。捕获这些异常的代码可以记录必要的数据。