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

2022-09-01 06:54:46

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

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

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

此问题的描述是

“前提条件”和日志记录参数不应要求求值 (squid:S2629)

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

同样,将串联字符串传递到日志记录方法中也可能导致不必要的性能损失,因为每次调用该方法时都会执行串联,无论日志级别是否足够低以显示消息。

相反,您应该构建代码以将静态或预计算值传递到前提条件条件检查和日志记录调用中。

具体而言,应使用内置字符串格式而不是字符串串联,如果消息是方法调用的结果,则应跳过前提条件,而应有条件地引发相关异常。

不合规代码示例

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正在抱怨它。


答案 1

我相信你有你的答案。

串联是在条件检查之前计算的。因此,如果您有条件地调用日志记录框架 10K 次,并且所有时间的计算结果都为 false,那么您将毫无理由地连接 10K 次。

另请查看此主题。并检查Icaro的答案的评论。

看看StringBuilder


答案 2

字符串串联表示 LOGGER.info(“程序开始于 ” + new Date());

内置的记录器格式意味着
LOGGER.info(“程序在{}”开始,新的Date());

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