简介
当用户在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);
但是用户会看到:
“将异常详细信息复制到剪贴板”:
System.TypeInitializationException未处理 消息:mscorlib.dll中发生了未处理的“System.TypeInitializationException”类型的异常 附加信息:'TypeInitializationExceptionTest.Program'的类型初始化程序引发了异常。
所以消息消失了!
问题
TypeInitializationException
发送更多信息吗?像一条消息?我们已经发送了一个innerException。 Exception
上有属性,以便报告更多信息? 备注
修改:
请注意我是图书馆维护者,而不是图书馆的用户。 我无法更改通话代码!
答案 0 :(得分:11)
我只想指出你在这里遇到的根本问题。您正在调试调试器中的错误,它有一个非常简单的解决方法。使用工具>选项>调试>一般>勾选“使用托管兼容模式”复选框。同时解开Just My Code以获取最丰富的调试报告:
如果勾选了“仅我的代码”,则异常报告的信息量较少,但仍可通过单击“查看详细信息”链接轻松钻取。
选项名称不必要地含糊不清。 真正所做的是告诉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。
希望你现在能够保持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计算出来。)
因此,当您运行测试人员分支时,您将看到以下对话框:
它没有显示查看详细信息链接。
将异常详细信息复制到剪贴板也不是特别有用:
System.TypeInitializationException未处理
消息:mscorlib.dll中发生了未处理的“System.TypeInitializationException”类型异常 附加信息:'TypeInitializationExceptionTest.Program'的类型初始化程序引发了异常。
现在,使用确定按钮关闭对话框,然后转向 Locals 调试窗口。以下是您将看到的内容:
请参阅? 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>
之后,问题得以解决。
<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>