记录日志的正确方式

8
所以我的问题是关于日志记录,以及如何处理可能对您的代码和运行时行为产生影响的日志语句。
日志文件...每个程序都应该编写这些文件以进行适当的问题解决,但如何正确地执行此操作?
大多数日志语句很难获取,因为它们应该提供有用的信息,并且即使完全禁用日志记录,它们也总是被构建。
日志记录可以通过xml、inCode或某些设置进行配置,但这并不能解决字符串构建问题。
例如,以下代码总是加载一个巨大的延迟加载树,在正常执行期间永远不会完全加载。
整个树仅为了显示在日志文件中而创建
(好吧,我知道...,但这只是替换了大多数程序中存在的复杂日志记录方法,在正常发布执行期间不应执行)
public void SomeMethod(){
    logger.Debug(someObject.GetHeavyDescriptionFromLazyTree());
}

即使关闭了日志记录,someObject.GetHeavyDescriptionFromLazyTree() 方法仍然会被调用。因此,有一些常见的解决方法,如:

public void SomeMethod(){
#if DEBUG
    logger.Debug(someObject.GetHeavyDescriptionFromLazyTree());
#endif       
}

public void SomeMethod(){
    if(logger.DoLogDebug())
        logger.Debug(someObject.GetHeavyDescriptionFromLazyTree());
}

我认为很明显,#if DEBUG编译器标记不是用于生产代码的解决方案。如果我使用logger.DoLogDebug()将会存在过多的检查代码,以判断是否启用日志记录... 因此这也不能成为解决方案。我认为ConditionalAttribute可以帮助解决问题,但它也受到编译器标记的限制,并且无法禁用对GetHeavyDescriptionFromLazyTree的调用。
[Conditional("DEBUG")]  
public void Debug(string debugMessage) 
{ 
    Console.WriteLine(debugMessage); 
} 

因此,我正在寻找一种日志记录解决方案,它具有以下特点:

  • 不会使用调试语句过度破坏我的源代码
  • 日志级别在运行时而不是编译时决定
  • 仅在必要时解析语句(logLevel> definedLogLevel)

最佳答案将提供额外的日志行;-)

编辑:我正在寻找.NET 2.0中的解决方案。

3个回答

6

“懒惰日志记录”的一种方法是使用表达式(创建一个返回消息的函数委托),而不是直接评估:

logger.Debug(() => someObject.GetHeavyDescriptionFromLazyTree());

这种方法有其自身的缺点(代理的构造),但它可能比昂贵的方法更便宜。通常你会提供这个重载,只在你知道评估表达式很昂贵的情况下使用它。


谢谢你的解决方案,听起来很不错,但我忘了它必须使用.NET 2.0来解决 :-( - oberfreak
@oberfreak,在这种情况下,我认为标准的“if”检查是你最好的选择;希望你没有太多记录开销很大的地方。 - Dan Bryant
为什么这种方法在.NET 2.0中不起作用?我经常在VS.NET 2010和以.NET 2.0作为目标平台时这样做。 - Uwe Keim
3
如果你想将.NET 2.0作为目标环境,但是在开发时拥有.NET 3.5编译器(VS 2008或2010),那么这个方法也可以工作。当然,如果使用VS 2003,则无法实现这个方法。 - Doc Brown
没想到这个...不错的解决方案!谢谢! - oberfreak

4

大多数日志语句都很昂贵,因为它们应该提供有用的信息,并且即使完全禁用日志记录,它们也总是被构建。

这不完全正确。请看这里:

if (Log.IsDebugEnabled)
{
    StackFrame f = new StackFrame(2);
    Log.Debug("Very expensive message to put together and stuff." + f.GetMethod().Name);
}

你的信息如果调试日志未处于启用状态,则不会被构建。这样做符合你的三个条件,是相当标准的做法。
编辑:我应该补充说明一下,IsDebugEnabled 考虑了全局调试日志是否已启用以及此特定记录器的任何覆盖配置。这很具体,是在运行时确定的,但也会被缓存直到配置更改。
编辑2:

如果我使用logger.DoLogDebug(),那么检查日志是否已启用会产生过多的代码…所以这也不是解决方案。

我上面概述的 if 语句并不过多,而且计算非常快。我建议你尝试一下,并自行评估其性能是否足够优秀。如果你发现自己在各个地方都有这些 if 块,标准建议是尽可能将它们合并。
关键的思想在于 log4net 有五个日志级别,每个级别都可以完全独立地考虑。因此,你可能在一个地方使用 IsDebugEnabled,在另一个地方使用 IsInfoEnabled。要用条件编译实现这个功能并不容易,那样会证明更加麻烦。这种情况在我所从事的代码中其实相当普遍。
if (Log.IsDebugEnabled)
{
    StackFrame f = new StackFrame(2);
    Log.Debug("Very expensive message to put together and stuff." + f.GetMethod().Name);
}
if (Log.IsInfoEnabled)
{
    StackFrame f = new StackFrame(2);
    Log.Debug("Very expensive message to put together and stuff." + f.GetMethod().Name);
}
if (Log.IsErrorEnabled)
{
    StackFrame f = new StackFrame(2);
    Log.Debug("Very expensive message to put together and stuff." + f.GetMethod().Name);
}

如果你想根据日志级别、类别甚至整个命名空间来定制日志输出,这就是最佳选择。


1
这确实是Log4Net FAQ推荐的“最快”的方式:http://logging.apache.org/log4net/release/faq.html#perf-not-logging - Ross Patterson
谢谢你的回答,但是很抱歉我不想要记录决策代码在我的业务逻辑里,这样可以更容易地进行扩展和替换。如果决策发生在业务逻辑中,每次你想引入一个新的日志级别时,就需要检查1000多行代码,这样将来不够灵活。(该软件将有大约10-15年的使用寿命,因此应具有可扩展性和可维护性) - oberfreak
@oberfreak,决策严格来自于log4net配置,而不是你的代码。从另一个程序中使用的库将使用该程序的配置,而不是它们自己的配置。 - user47589
@Inuyasha 是的,那是我的下一个问题 :-) 所以我想我会使用委托和lambda表达式来解决这个问题。 - oberfreak

0

您可以通过 web.config 或 app.config 来使其可配置。添加一个名为 EnableLogging 的项 (<add key="EnableLogging" value="false"/>)。然后创建一个静态方法,从 .config 文件中获取该值并用于检查。


4
我认为您几乎完全错过了问题的要点。 - Doc Brown
我认为这将与他的第二个选项DoLogDebug()检查一起使用,但该方法不需要太多逻辑。毕竟,有什么比从web.config或app.config获取appsettings值更简单的呢? - Alex Mendez
“EnableLogging”甚至不是log4net配置键,既然log4net已经具备自身的启用配置功能,为什么还要这样做呢? - user47589
你是对的Inuyasha,我错过了他在这个问题中标记log4net的事实。你是绝对正确的。Log4net提供了自己的选项。 - Alex Mendez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接