9

这里有两个似乎被普遍接受的陈述,但我无法克服:

1) Scala 的别名参数优雅地替换了令人讨厌的 log4j 使用模式:

if (l.isDebugEnabled() ) { 
      logger.debug("expensive string representation, eg: godObject.toString()") 
   }

因为在方法调用之前不会 对 by-name-parameter(Scala 特定的语言功能)进行评估。

2)但是,这个问题是通过slf4f中的参数化日志记录来解决的:

logger.debug("expensive string representation, eg {}:", godObject[.toString()]);

那么,这是如何工作的呢?slf4j 库中是否有一些低级魔法会阻止在“调试”方法执行之前对参数进行评估?(这甚至可能吗?图书馆可以影响语言的这样一个基本方面吗?)

或者仅仅是一个简单的事实,一个对象被传递给方法——而不是一个字符串?(如果适用,可能在 debug() 方法本身中调用该对象的 toString())。

但是,log4j 不也是这样吗?(它确实有带有对象参数的方法)。这是否意味着如果你传递一个字符串——如上面的代码——它的行为与 log4j 相同?

我真的很想对这件事有所了解。

谢谢!

4

1 回答 1

26

slf4j 没有魔法。日志记录的问题曾经是,如果你想记录让我们说

logger.debug("expensive string representation: " + godObject)

那么无论是否在记录器中启用了调试级别,您总是评估godObject.toString()哪个可能是一项昂贵的操作,然后还要进行字符串连接。这仅仅是因为在 Java(和大多数语言)中,参数在传递给函数之前会被评估。

这就是引入 slf4j 的原因logger.debug(String msg, Object arg)(以及更多参数的其他变体)。整个想法是,您将廉价的参数传递给debug函数,它会调用toString它们并将它们组合成一条消息,只有在调试级别打开时。

请注意,通过调用

logger.debug("expensive string representation, eg: {}", godObject.toString());

你大大降低了这个优势,因为这样你godObject在传递它之前一直在转换debug,无论调试级别是什么。你应该只使用

logger.debug("expensive string representation, eg: {}", godObject);

但是,这仍然不理想。它只保留调用toString和字符串连接。但是,如果您的日志消息需要一些其他昂贵的处理来创建消息,那将无济于事。就像您需要调用一些expensiveMethod来创建消息一样:

logger.debug("expensive method, eg: {}",
    godObject.expensiveMethod());

thenexpensiveMethod在传递给 之前总是被评估logger。为了让 slf4j 有效地完成这项工作,您仍然必须求助于

if (logger.isDebugEnabled())
    logger.debug("expensive method, eg: {}",
        godObject.expensiveMethod());

Scala 的名称调用在这方面有很大帮助,因为它允许您将任意一段代码包装到一个函数对象中,并仅在需要时评估该代码。这正是我们所需要的。例如,让我们看一下 slf4s。这个库公开了类似的方法

def debug(msg: => String) { ... }

为什么没有像 slf4j 中的参数Logger?因为我们不再需要它们了。我们可以只写

logger.debug("expensive representation, eg: " +
    godObject.expensiveMethod())

我们不传递消息及其参数,我们直接传递一段对消息进行评估的代码。但前提是记录器决定这样做。如果调试级别未打开,logger.debug(...)则不会评估其中的任何内容,整个事情都会被跳过。既没有expensiveMethod被调用,也没有任何toString调用或字符串连接发生。所以这种方法是最通用和最灵活的。您可以传递任何计算结果为 to 的表达式,String无论debug它有多复杂。

于 2012-11-02T18:57:26.820 回答