更好的TypeInitializationException(innerException也为null)

Jul*_*ian 17 c# exception nlog

介绍

当用户在NLog的配置中创建错误(如无效的XML)时,我们(NLog)抛出一个NLogConfigurationException.该例外包含描述错误的内容.

但有时如果第一次调用NLog来自静态字段/属性,则会NLogConfigurationException被"吃掉" System.TypeInitializationException.

例如,如果用户有此程序:

using System;
using System.Collections.Generic;
using System.Linq;
using NLog;

namespace TypeInitializationExceptionTest
{
    class Program
    {
        //this throws a NLogConfigurationException because of bad config. (like invalid XML)
        private static Logger logger = LogManager.GetCurrentClassLogger();

        static void Main()
        {
            Console.WriteLine("Press any key");
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并且配置中存在错误,NLog抛出:

throw new NLogConfigurationException("Exception occurred when loading configuration from " + fileName, exception);
Run Code Online (Sandbox Code Playgroud)

但是用户会看到:

抛出异常

"将异常详细信息复制到剪贴板":

System.TypeInitializationException未处理消息:mscorlib.dll中发生未处理的类型'System.TypeInitializationException'的异常附加信息:'TypeInitializationExceptionTest.Program'的类型初始值设定项引发异常.

所以消息消失了!

问题

  1. 为什么innerException不可见?(在Visual Studio 2013中测试).
  2. 我可以发送更多信息TypeInitializationException吗?像一条消息?我们已经发送了一个innerException.
  3. 我们可以使用另一个例外或是否有属性,Exception以便报告更多信息?
  4. 还有另一种方法可以向用户提供(更多)反馈吗?

笔记

编辑:

请注意我是图书馆维护者,而不是图书馆的用户.我无法更改调用代码!

Han*_*ant 11

我只想指出你在这里遇到的根本问题.您正在调试调试器中的错误,它有一个非常简单的解决方法.使用工具>选项>调试>常规>勾选"使用托管兼容模式"复选框.同时解开Just My Code以获取最丰富的调试报告:

在此输入图像描述

如果勾选了"仅我的代码",则异常报告的信息量较少,但仍可通过单击"查看详细信息"链接轻松钻取.

选项名称不必要地含糊不清.它真正的作用是告诉Visual Studio使用旧版本的调试引擎.任何使用VS2013或VS2015的人都会遇到新引擎的问题,可能是VS2012.此问题的基本原因还没有在NLog中解决过.

虽然这是一个非常好的解决方法,但它并不容易发现.程序员也不会特别喜欢使用旧引擎,旧引擎不支持返回值调试和64位代码的E + C等闪亮的新功能.这是否真的是一个错误,新引擎的疏忽或技术限制很难猜测.这太难看了,所以不要犹豫,将它标记为"bug",我强烈建议你把它带到connect.microsoft.com.当它被修复时,每个人都会领先,我至少忘记了一次这个问题.通过使用Debug> Windows> Exceptions>勾选当时的CLR异常将其向下钻取.

这种非常不幸的行为的解决办法肯定是丑陋的.你必须延迟提出异常,直到程序执行进展得足够远.我不太了解您的代码库,但是延迟解析配置,直到第一个日志记录命令应该处理它.或者存储异常对象并将其抛出到第一个日志命令上,可能更容易.


ven*_*mit 6

我看到的原因是因为入口点类的类型初始化失败.由于没有初始化类型,因此Type loader没有任何关于失败类型的报告TypeInitializationException.

但是如果将logger的Static初始化程序更改为其他类,然后在Entry方法中引用该类.你会在TypeInitialization异常上得到InnerException.

static class TestClass
{
    public static Logger logger = LogManager.GetCurrentClassLogger();  
}

class Program
{            
    static void Main(string[] args)
    {
        var logger = TestClass.logger;
        Console.WriteLine("Press any key");
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您将获得InnerException,因为已加载Entry类型以报告TypeInitializationException.

在此输入图像描述

希望你现在能够保持Entry point的清洁,并从Main()而不是Entry point class的static属性引导应用程序.

更新1

您还可以利用它Lazy<>来避免在声明时执行配置初始化.

class Program
{
    private static Lazy<Logger> logger = new Lazy<Logger>(() => LogManager.GetCurrentClassLogger());

    static void Main(string[] args)
    {
        //this will throw TypeInitialization with InnerException as a NLogConfigurationException because of bad config. (like invalid XML)
        logger.Value.Info("Test");
        Console.WriteLine("Press any key");
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,尝试Lazy<>在LogManager中进行记录器实例化,以便在实际发生第一个日志语句时进行配置初始化.

更新2

我分析了NLog的源代码,看起来它已经实现了,这很有道理.根据对属性的评论"除非LogManager.ThrowExceptionsLogManager.cs中的属性指定,否则NLog不应抛出异常".

修复 - 在LogFactory类中,私有方法GetLogger()具有导致异常发生的初始化语句.如果您通过检查属性引入try catch,ThrowExceptions则可以防止初始化异常.

      if (cacheKey.ConcreteType != null)
            {
                try
                {
                    newLogger.Initialize(cacheKey.Name, this.GetConfigurationForLogger(cacheKey.Name, this.Configuration), this);
                }
                catch (Exception ex)
                {
                    if(ThrowExceptions && ex.MustBeRethrown())
                    throw;
                }
            }
Run Code Online (Sandbox Code Playgroud)

将这些异常/错误存储在某处也是很棒的,这样可以追踪Logger初始化失败的原因,因为它们被忽略了ThrowException.