【问题标题】:Uniquely identify .NET exceptions唯一标识 .NET 异常
【发布时间】:2011-03-29 22:58:28
【问题描述】:

我希望通过对异常进行分组来改进我们的 .NET 应用程序日志记录。理想情况下,我们会按它们在代码中出现的位置以及它们是什么类型的异常进行分组。所以基本上我们最终会得到一个每个异常发生的列表(具有不同的日期、服务器变量等)。

例如,如果我的注册使用页面上有一个方法抛出了 5 次空引用异常,我将只列出该异常一次,出现 5 次。在不同页面上引发的另一个空引用异常将单独列出。

是否可以使用堆栈跟踪来可靠地识别异常,还是我遗漏了其他东西?

【问题讨论】:

  • 为什么需要堆栈跟踪?你不能只通过他们的名字来识别他们吗?还是我在这里错过了你的观点?
  • 添加了一个例子,希望能让自己更清楚。

标签: .net exception logging exception-handling


【解决方案1】:

如果异常被捕获然后重新抛出,那么它的堆栈跟踪将显示为好像异常发生在重新抛出的点而不是原始位置。希望您的代码中不会发生太多这样的事情。如果不是,那么仅堆栈跟踪就足以区分异常位置。

另一个问题是堆栈跟踪是否相同,因此当它们实际上相同时,它们看起来不会是两个不同的异常。这也不应该是一个问题,因为根据我的经验,堆栈跟踪是相同的。 (异常对象上的 ToString() 结果通常不会相同。)

更新

关于Exception.StackTrace 将被损坏的具体情况已经有一些讨论。基本规则是随时

throw *expression*; 

被执行,然后*expression* 标识的Exception 对象将设置其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 导致 Replace() 抛出异常

RethrowExplicit 捕获 ThrowAnException() 抛出的异常

RethrowExplicit 导致在 RethrowExplicit() 抛出异常

RethrowImplicit 捕获 ThrowAnException() 抛出的异常

RethrowImplicit 导致 ThrowAnException() 抛出异常

myException 以在 --empty-- 处引发的异常开始

myException 更改为 Main(String[] args) 引发的异常

第六行很有意思。

在那里,我现在已经彻底击败了这一点。 :-)

【讨论】:

  • "如果一个异常被捕获然后重新抛出,那么它的堆栈跟踪将看起来好像异常发生在重新抛出的点而不是原来的地方。" -除非你使用throw; 而不是throw ex;
  • @Maxim Gueivandov - 是的,这就是我的意思。 (正如你在我对@ChrisF 的评论中看到的那样。)我试图简洁,因为这是一个旁白。
【解决方案2】:

好的,所以您基本上想要可靠地识别异常 PLUS 异常的位置。 堆栈跟踪对我来说似乎很好。 但您也可以使用方法名称作为您的位置识别的基础

System.Reflection.MethodBase.GetCurrentMethod()

您可以从将在构造函数中调用 GetCurrentMethod() 的基本异常派生所有异常类型,并通过只读属性公开方法名称,因此您可以知道在您想要捕获的任何地方创建异常的点在哪里例外情况。

例子:

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

【讨论】:

  • 空引用异常(OP 中的示例)不会使用自定义异常基类。此外,如果从多个位置调用 throwing 方法,则如果仅使用方法名称,则多个异常原因可能看起来相同,尽管它们非常不同。
【解决方案3】:

只要你没有吞下异常然后抛出一个新的异常(如下面的坏例子)你应该没问题。

try
{
    ... your code
}
catch (ExceptionA exA)
{
    ... some error handling
    throw new ExceptionZ();
}
catch (ExceptionB exB)
{
    ... some error handling
    throw new ExceptionZ();
}

此代码在这种情况下很糟糕,因为抛出异常会替换原始异常的堆栈跟踪。无论如何,这通常不是一个好习惯,但在这种情况下,它会阻止您唯一地查找和记录您的异常。

【讨论】:

  • 请注意throw exA; 而不是throw new ExceptionZ();替换堆栈跟踪。只有throw;(没有指定异常对象)会在一个捕获和抛出的场景中保留堆栈跟踪。
  • 我只是提出一个单独的观点,但现在您的更正使答案错误!抛出任何异常对象(new 或旧的)都会在该异常上设置堆栈跟踪。
【解决方案4】:

在您的示例中(实际上在大多数情况下),我会抛出两个 不同 自定义异常类型,具体取决于 null 值是什么。捕获这些异常的代码可以记录必要的数据。

【讨论】:

    猜你喜欢
    • 2010-10-19
    • 1970-01-01
    • 2010-10-12
    • 2017-05-16
    • 1970-01-01
    • 1970-01-01
    • 2014-03-05
    • 2015-05-08
    相关资源
    最近更新 更多