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条件检查和记录调用.
具体来说,应该使用内置字符串格式而不是字符串连接,如果消息是方法调用的结果,那么应该跳过前提条件,并且应该有条件地抛出相关的异常.
不合规的代码示例
Run Code Online (Sandbox Code Playgroud)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()); }
我不是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也是如此.
字符串连接的意思是 LOGGER.info("The program started at " + new Date());
记录器的内置格式意味着
LOGGER.info("The program started at {}", new Date());
考虑以下日志记录语句:
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)
仅当“上面解释的级别规则”满足时,才会导致字符串形成。
那么哪个更聪明?
查阅此网址。
首先让我们了解问题,然后谈谈解决方案。
我们可以简单点,假设下面的例子
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)