【问题标题】:Even with slf4j, should you guard your logging?即使使用 slf4j,您是否应该保护您的日志记录?
【发布时间】:2012-01-16 16:09:24
【问题描述】:

在这里帮助我进行辩论.. :)

这里的 slf4j 站点 http://www.slf4j.org/faq.html#logging_performance 表示由于参数化日志记录,日志保护不是必需的。 IE。而不是写:

if(logger.isDebugEnabled()) {
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

你可以逃脱:

Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);

这真的没问题,还是会产生(尽管较低)创建传递给跟踪方法的静态字符串的成本..?

【问题讨论】:

    标签: java performance logging slf4j


    【解决方案1】:

    我会试着从另一个角度把我的两分钱放在一边

    参数化日志记录究竟有什么好处?

    您只需推迟 toString() 调用字符串连接 直到真正需要,也就是您真正需要记录消息的时候。当特定的日志记录操作被禁用时,这会优化性能。如果不确定,请查看source code for SLF4J

    参数化的日志记录是否让守卫在所有情况下都无用?

    没有。

    日志守卫在哪些情况下有用?

    当有其他潜在的昂贵操作时。

    例如(在这个特定的日志记录操作被禁用的情况下),如果我们没有没有日志保护

    logger.debug("User name: {}", getUserService().getCurrentUser());
    
    1. 我们将支付来自obj = getUserService().getCurrentUser()的费用
    2. 我们将从"User name: " + obj.toString() 节省成本

    如果我们使用日志保护

    if (logger.isDebugEnabled()) {
        logger.debug("User: {}", getUserService().getCurrentUser());
    }
    
    1. 我们将支付logger.isDebugEnabled()的费用
    2. 我们会节省来自obj = getUserService().getCurrentUser() 的费用
    3. 我们将从"User name: " + obj.toString() 节省成本

    在后一种情况下,启用此特定日志记录操作后,我们会以检查isDebugEnabled() 两次为代价来节省这两种成本。

    注意:这只是一个示例,并非试图在此处讨论好/坏做法。

    【讨论】:

    • 感谢 fgelz,这是最清楚的解释,所以我改变了主意。让您认为,尽管有所有不同的框架,但在延迟昂贵的操作方面,日志世界仍有创新的空间......也许是委托
    • @MarkD JDK 8 Project Lambda 将帮助创新 (openjdk.java.net/projects/lambda)
    • 您还应该知道调用可变参数方法(例如 logger.debug(String,Object..) 实际上是在 logger.debug(String,new Object[...] ). 因此,如果您不包装调用,则每次遍历此方法时,您都会创建(然后处理)一个新的 Object 数组,即使您从未写出内容。如果您有高性能系统或体验内存压力,那么这会随着时间的推移而增加;如果经常使用,logger.isDebugEnabled() 调用将由一个像样的 JIT 内联。SLF4J 专门针对一个和两个参数进行此操作,但对于更多参数,您会流失。
    • 在 Java 8 lambda 的现代世界中,它们将是一种非常好的延迟方法调用的方法,但不幸的是,没有简单的方法可以提供这一点。
    • 查看 logger.debug() 的实现,它会通过调用 logger.isDebugEnabled() 来检查日志级别。因此,我们还需要支付第一种情况下logger.isDebugEnabled()的费用。
    【解决方案2】:

    编写和阅读所有这些if(logger.isDebugEnabled()) {} 所花费的时间可能与它们为您节省的时间一样多。

    当然,调用 log 方法不是免费的,但调用 isDebugEnabled() 也是如此。因此,如果您使用此模式,您将为每个处于活动状态的日志语句支付更多费用(因为日志框架将检查级别两次)。

    它也使代码混乱。

    在实践中,我还没有发现性能损失大到足以打扰。

    如果日志记录对您来说太慢,请编写一个非阻塞附加程序,将日志事件推送到队列中而无需进行几次检查,并使用后台线程来处理它们。

    背景:标准附加程序都是同步的,因此在多线程应用程序中进行日志记录可能会导致所有线程等待将日志消息写入文件的大量小暂停。

    【讨论】:

    • 谢谢,我也是这么想的。在我给你加分之前,我会等待看到普遍的共识:) 我喜欢关于 appenders 的提示,这对我们来说很可能是个问题。
    • “调用日志方法”和“调用 isDebugEnabled()”都可以实际上是免费的,我认为有一些 JIT 优化。我不确定,但如果是为 sl4jf 提供实际高性能实现的人,我会在 rutime 为禁用级别放置一个无操作方法的实现,以便 JIT 可以删除调用。
    【解决方案3】:

    由于创建字符串,未使用保护。

    相反,它通常用于避免可能昂贵的参数表达式,例如entry[i].retrieveExtendedDebugInformation().formatNicely()。为此,logback 确保仅在实际打印日志消息时才评估参数,而 log4j 始终在调用 debug() 之前评估参数。

    这里唯一的候选者是String.valueOf(entry[i]),它也不是很贵,所以你可以说这个守卫完全没有必要。

    【讨论】:

    • 好的 - 这是有道理的,是对事物的另一种看法。因此,在您看来,这是对是否进行防护的判断,而防护应该仅用于相对罕见(昂贵)的操作?...引入参数减少了这些操作的数量..
    • 是的,并且根据操作的成本保护通常只对循环内的跟踪语句有意义。甚至有人可能会争辩说,如果您的调试语句非常昂贵以至于即使您处于更高的日志级别也会减慢课程速度,那么您没有正确使用 log.debug,特别是您可能正在使用while(condition){log.debug(complicatedExpression); doSomething();}而你应该做log.debug(complicatedExpression); while(condition){log.trace(simpleExpression); doSomething();}。但我不是狂热者,我承认我经常写守卫。
    • “不是很贵”仍然足够昂贵,足以对​​具有大量日志语句的程序产生影响。
    • logback 如何防止在方法调用之前评估参数?
    • 在 log4j 的 log.debug("Entry is: " + entry); 中,必须在调用 log.debug() 之前构造参数,因此在框架有机会确定是否需要日志语句之前。为此,会调用entry.toString(),这可能需要一些时间。 Logback 的log.debug("Entry is : {}", entry); 没有这个问题,entry 是一个单独的参数,它的toString() 不必在debug() 之前调用。因此 logback 有机会在调用 entry.toString() 之前看到不需要日志条目。
    【解决方案4】:

    这样的日志语句的问题:

    logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
    

    是将值连接到String 会做很多工作,如果调试日志关闭,则永远不会使用它。因此,在这种情况下,在执行此行之前检查调试日志记录是否打开是有益的。当您只是传递参数时:

    logger.debug("The entry is {}.", entry);
    

    那么它就不需要构建一个从未使用过的String,也不需要检查;仅将参数传递给方法不会产生很高的开销。

    请注意,如果日志语句中的参数表达式相对昂贵,则首先检查日志记录级别可能仍然有益。

    【讨论】:

    • 您可以通过将它们放在匿名类的 toString() 方法中来处理昂贵的表达式,这样在实际需要之前不会对其进行评估。
    • @ThorbjørnRavnAndersen 是的,但这会使代码变得非常冗长。
    【解决方案5】:

    请不要使用if 语句,因为我每次看代码都是这样的

    if (logger.isDebug()) {
       logger.debug("Of course it's debug {}", sadFace);
    }
    

    我哭了。

    我希望创建静态字符串的成本低到对 99% 的用户来说是微不足道的。

    【讨论】:

    • slf4j 不使用{0} 构造。
    • 如果您使用大量调试日志记录,大多数大型应用程序都会这样做,那么开销可能会很大。您正在为垃圾收集器创建更多垃圾来收集,并且某些系统已经在该区域具有足够的压力。如果您不进行任何调用来获取您的参数,并且您只使用常量字符串作为参数字符串,那很好,直到有人出现并更改它以做一些不那么琐碎的事情。从“编码标准”的角度来看,只需要 if 语句会更容易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-21
    • 2017-05-06
    • 2018-03-28
    • 1970-01-01
    相关资源
    最近更新 更多