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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42388341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:35:29  来源:igfitidea点击:

Built-in string formatting vs string concatenation as logging parameter

javastringloggingconcatenationsonarlint

提问by Naxos84

I'm using SonarLintthat shows me an issue in the following line.

我正在使用SonarLint,它在以下行中向我显示了一个问题。

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

Side-note: The method that contains this line might get called quite often.

The description for this issue is

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

这个问题的描述是

"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()); }

“前提条件”和日志参数不应该需要评估 (squid: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

合规解决方案

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()); }

I'm not 100% sure whether i understand this right. So why is this really an issue. Especially the part about the performance hit when using string concatenation. Because I often read that string concatenation is faster than formatting it.

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

EDIT:Maybe someone can explain me the difference between

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

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

AND

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

is in the background. Because I think the String will get created before it is passed to the method. Right? So for me there is no difference. But obviously I'm wrong because SonarLint is complaining about it

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

采纳答案by luso

I believe you have your answer there.

我相信你有你的答案。

Concatenation is calculated beforehand the condition check. So if you call your logging framework 10K times conditionally and all of them evaluates to false, you will be concatenating 10K times with no reason.

连接是在条件检查之前计算的。因此,如果您有条件地调用日志框架 10K 次并且所有这些评估结果为 false,您将毫无理由地连接 10K 次。

Also check this topic. And check Icaro's answer's comments.

也检查这个话题。并查看 Icaro 的回答评论。

Take a look to StringBuildertoo.

也看看StringBuilder

回答by Number945

Consider the below logging statement :

考虑以下日志语句:

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

what is this 'debug' ?

这是什么“调试”?

This is the level of logging statement and not level of the LOGGER. See, there are 2 levels :

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

a) one of the logging statement (which is debug here) :

a) 日志语句之一(此处为调试):

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

b) One is level of the LOGGER. So, what is the level of LOGGER object : This also must be defined in the code or in some xml , else it takes level from it's ancestor .

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

Now why am I telling all this ?

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

Now the logging statement will be printed (or in more technical term send to its 'appender') if and only if :

现在,当且仅当以下情况时,将打印日志记录语句(或在更专业的术语中发送到其“附加程序”):

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

Possible values of a Level can be

Level 的可能值可以是

DEBUG < INFO <  WARN < ERROR

(There can be few more depending on logging framework)

(根据日志框架,可以有更多)

Now lets come back to question :

现在让我们回到问题:

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

will always lead to creation of string even if we find that 'level rule' explained above fails.

即使我们发现上面解释的“级别规则”失败,也将始终导致创建字符串。

However,

然而,

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

will only result in string formation if 'level rule explained above' satisfies.

如果“上面解释的级别规则”满足,则只会导致字符串形成。

So which is more smarter ?

那么哪个更聪明呢?

Consult this url.

咨询这个网址

回答by Shubham Pandey

String concatenation means LOGGER.info("The program started at " + new Date());

字符串连接的意思是LOGGER.info("程序开始于" + new Date());

Built in formatting of logger means
LOGGER.info("The program started at {}", new Date());

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

very good article to understand the difference http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument

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

回答by ahmednabil88

First let's understand the problem, then talk about solutions.

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

We can make it simple, assume the following example

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

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

The above logging message string consists of 4 parts
And will require 3 String concatenationsto be constructed.

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

Now, let's go to what is the issue of this logging statement.

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

Assume our logging level is OFF, which means that we don't interested in logging now.

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

We can imagine that the String concatenations(slow operation) will be ALWAYSapplied and will not consider the logging level.

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

Wow, after understanding the performance issue, let's talk about the best practice.

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

Solution 1 (NOT optimal)
Instead of using String concatenations, we can use String Builder

解决方案 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());

Solution 2 (optimal)
We don't need to construct the logging message before check the debugging level.
So we can pass logging message formatand all partsas parameters to the LOGGING engine, then delegate String concatenationsoperations to it, and according to the logging level, the engine will decide to concatenate or not.

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

So, It's recommended to use parameterized loggingas the following example

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

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