SLF4J是一个非常流行的日志记录外观,但是,就像我们使用的所有库一样,我们有可能以错误的方式或至少以非最佳方式使用它。

在本教程中,我们将列出常见的日志记录错误以及如何使用FindBugs检测到它们。 我们还将在相关时提及PMD和Sonar Squid检查。

我们将使用两个外部的FindBugs插件,它们将日志检测器添加到FindBugs。

第一个是Kengo Toda的SLF4J only插件 ,其中仅包含SLF4J检测器。

第二个插件是流行的FB Contrib ,它包含许多测井仪。

有关如何使用FindBugs插件的信息,请参阅以下文章:

  • [Maven]( https://gualtierotesta.wordpress.com/2015/06/14/tutorial-using-findbugs-with-maven/ )
  • [NetBeans]( https://gualtierotesta.wordpress.com/2015/06/07/findbugs-plugins/ )

注意:在所有示例中,我们将假定以下导入:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

1.记录仪定义

错误的方法:

W1a. Logger log = LoggerFactory.getLogger(MyClass.class);
W1b. private Logger logger = LoggerFactory.getLogger(MyClass.class);
W1c. static Logger LOGGER = LoggerFactory.getLogger(AnotherClass.class);

正确方法:

C1a. private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
C1b. private final Logger logger = LoggerFactory.getLogger(getClass());

一般规则 :记录器应该是最终的且私有的,因为没有理由与其他类共享或重新分配它。

相反,对于记录器是否应该是静态的,没有普遍的协议。 SLF4J插件支持非静态版本(C1b),而PMD(“ LoggerIsNotStaticFinal”规则)和Sonar(Squid规则S1312)更喜欢静态记录器(C1a),因此这两个选项均应视为有效。

附加信息:

  • [SLF4J常见问题解答]( http://slf4j.org/faq.html#declared_static )
  • [Apache Commons静态日志]( http://wiki.apache.org/commons/Logging/StaticLog )。

请注意:

  • 在静态版本(C1a)中,记录器名称通常使用大写字母作为所有常量字段。 否则,PMD将报告“ VariableNamingConventions”违规行为。
  • 在这两种情况下,建议的名称都是“ logger / LOGGER”而不是“ log / LOG”,因为某些命名约定会避免使用太短的名称(少于四个字符)。 而且log是动词,更适合于方法名。
  • W1c是错误的,因为我们指的是一个类(AnotherClass),它不是定义记录器的类。 在99%的情况下,这是由于从一类到另一类的复制粘贴所致。

相关的FindBugs(SLF4J插件)检查:

  • SLF4J_LOGGER_SHOULD_BE_PRIVATE
  • SLF4J_LOGGER_SHOULD_BE_NON_STATIC
  • SLF4J_LOGGER_SHOULD_BE_FINAL
  • SLF4J_ILLEGAL_PASSED_CLASS

2.格式字符串

错误的方法:

W2a. LOGGER.info("Obj=" + myObj);
W2b. LOGGER.info(String.format(“Obj=%s”, myObj));

正确方法:

C2. LOGGER.info("Obj={}",myObj);

一般规则 :格式字符串(第一个参数)应为常量,没有任何字符串串联。 动态内容(示例中的myObj值)应使用占位符('{}')添加。

动机很简单:无论是否记录消息,我们都应在记录器建立后延迟记录消息的创建,具体取决于当前的记录级别。 如果我们使用字符串连接,则无论采用哪种日志记录级别,都会以任何方式构建消息,这会浪费CPU和内存资源。

相关的FindBugs(SLF4J插件)检查:

  • SLF4J_FORMAT_SHOULD_BE_CONST格式应为常数
  • SLF4J_SIGN_ONLY_FORMAT格式字符串不应仅包含占位符

相关的FindBugs(FB Contrib插件)检查:

  • LO_APPENDED_STRING_IN_FORMAT_STRING方法将串联的字符串传递给SLF4J的格式字符串

3.占位符参数

错误的方法:

W3a. LOGGER.info("Obj={}",myObj.getSomeBigField());
W3b. LOGGER.info("Obj={}",myObj.toString());
W3c. LOGGER.info("Obj={}",myObj, anotherObj);
W3d. LOGGER.info("Obj={} another={}",myObj);

正确方法:

C3a. LOGGER.info("Obj={}",myObj);
C3b. LOGGER.info("Obj={}",myObj.log());

一般规则 :占位符应该是对象(C3a),而不是方法返回值(W3a),以便在记录级别分析后推迟对其求值(请参见上一段)。 在W3a示例中,无论日志记录级别如何,都会始终调用方法getSomeBigField()。 出于相同的原因,我们应该避免在语义上与C3a等效的W3b,但是它总是在toString()方法调用中产生。

解决方案W3c和W3d错误,因为格式字符串中占位符的数量与占位符参数的数量不匹配。

解决方案C3b可能在某种程度上具有误导性,因为它包括方法调用,但只要myObj包含多个字段(例如,它是一个大型JPA实体),但我们不想记录其所有内容时,它就很有用。

例如,让我们考虑以下类:

public class Person {
private String id;
private String name;
private String fullName;
private Date birthDate;
private Object address;
private Map<String, String> attributes;
private List phoneNumbers;

它的toString()方法很可能会包含所有字段。 使用解决方案C3a,所有它们的值都将打印在日志文件中。

如果不需要所有这些数据,则定义如下的帮助方法将很有用:

public String log() {
return String.format("Person: id=%s name=%s", this.id, this.name);
}

仅打印相关信息。 此解决方案的CPU和内存也比toString()轻。

有什么关系? 它取决于应用程序和对象类型。 对于JPA实体,我通常在log()方法中包括ID字段(如果需要所有列数据,以便让我在数据库中找到记录),并且可能是一两个重要字段。

毫无疑问,应该记录密码字段和/或敏感信息(电话号码,…)。 这是不使用toString()登录的另一个原因。

相关的FindBugs(SLF4J插件)检查:

  • SLF4J_PLACE_HOLDER_MISMATCH

4.调试消息

重要提示:规则4(请参阅5条规则文章 )指导我们使用受保护的调试日志记录

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“Obj={}”, myObj);
}

使用SLF4J,如果占位符参数是对象引用(请参阅解决方案C3a / C3b),则可以使用if来避免代码混乱。

因此,使用以下内容是安全的:

LOGGER.debug(“Obj={}”, myObj);

5.例外

适当的异常日志记录是问题分析的重要支持,但很容易忽略它的用处。

错误的方法:

W5a. catch (SomeException ex) { LOGGER.error(ex);}..
W5b. catch (SomeException ex) { LOGGER.error("Error:" + ex.getMessage());}..

正确方法:

C5. catch (SomeException ex) { LOGGER.error("Read operation failed: id={}", idRecord, ex);}..`

一般规则 :

  1. 不要使用getMessage()(请参阅W5b)而不是完整的异常来删除堆栈跟踪信息。 堆栈跟踪通常包括问题的真正原因,很容易是底层代码引发的另一个异常。 仅记录消息将阻止我们发现问题的真正原因。
  2. 确实在日志消息中显示了重要的信息(供将要分析日志文件的人员使用),该信息显示了一个文本,解释了在引发异常(不是异常类型或诸如“错误”之类的消息)时我们想要执行的操作:我们已经知道有些不良发生了)。 我们需要知道的是我们在做什么以及在哪些数据上。

C5示例告诉我们,我们正在尝试读取具有特定ID的记录,该ID的值已与消息一起写入日志。

请注意,C5在格式字符串中使用一个占位符,但是有两个附加参数。 这不是错误,而是一种特殊的模式,SLF4J将其识别为异常记录案例:最后一个参数(在C5示例中为ex)被SLF4J视为Throwable(异常),因此不应将其包含在格式字符串中。

相关的FindBugs(SLF4J插件)检查:

  • SLF4J_MANUALLY_PROVIDED_MESSAGE:消息不应基于异常getMessage()

翻译自: https://www.javacodegeeks.com/2016/02/tutorial-correct-slf4j-logging-usage-check.html