ILoggerFactory.GetCurrentClassLogger 仅在发布版本中因 InvalidOperationException 而失败
ILoggerFactory.GetCurrentClassLogger fails with InvalidOperationException in Release build only
上周将我的 Windows 服务应用程序部署到生产环境失败,因此我尝试 运行 在本地发布模式下的项目并得到 InvalidOperationException: Can not determine current class.
可以追溯到调用GetCurrentClassLogger
.
我的项目使用 Ninject 将 ILoggerFactory
解析为中间层的每个服务。然后服务使用 GetCurrentClassLogger()
在构造函数中获取正确的 ILogger
。
例如:
public class FooService : IFooService
{
private readonly ILogger logger;
public FooService(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.GetCurrentClassLogger();
}
// ...
}
如果我在 Ninject.Extensions.Logging 的 LoggerFactoryBase
深入研究 GetCurrentClassLogger
的实现:
[MethodImpl(MethodImplOptions.NoInlining)]
public ILogger GetCurrentClassLogger()
{
StackFrame stackFrame = new StackFrame(0, false);
if (stackFrame.GetMethod() == (MethodBase) LoggerFactoryBase.getCurrentClassLoggerMethodInfo)
stackFrame = new StackFrame(1, false);
Type declaringType = stackFrame.GetMethod().DeclaringType;
if (declaringType == (Type) null)
throw new InvalidOperationException(string.Format("Can not determine current class. Method: {0}", (object) stackFrame.GetMethod()));
return this.GetLogger(declaringType);
}
我可以看到抛出异常的地方。
我的第一直觉是检查是否有任何系统、框架或项目更新导致它,但自上次成功部署以来没有做任何有意义的事情。
现在...关于此问题的 "funny" 事情是当我在构造函数中添加带有 Trace.WriteLine("Test");
的行时,GetCurrentClassLogger
执行得很好。
我能够将这个问题与编译器的代码优化联系起来。如果我在项目属性的 Build 选项卡中禁用它,它也可以正常执行。
问题:什么会导致 StackFrame
停止提供 DeclaringType
?
同时我正在使用它作为解决方法,但我更喜欢使用原始方法:
this.logger = loggerFactory.GetLogger(this.GetType());
如有任何帮助,我们将不胜感激。
如果不查看发出的 CIL,很难确切地说行为不同的原因,但我猜它正在内联。
如果您将构造函数属性化为:
,您可能会得到想要的结果
[MethodImpl(MethodImplOptions.NoInlining)]
正如 NInject 代码所做的那样。该方法可能会被内联,因为它只是分配了一个只读字段的一行。您可以在此处了解内联如何影响 StackFrames:http://www.hanselman.com/blog/ReleaseISNOTDebug64bitOptimizationsAndCMethodInliningInReleaseBuildCallStacks.aspx
就是说,这种不可预测性是使用堆栈帧不是一个好主意的原因。如果您想要不必指定 class 名称的语法,正在使用 VS 2012,并且您的文件名与您的 class 名称匹配,您可以在扩展方法上使用 CallerFilePath 属性并保留相同的像现在一样的行为,而不使用堆栈框架。看:
https://msdn.microsoft.com/en-us/library/hh534540.aspx
更新:
另一种方法可能是直接注入 ILogger 并让您的容器负责确定要注入的组件的类型(因为它肯定知道)。这看起来也更干净。请参阅此处的示例:https://github.com/ninject/ninject.extensions.logging/issues/19
this.Bind<ILogger>().ToMethod(context =>
{
var typeForLogger = context.Request.Target != null
? context.Request.Target.Member.DeclaringType
: context.Request.Service;
return context.Kernel.Get<ILoggerFactory>().GetLogger(typeForLogger);
});
上周将我的 Windows 服务应用程序部署到生产环境失败,因此我尝试 运行 在本地发布模式下的项目并得到 InvalidOperationException: Can not determine current class.
可以追溯到调用GetCurrentClassLogger
.
我的项目使用 Ninject 将 ILoggerFactory
解析为中间层的每个服务。然后服务使用 GetCurrentClassLogger()
在构造函数中获取正确的 ILogger
。
例如:
public class FooService : IFooService
{
private readonly ILogger logger;
public FooService(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.GetCurrentClassLogger();
}
// ...
}
如果我在 Ninject.Extensions.Logging 的 LoggerFactoryBase
深入研究 GetCurrentClassLogger
的实现:
[MethodImpl(MethodImplOptions.NoInlining)]
public ILogger GetCurrentClassLogger()
{
StackFrame stackFrame = new StackFrame(0, false);
if (stackFrame.GetMethod() == (MethodBase) LoggerFactoryBase.getCurrentClassLoggerMethodInfo)
stackFrame = new StackFrame(1, false);
Type declaringType = stackFrame.GetMethod().DeclaringType;
if (declaringType == (Type) null)
throw new InvalidOperationException(string.Format("Can not determine current class. Method: {0}", (object) stackFrame.GetMethod()));
return this.GetLogger(declaringType);
}
我可以看到抛出异常的地方。
我的第一直觉是检查是否有任何系统、框架或项目更新导致它,但自上次成功部署以来没有做任何有意义的事情。
现在...关于此问题的 "funny" 事情是当我在构造函数中添加带有 Trace.WriteLine("Test");
的行时,GetCurrentClassLogger
执行得很好。
我能够将这个问题与编译器的代码优化联系起来。如果我在项目属性的 Build 选项卡中禁用它,它也可以正常执行。
问题:什么会导致 StackFrame
停止提供 DeclaringType
?
同时我正在使用它作为解决方法,但我更喜欢使用原始方法:
this.logger = loggerFactory.GetLogger(this.GetType());
如有任何帮助,我们将不胜感激。
如果不查看发出的 CIL,很难确切地说行为不同的原因,但我猜它正在内联。 如果您将构造函数属性化为:
,您可能会得到想要的结果[MethodImpl(MethodImplOptions.NoInlining)]
正如 NInject 代码所做的那样。该方法可能会被内联,因为它只是分配了一个只读字段的一行。您可以在此处了解内联如何影响 StackFrames:http://www.hanselman.com/blog/ReleaseISNOTDebug64bitOptimizationsAndCMethodInliningInReleaseBuildCallStacks.aspx
就是说,这种不可预测性是使用堆栈帧不是一个好主意的原因。如果您想要不必指定 class 名称的语法,正在使用 VS 2012,并且您的文件名与您的 class 名称匹配,您可以在扩展方法上使用 CallerFilePath 属性并保留相同的像现在一样的行为,而不使用堆栈框架。看: https://msdn.microsoft.com/en-us/library/hh534540.aspx
更新:
另一种方法可能是直接注入 ILogger 并让您的容器负责确定要注入的组件的类型(因为它肯定知道)。这看起来也更干净。请参阅此处的示例:https://github.com/ninject/ninject.extensions.logging/issues/19
this.Bind<ILogger>().ToMethod(context =>
{
var typeForLogger = context.Request.Target != null
? context.Request.Target.Member.DeclaringType
: context.Request.Service;
return context.Kernel.Get<ILoggerFactory>().GetLogger(typeForLogger);
});