内置字符串格式与字符串连接作为日志参数
Built-in string formatting vs string concatenation as logging parameter
我正在使用 SonarLint,它在以下行中显示了一个问题。
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
旁注:包含此行的方法可能会经常被调用。
这个问题的描述是
"Preconditions" and logging arguments should not require evaluation
(squid:S2629)
Passing message arguments that require further evaluation into a Guava
com.google.common.base.Preconditions check can result in a performance
penalty. That's because whether or not they're needed, each argument
must be resolved before the method is actually called.
Similarly, passing concatenated strings into a logging method can also
incur a needless performance hit because the concatenation will be
performed every time the method is called, whether or not the log
level is low enough to show the message.
Instead, you should structure your code to pass static or pre-computed
values into Preconditions conditions check and logging calls.
Specifically, the built-in string formatting should be used instead of
string concatenation, and if the message is the result of a method
call, then Preconditions should be skipped altoghether, and the
relevant exception should be conditionally thrown instead.
Noncompliant Code Example
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
Compliant Solution
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 正在抱怨它
我相信你已经有了答案。
连接是在条件检查之前计算的。因此,如果您有条件地调用您的日志记录框架 10K 次并且所有这些都评估为 false,那么您将无缘无故地连接 10K 次。
同时勾选 this topic。并查看 Icaro 的回答评论。
也去StringBuilder看看。
字符串连接方式
LOGGER.info("The program started at " + 新日期());
内置记录器格式
LOGGER.info("The program started at {}", 新日期());
非常好的文章,了解其中的区别
http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument
考虑以下日志记录语句:
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
这是什么'debug'?
这是日志语句的级别,不是LOGGER的级别。
看,有 2 个级别:
a) 日志语句之一(此处为调试):
"Comparing objects: " + object1 + " and " + object2
b) 一个是LOGGER的级别。那么,LOGGER 对象的级别是多少:
这也必须在代码或某些 xml 中定义,否则它需要从它的祖先 .
获得水平
现在我为什么要讲这些?
现在,当且仅当:
Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code
Level 的可能值可以是
DEBUG < INFO < WARN < ERROR
(根据日志框架可以有更多)
现在回到问题:
"Comparing objects: " + object1 + " and " + object2
将始终导致创建字符串,即使我们发现上面解释的 'level rule' 失败。
然而,
LOGGER.debug("Comparing objects: {} and {}",object1, object2);
只有在'level rule explained above'满足时才会形成字符串。
那么哪个更聪明?
参考这个url。
先了解问题,再说解决方案。
我们可以简单点,假设下面的例子
LOGGER.debug("User name is " + userName + " and his email is " + email );
上面的日志消息字符串由4部分组成
并且 将需要构造 3 个字符串连接 。
现在,让我们来看看这个日志语句的问题是什么。
假设我们的日志级别是OFF
,这意味着我们现在对日志不感兴趣。
我们可以想象 String concatenations(缓慢的操作)将 ALWAYS 应用并且不会考虑日志记录级别。
哇,了解了性能问题,我们来谈谈最佳实践。
解决方案 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());
方案二(最优)
在检查调试级别之前,我们不需要构造日志消息。
所以我们可以将logging message format和all parts作为参数传递给LOGGING引擎,然后委托String concatenations 操作,并且 根据日志记录级别 ,引擎将决定是否连接。
所以,建议使用参数化日志记录,如下例
LOGGER.debug("User name is {} and his email is {}", userName, email);
我正在使用 SonarLint,它在以下行中显示了一个问题。
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
旁注:包含此行的方法可能会经常被调用。
这个问题的描述是
"Preconditions" and logging arguments should not require evaluation (squid:S2629)
Passing message arguments that require further evaluation into a Guava com.google.common.base.Preconditions check can result in a performance penalty. That's because whether or not they're needed, each argument must be resolved before the method is actually called.
Similarly, passing concatenated strings into a logging method can also incur a needless performance hit because the concatenation will be performed every time the method is called, whether or not the log level is low enough to show the message.
Instead, you should structure your code to pass static or pre-computed values into Preconditions conditions check and logging calls.
Specifically, the built-in string formatting should be used instead of string concatenation, and if the message is the result of a method call, then Preconditions should be skipped altoghether, and the relevant exception should be conditionally thrown instead.
Noncompliant Code Example
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
Compliant Solution
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 正在抱怨它
我相信你已经有了答案。
连接是在条件检查之前计算的。因此,如果您有条件地调用您的日志记录框架 10K 次并且所有这些都评估为 false,那么您将无缘无故地连接 10K 次。
同时勾选 this topic。并查看 Icaro 的回答评论。
也去StringBuilder看看。
字符串连接方式 LOGGER.info("The program started at " + 新日期());
内置记录器格式
LOGGER.info("The program started at {}", 新日期());
非常好的文章,了解其中的区别 http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument
考虑以下日志记录语句:
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
这是什么'debug'?
这是日志语句的级别,不是LOGGER的级别。 看,有 2 个级别:
a) 日志语句之一(此处为调试):
"Comparing objects: " + object1 + " and " + object2
b) 一个是LOGGER的级别。那么,LOGGER 对象的级别是多少: 这也必须在代码或某些 xml 中定义,否则它需要从它的祖先 .
获得水平现在我为什么要讲这些?
现在,当且仅当:
Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code
Level 的可能值可以是
DEBUG < INFO < WARN < ERROR
(根据日志框架可以有更多)
现在回到问题:
"Comparing objects: " + object1 + " and " + object2
将始终导致创建字符串,即使我们发现上面解释的 'level rule' 失败。
然而,
LOGGER.debug("Comparing objects: {} and {}",object1, object2);
只有在'level rule explained above'满足时才会形成字符串。
那么哪个更聪明?
参考这个url。
先了解问题,再说解决方案。
我们可以简单点,假设下面的例子
LOGGER.debug("User name is " + userName + " and his email is " + email );
上面的日志消息字符串由4部分组成
并且 将需要构造 3 个字符串连接 。
现在,让我们来看看这个日志语句的问题是什么。
假设我们的日志级别是OFF
,这意味着我们现在对日志不感兴趣。
我们可以想象 String concatenations(缓慢的操作)将 ALWAYS 应用并且不会考虑日志记录级别。
哇,了解了性能问题,我们来谈谈最佳实践。
解决方案 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());
方案二(最优)
在检查调试级别之前,我们不需要构造日志消息。
所以我们可以将logging message format和all parts作为参数传递给LOGGING引擎,然后委托String concatenations 操作,并且 根据日志记录级别 ,引擎将决定是否连接。
所以,建议使用参数化日志记录,如下例
LOGGER.debug("User name is {} and his email is {}", userName, email);