更好的TypeInitializationException(innerException也为null)

时间:2016-01-25 14:01:36

标签: c# exception nlog

简介

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

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

示例

E.g。如果用户有这个程序:

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

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

throw new NLogConfigurationException("Exception occurred when loading configuration from " + fileName, exception);

但是用户会看到:

Throw exception

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

  

System.TypeInitializationException未处理   消息:mscorlib.dll中发生了未处理的“System.TypeInitializationException”类型的异常   附加信息:'TypeInitializationExceptionTest.Program'的类型初始化程序引发了异常。

所以消息消失了!

问题

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

    修改

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

7 个答案:

答案 0 :(得分:11)

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

enter image description here

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

选项名称不必要地含糊不清。 真正所做的是告诉Visual Studio使用旧版本的调试引擎。任何使用VS2013或VS2015的人都会遇到新引擎的问题,可能是VS2012。也是之前NLog没有解决这个问题的基本原因。

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

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

答案 1 :(得分:6)

我看到的原因是因为入口点类的类型初始化失败。由于没有初始化类型,因此类型加载器无法报告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();
    }
}

现在你将获得InnerException,因为加载了Entry类型以报告TypeInitializationException。

enter image description here

希望你现在能够保持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();
    }
}

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

更新2

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

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

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

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

答案 2 :(得分:3)

问题是在首次引用类时发生静态初始化。在Program中,它甚至在Main()方法之前就会发生。因此,经验法则 - 避免任何可能在静态初始化方法中失败的代码。至于你的特殊问题 - 改用lazy方法:

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

static void Main() {
  logger.Value.Log(...);
}

因此,当您首次访问记录器时,记录器的初始化将会发生(并且可能会失败) - 而不是在某些疯狂的静态环境中。

<强>更新

图书馆用户最终要坚持最佳做法。所以,如果是我,我会保持原样。如果您真的必须在最后解决它,那么几乎没有选择:

1)不要抛出异常 - 永远 - 这是记录引擎中的有效方法,以及log4net如何工作 - 即

static Logger GetCurrentClassLogger() {
    try {
       var logger = ...; // current implementation
    } catch(Exception e) {
        // let the poor guy now something is wrong - provided he is debugging
        Debug.WriteLine(e);
        // null logger - every single method will do nothing 
        return new NullLogger();
    }
}

2)围绕Logger类的实现包装惰性方法(我知道你的Logger类要复杂得多,为了解决这个问题,我们假设它只有一个方法Log需要string className来构建Logger实例。

class LoggerProxy : Logger {
  private Lazy<Logger> m_Logger;
  // add all arguments you need to construct the logger instance
  public LoggerProxy(string className) {
    m_Logger = new Lazy<Logger>(() => return new Logger(className)); 
  }
  public void Log(string message) {
   m_Logger.Value.Log(message);
  }
}

static Logger GetCurrentClassLogger() {
  var className = GetClassName();
  return new LoggerProxy(className);
}

你将摆脱这个问题(在调用第一个log方法时会发生真正的初始化,并且它是向后兼容的方法);唯一的问题是你已经添加了另一层(我不希望任何性能大幅降级,但是一些日志引擎真的进入了微优化)。

答案 3 :(得分:2)

我现在看到的唯一解决方案是:

将静态初始化(字段)移动到具有ServletRequest request = (ServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); try { if (!(request instanceof MultipartRequest)) { request = unwrapMultipartRequest(request); } if (request instanceof MultipartRequest) { MultipartRequest multipartRequest = (MultipartRequest) request; String clientId = "upload"; setFileData(multipartRequest.getFileBytes(clientId)); setFileContentType(multipartRequest.getFileContentType(clientId)); setFileName(multipartRequest.getFileName(clientId)); saveOpenAttachment(); } }

的静态构造函数

答案 4 :(得分:2)

我在之前的评论中说过,我无法重现您的问题。然后我发现你只是 在弹出异常对话框中查找它,它不会显示显示详细信息链接。

所以这里是你如何得到InnerException,因为它绝对存在,除了Visual Studio因某些原因没有报告它(可能是因为它在入口点类型为vendettamit计算出来。)

因此,当您运行测试人员分支时,您将看到以下对话框:

Exception dialog

它没有显示查看详细信息链接。

将异常详细信息复制到剪贴板也不是特别有用:

  

System.TypeInitializationException未处理
  消息:mscorlib.dll中发生了未处理的“System.TypeInitializationException”类型异常   附加信息:'TypeInitializationExceptionTest.Program'的类型初始化程序引发了异常。

现在,使用确定按钮关闭对话框,然后转向 Locals 调试窗口。以下是您将看到的内容:

Locals view

请参阅? InnerException肯定是 那里,你找到了一个VS怪癖: - )

答案 5 :(得分:0)

System.TypeInitializationException始终或几乎总是因为不正确初始化类的静态成员而发生。

您必须通过调试器检查LogManager.GetCurrentClassLogger()。我确定错误发生在代码部分内部。

//go into LogManager.GetCurrentClassLogger() method
private static Logger logger = LogManager.GetCurrentClassLogger();

另外,我建议你仔细检查你的app.config并确保你没有包含任何错误。

每当静态构造函数抛出异常时,或者每当您尝试访问静态构造函数引发异常的类时,都会抛出

System.TypeInitializationException

.NET加载该类型时,它必须在您使用该类型的 第一次 之前准备好所有静态字段。有时,初始化需要运行代码。当代码失败时,您会得到System.TypeInitializationException

根据docs

  

当类初始值设定项无法初始化类型时,会创建TypeInitializationException并传递对类型的类初始值设定项引发的异常的引用。 TypeInitializationException的InnerException属性保存基础异常。   TypeInitializationException使用HRESULT COR_E_TYPEINITIALIZATION,其值为0x80131534。   有关TypeInitializationException实例的初始属性值列表,请参阅TypeInitializationException构造函数。

1)InnerException不可见,这种类型的异常非常典型。 Visual Studio的版本无关紧要(确保选中“启用例外助理”选项 - Tools> Options>Debugging>General

2)通常TypeInitializationException隐藏了可以通过InnerException查看的真实异常。但下面的测试示例显示了如何填充内部异常信息:

public class Test {
    static Test() {
        throw new Exception("InnerExc of TypeInitializationExc");
    }
    static void Main(string[] args) {
    }
}
  

但有时这个NLogConfigurationException被“吃掉”了   System.TypeInitializationException如果第一次调用NLog来自a   静态字段/属性。

没什么奇怪的。有人在某处错过了try catch区块。

答案 6 :(得分:-1)

对于我来说,我在config文件中添加了一些内容,然后NLog给我例外

  <entityFramework>
    <providers>
      <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.2.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
    </providers>
  </entityFramework>

最后,我发现我忘记将<entityFramework>添加到顶部的<configSection>元素中,而在添加到<configSection>之后,问题得以解决。

Reference Article

<configSections>
    <!--This is your EF section definition that was added-->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
     ...
</configSections>