调试语句的最佳实践是什么,其中包含字符串操作?
我经常发现自己在log4net和log4j的调试语句中添加了连接字符串或使用字符串格式化器,我应该用“if debug”块来包围这些调试语句,以便通过处理这些参数来阻止自己浪费资源,即使调试语句将会不打印出来?
我会假设检查if(isDebug)是否比发生字符串操作更快更有效,但是当调试级别设置高于debug时,它会导致程序以不同的方式运行(更快),这可能意味着同步问题当我写入日志时,在生产中发生的事情不会发生。
对于Java,您可以尝试log5j 。
log4j的:
log.debug("This thing broke: " + foo + " due to bar: " + bar + " on this thing: " + car);
log5j:
log.debug("This thing broke: %s due to bar: %s on this thing: %s", foo, bar, car); log.debug("Exception #%d", aThrowable, exceptionsCount++);
我会说这取决于调用调试语句的频率以及性能的重要性。 谨防过早优化和所有。
SLF4J常见问题解答中详细解答了这个问题 。 简而言之,使用参数化消息。 例如, entry
是一个对象,你可以写:
Object entry = new SomeObject(); logger.debug("The entry is {}.", entry);
在评估是否记录之后,并且仅当决策是肯定的时,记录器实现将格式化消息并用条目的字符串值替换“{}”对。 换句话说,在禁用日志语句的情况下,此表单不会产生参数构造的成本。
以下两行将产生完全相同的输出。 但是,在禁用日志记录语句的情况下,第二种forms将比第一种forms的性能至少提高30倍。
logger.debug("The new entry is "+entry+"."); logger.debug("The new entry is {}.", entry);
你有没有测量连接这些字符串需要多少额外时间? 鉴于日志记录基础结构将获取结果消息,检查是否需要将它们分派到(可能)多个接收器,然后可能使用某些I / O进行写入,那么您可能无法获得任何信息。 我的感觉是,除非你的toString()机制很慢,否则这可能是一个优化太过分了。
我也对这种方法保持警惕,以防有人写这样的东西(我们知道他们不应该,但我以前见过这个)
if (Log.isDebugEnabled()) { Log.debug("Received " + (items++) + " items"); }
然后这将根据您的日志记录级别工作/失败。
在log4j中,建议采用以下最佳实践:
if ( log.isDebugEnabled() ) { log.debug("my " + var + " message"; }
这节省了字符串连接等系统资源。假设启用调试级别时程序执行速度较慢,这是正确的,但这是正确的:观察中的系统因观察而被更改。 同步问题(主要)处理线程之间的不幸时序或可变可见性,这两者都不会直接受到调试级别更改的影响。 您仍然需要与系统“一起玩”以重现multithreading问题。
我们采用了这种做法,在每个类中定义一个私有静态只读布尔值DEBUG变量。
private static readonly log4net.ILogger LOG = log4net.LogManager.GetLogger(); private static readonly bool DEBUG = LOG.IsDebugEnabled;
每个实际的调试日志行都是这样的
if (DEBUG) LOG.Debug(...);
其中……可以具有任意复杂性,仅在需要调试时进行评估。
请参阅: http : //logging.apache.org/log4net/release/faq.html ,答案为“什么是真正最快的(非)记录方式?”
这对我们有用,因为我们只在启动时读取日志配置。
由于每个函数调用至少有一个调试语句,我们觉得必须写if(DEBUG)值得努力在关闭debuging时获得最大性能。 我们在调试开启和关闭的情况下进行了一些测量,发现性能提高了10%到20%。 我们还没有测量if(DEBUG)的效果。
顺便说一句:我们只为调试消息执行此操作。 警告,信息和错误直接通过LOG.Warn等生成。
试试slf4j( http://www.slf4j.org/ )。 你写的语句如下:
log.fine("Foo completed operation {} on widget {}", operation, widget);
在确定日志级别足够高之前,日志消息未在库内组装。 我觉得这是最好的答案。
(这与上面的log5j解决方案非常相似。)
使用C#,我们已经开始使用委托来处理昂贵的日志语句。 只有在日志级别足够高时才会调用它:
log.Debug(()=> "Getting the value " + SomeValue() " is expensive!");
这可以防止在检查的级别与记录的级别不同的日志记录错误,即:
if(log.Level == Level.Info) log.Debug("Getting the value " + SomeValue() " is expensive!");
而且我觉得它更具可读性。
[编辑]如果因为不适用于Log4Net而被低估 – 那么在Log4Net周围写一个包装器来做这件事是微不足道的 。
条件编译与最终常量一起使用,最终常量是静态最终变量。 类可以像这样定义常量:
private static final boolean DEBUG = false;
定义了这样的常量,其中的任何代码都是:
if (DEBUG) { // code }
实际上并没有编译到类文件中 。 要激活类的调试,只需要将常量的值更改为true并重新编译该类(您可以使用两个版本的二进制文件,一个用于开发,一个用于生产)。
然而,由于几个原因,这种解决方案是次优的。
我倾向于在isDebug语句中包含所有调用调用。 我不认为这是一个不成熟的优化,这只是一个好习惯。
担心应用程序以不同的速度运行并不是真的合理,因为处理器上的负载可能/将影响您的应用程序,而不是调试代码。
从log4j配置文件中查找外化日志级别。 这样您就可以根据您的环境选择打开或关闭日志(并避免所有这些字符串连接)。
if(log.isDebugEnabled()) { log.debug("Debug message"); }