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

Nax*_*s84 17 java string logging concatenation sonarlint

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

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
Run Code Online (Sandbox Code Playgroud)

附注:包含此行的方法可能会经常调用.

这个问题的描述是

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

将需要进一步评估的消息参数传递到Guava com.google.common.base.Preconditions检查可能会导致性能下降.这是因为无论是否需要它们,必须在实际调用方法之前解析每个参数.

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

相反,您应该构造代码以将静态或预先计算的值传递到Preconditions条件检查和记录调用.

具体来说,应该使用内置字符串格式而不是字符串连接,如果消息是方法调用的结果,那么应该跳过前提条件,并且应该有条件地抛出相关的异常.

不合规的代码示例

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
Run Code Online (Sandbox Code Playgroud)

合规解决方案

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()); }
Run Code Online (Sandbox Code Playgroud)

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

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

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
Run Code Online (Sandbox Code Playgroud)

LOGGEr.debug("Comparing objects: {} and {}",object1, object2);
Run Code Online (Sandbox Code Playgroud)

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

lus*_*uso 11

我相信你有答案.

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

另请查看此主题.并查看Icaro的回答评论.

看看StringBuilder也是如此.


Shu*_*dey 8

字符串连接的意思是 LOGGER.info("The program started at " + new Date());

记录器的内置格式意味着
LOGGER.info("The program started at {}", new Date());

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


Bre*_*min 6

考虑以下日志记录语句:

LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
Run Code Online (Sandbox Code Playgroud)

这是什么“调试”?

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

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

"Comparing objects: " + object1 + " and " + object2
Run Code Online (Sandbox Code Playgroud)

b)一个是记录器的级别。因此,LOGGER对象的级别是什么:还必须在代码或某些xml中定义它,否则它取自其祖先的级别。

现在我为什么要告诉所有这些?

现在,仅当且仅当以下情况时,才会打印日志记录声明(或更确切地说,将其发送到“附加信息”):

Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code
Run Code Online (Sandbox Code Playgroud)

级别的可能值可以是

DEBUG < INFO <  WARN < ERROR
Run Code Online (Sandbox Code Playgroud)

(根据日志框架的不同,可能会少一些)

现在让我们回到问题:

"Comparing objects: " + object1 + " and " + object2
Run Code Online (Sandbox Code Playgroud)

即使我们发现上述“级别规则”失败,也总是会导致字符串的创建。

然而,

LOGGER.debug("Comparing objects: {} and {}",object1, object2);
Run Code Online (Sandbox Code Playgroud)

仅当“上面解释的级别规则”满足时,才会导致字符串形成。

那么哪个更聪明?

查阅此网址

  • 谢谢链接。您能否提供链接内容的简短摘要?(以防万一将来某个时候链接中断) (2认同)

ahm*_*l88 6

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

我们可以简单点,假设下面的例子

LOGGER.debug("User name is " + userName + " and his email is " + email );
Run Code Online (Sandbox Code Playgroud)

上述日志消息字符串由4个部分组成
将要求3个的字符串连接到构成。

现在,让我们来看看这个日志语句的问题是什么。

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

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

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

解决方案 1(不是最优的)
代替使用String concatenations,我们可以使用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());
Run Code Online (Sandbox Code Playgroud)

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

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

LOGGER.debug("User name is {} and his email is {}", userName, email);
Run Code Online (Sandbox Code Playgroud)