内置字符串格式化与字符串连接作为日志记录参数

时间:2017-02-22 10:02:02

标签: string performance formatting concatenation sonarlint

我正在使用SonarLint向我显示以下行中的问题。

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);

旁注:包含此行的方法可能会经常被调用。

该问题的描述是

  

"前提条件"和记录参数不应该要求评估   (鱿鱼:S2629)

     

将需要进一步评估的消息参数传递给Guava   com.google.common.base.Preconditions检查可以带来效果   罚款。这是因为他们是否需要,每个论点   必须在实际调用方法之前解决。

     

类似地,将连接的字符串传递给日志记录方法也可以   由于串联将导致不必要的性能损失   每次调用该方法时执行,无论是否为日志   级别足够低以显示消息。

     

相反,您应该构造代码以传递静态或预先计算   值进入前置条件条件检查和记录调用。

     

具体来说,应该使用内置字符串格式代替   字符串连接,如果消息是方法的结果   打电话,然后应该跳过前提条件altoghether,和   相反的例外应该有条件地抛出。

     

不合规代码示例

logger.log(Level.DEBUG, "Something went wrong: " + message);  // Noncompliant; string concatenation performed even when log level too high to show DEBUG messages

LOG.error("Unable to open file " + csvPath, e);  // Noncompliant

Preconditions.checkState(a > 0, "Arg must be positive, but got " + a); // Noncompliant. String concatenation performed even when a > 0

Preconditions.checkState(condition, formatMessage());  //Noncompliant. formatMessage() invoked regardless of condition

Preconditions.checkState(condition, "message: %s", formatMessage()); // Noncompliant
     

合规解决方案

logger.log(Level.SEVERE, "Something went wrong: %s", message);  // String formatting only applied if needed

logger.log(Level.SEVERE, () -> "Something went wrong: " + message); //since Java 8, we can use Supplier , which will be evaluated lazily

LOG.error("Unable to open file {}", csvPath, e);

if (LOG.isDebugEnabled() {   LOG.debug("Unable to open file " + csvPath, e);  // this is compliant, because it will not evaluate if log level is above debug. }

Preconditions.checkState(arg > 0, "Arg must be positive, but got %d", a);  // String formatting only applied if needed

if (!condition) {   throw new IllegalStateException(formatMessage()); // formatMessage() only invoked conditionally }

if (!condition) {   throw new IllegalStateException("message: " + formatMessage()); }

我不能100%确定我是否理解这一点。那为什么这真的是一个问题。特别是关于使用字符串连接时性能的部分。因为我经常读到字符串连接比格式化更快。

编辑:也许有人可以解释我之间的区别

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);

LOGGEr.debug("Comparing objects: {} and {}",object1, object2);

在后台。因为我认为在传递给方法之前会创建String。对?所以对我来说没有区别。但显然我错了,因为SonarLint抱怨它

4 个答案:

答案 0 :(得分:8)

我相信你有答案。

预先计算连接条件检查。因此,如果您有条件地将日志框架调用10K次并且所有日志框架的评估结果为false,那么您将无缘无故地连接10K次。

同时检查this topic。并查看Icaro的回答。

也看看StringBuilder

答案 1 :(得分:5)

考虑以下日志记录声明:

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);

这是什么'调试'?

这是日志记录语句的级别,而不是LOGGER的级别。 看,有两个级别:

a)其中一个日志记录语句(在此处调试):

"Comparing objects: " + object1 + " and " + object2

b)一个是LOGGER的等级。那么,LOGGER对象的级别是什么: 这也必须在代码或某些xml中定义,否则它需要从它的祖先开始。

现在我为什么要说这一切?

现在,当且仅当以下情况下,将打印日志记录语句(或更多技术术语发送到其'appender'):

Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code

Level的可能值可以是

DEBUG < INFO <  WARN < ERROR

(根据日志框架可能会有更多内容)

现在让我们回答问题:

"Comparing objects: " + object1 + " and " + object2
即使我们发现上面解释的“级别规则”失败,

也会始终导致字符串的创建。

然而,

LOGGER.debug("Comparing objects: {} and {}",object1, object2);
如果“上面说明的级别规则”满足,

只会导致字符串形成。

哪个更聪明?

咨询url

答案 2 :(得分:3)

字符串连接意味着 LOGGER.info(&#34;程序从&#34; + new Date()开始);

内置格式化记录器意味着 LOGGER.info(&#34;程序从{}&#34;开始,新的Date());

非常好的文章,以了解差异 http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument

答案 3 :(得分:1)

首先让我们了解问题,然后讨论解决方案。

我们可以简化一下,假设以下示例

LOGGER.debug("User name is " + userName + " and his email is " + email );

上面的日志消息字符串由4部分组成
并且将需要构造3个String串联

现在,让我们转到此日志记录语句的问题所在。

假设我们的日志记录级别为OFF,这意味着我们现在对日志记录不感兴趣。

我们可以想象字符串连接(慢速操作)将被始终应用并且不会考虑日志记录级别。

哇,在理解了性能问题之后,我们来讨论最佳实践。

解决方案1(并非最佳)
除了使用字符串串联,我们还可以使用String Builder

StringBuilder loggingMsgStringBuilder = new StringBuilder();
loggingMsgStringBuilder.append("User name is ");
loggingMsgStringBuilder.append(userName);
loggingMsgStringBuilder.append(" and his email is ");
loggingMsgStringBuilder.append(email );
LOGGER.debug(loggingMsgStringBuilder.toString());

解决方案2(最佳)
在检查调试级别之前,我们不需要构造日志记录消息。
因此,我们可以将日志消息格式所有部分作为参数传递给LOGGING引擎,然后将字符串连接操作委托给它,然后根据记录级别,引擎将决定是否进行串联。

因此,建议使用参数化日志记录作为以下示例

LOGGER.debug("User name is {} and his email is {}", userName, email);