控制台如何获取异常的stack trace?

How does the console obtain the stack trace of an exception?

在 C# 中,我试图修改我创建的自定义异常的 StackTrace 属性。为此,我只是覆盖 StackTrace getter 并且它似乎正在工作(Exception 将它作为 virtual 方法,所以它应该工作)。这是我的自定义例外:

class CustomTimeoutException : Exception
{
    private string oldStack;

    public CustomTimeoutException(string message, String stack)
        : base(message)
    {
        oldStack = stack;
    }

    public override string StackTrace
    {
        get
        {

            return oldStack;
        }
    }
    public override string Message
    {
        get
        {
            return "lalalala2";
        }
    }

    public override System.Collections.IDictionary Data
    {
        get
        {
            return null;
        }
    }

}

我正在按以下方式使用此 class:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Program p = new Program();
            p.throwTimeout2();
        }
        catch(TimeoutException e)
        {
            CustomTimeoutException tor = new CustomTimeoutException(e.Message + "with more", e.StackTrace);
            Console.WriteLine(tor);
            Console.WriteLine(tor.StackTrace);
            throw tor;
        }
    }
    public void throwTimeout2()
    {
        throwTimeout();
    }
    public void throwTimeout()
    {
        throw new TimeoutException("this is a message ");
    }
}

当我将 属性 StackTrace 写入控制台时,它会打印 TimeoutExceptionStackTrace,这意味着它实际上覆盖了它。但是,当控制台显示错误时,StackTrace 不是它从我的 属性 获得的那个,而 Message 是它从 Message 属性我也顶了。

那么,控制台从哪里得到异常的StackTrace呢?我正在使用 Visual Studio 作为 运行。

(注意:关于你的问题我仍然真的不明白的一件事是,虽然你似乎在寻求一种方法来影响向控制台报告异常的方式,但你也假设你有一些 "tool" 显然可以访问 Exception 对象本身并且正在报告最内部的异常,即跟随 InnerException 属性 链直到它到达最后一个。这两个陈述似乎相互矛盾,所以我不确定我是否完全理解了这个问题。不过,我还是花了一些时间研究它,并且无论如何都会分享我所拥有的。:)。 )


恕我直言,没有做你想做的好方法。有一种丑陋的方式(见下文),但我不推荐它。特别是因为您的主要限制是 ;恕我直言,为了适应某些工具而更改生产代码是一个非常的坏主意。如果不是完全错误的代码,那条路径会导致严重的维护问题。

如果您真的必须更改异常消息,您应该硬着头皮将要保留的整个堆栈跟踪文本也扔到消息中。试图弄乱最终抛出的异常本身的堆栈跟踪是个坏消息。

也就是说……


如果您要将对 Console.WriteLine() 的诊断调用添加到您的方法覆盖中,您将确认(正如您可能已经猜到的那样)当异常重新出现时 StackTrace 属性 不会被调用- 由 CLR 抛出并报告到控制台。覆盖 属性 无济于事。

尝试该策略至少有两个问题:

  1. StackTrace 属性 只是一个 string 包含有关堆栈跟踪的格式化信息。 Exception 对象实际上包含一个私有字段 _stackTrace。当您使用 Exception 对象执行 throw 语句时,CLR 会将当前堆栈跟踪信息存储到此字段中。您必须在事后找到一些方法来覆盖此字段,以伪造一个与实际不同的投掷站点。
  2. CLR 异常处理利用现有的 Windows 异常处理基础结构。抛出异常时首先发生的事情之一是异常处理代码尝试在堆栈中查找处理程序。如果找不到,则会收到 "Unhandled Exception:" 消息。但注意在这个过程中,栈并没有被展开。因此,异常处理代码甚至根本不需要检查 Exception 对象来确定抛出位置;栈还在,可以直接报告它的状态。如果异常处理代码确实直接查看堆栈状态,那么 nothing 你可以用 Exception 对象来影响报告。

现在,碰巧的是,对于我的 Windows 8.1 机器上的 .NET 版本,我是 运行,看起来 CLR 正在报告存储在 Exception 对象和 直接检查堆栈,根据上面的选项 #2。所以像这样的东西确实有效:

ExceptionWrapper class

class ExceptionWrapper : Exception
{
    private readonly string _trace;
    private readonly string _message;
    private readonly string _toString;
    private readonly object _stackTrace;

    public ExceptionWrapper(Exception e)
    {
        _trace = e.StackTrace;
        _message = e.Message + ", wrapped";
        _toString = e.ToString();
        _stackTrace = typeof(Exception).GetField("_stackTrace",
            BindingFlags.NonPublic | BindingFlags.Instance).GetValue(e);
    }

    public override string StackTrace { get { return _trace; } }

    public override string Message
    {
        get
        {
            typeof(Exception)
                .GetField("_stackTrace", BindingFlags.NonPublic | BindingFlags.Instance)
                .SetValue(this, _stackTrace);
            return _message;
        }
    }

    public override string ToString() { return _toString; }
}

测试程序

class Program
{
    static void Main(string[] args)
    {
        try
        {
            M1();
        }
        catch (Exception e)
        {
            throw;
        }
    }

    private static void M1()
    {
        try
        {
            M();
        }
        catch (Exception e)
        {
            throw new ExceptionWrapper(e);
        }
    }

    static void M()
    {
        throw new Exception("test exception");
    }
}

不过有一个非常重要的警告。这仅是因为两个特定事实:

  1. CLR 没有利用当前堆栈状态,而是信任 Exception 对象在 _stackTrace 字段中包含正确的堆栈信息。
  2. CLR 在检索堆栈跟踪之前调用 Exception 对象的 Message getter。这使对象有机会在 CLR 访问它以报告堆栈跟踪之前覆盖 _stackTrace 字段。

据我所知,这两个都是完全未记录的实现细节。第二个特别 非常 脆弱;虽然我明白为什么 CLR 可能总是必须坚持第一个细节,但我认为微软(更不用说 CLS 的其他实现者)没有理由被迫保留第二点的行为。

依赖这些实现细节编写代码是自找麻烦。无论这里的更广泛目标是什么,我都无法想象为了这种需要而冒着破坏项目的风险是值得的。恕我直言,简单地重写您要容纳的 "tool" 会更合理。

工具本质上是(或至少应该是)简单的,重写它不仅可以让你产生你想要的结果,而无需在第三方代码中处理私有实现细节,它还可以提供您有机会 a) 添加新功能,例如提供用于控制如何处理异常数据的选项(例如,查看除最内层异常以外的其他内容),以及 b) 有一个工具,您 拥有 的源代码,您可以在未来进一步改进。


我强烈建议你不要把自己描绘成这个特定的角落。 :)