Log4j 2 API

消息

虽然 Log4j 2 提供了接受字符串和对象的记录器方法,但所有这些最终都会被捕获在消息对象中,然后与日志事件关联。应用程序可以自由地构建自己的消息并将其传递给记录器。虽然这可能看起来比直接将消息格式和参数传递给事件更昂贵,但测试表明,使用现代 JVM,创建和销毁事件的成本很小,尤其是在将复杂的任务封装在消息而不是应用程序中时。此外,当使用接受字符串和参数的方法时,只有在任何配置的全局过滤器或记录器的日志级别允许处理消息时,才会创建底层消息对象。

考虑一个应用程序,它有一个包含 {"Name" = "John Doe", "Address" = "123 Main St.", "Phone" = "(999) 555-1212"} 的 Map 对象,以及一个具有 getId 方法(返回 "jdoe")的用户对象。开发人员希望添加一条信息消息,该消息返回 "用户 John Doe 使用 ID jdoe 登录"。实现此目的的一种方法是

logger.info("User {} has logged in using id {}", map.get("Name"), user.getId());

虽然这种方法本身没有错,但随着对象复杂性和所需输出的增加,这种方法变得越来越难用。作为替代方案,使用消息允许

logger.info(new LoggedInMessage(map, user));

在这个替代方案中,格式化被委托给 LoggedInMessage 对象的 getFormattedMessage 方法。虽然在这个替代方案中创建了一个新对象,但只有在格式化 LoggedInMessage 时才会调用传递给 LoggedInMessage 的对象的任何方法。当对象的 toString 方法没有生成您希望出现在日志中的信息时,这尤其有用。

消息的另一个优点是它们简化了布局的编写。在其他日志框架中,布局必须单独循环遍历参数,并根据遇到的对象确定要执行的操作。使用消息,布局可以选择将格式化委托给消息,或者根据遇到的消息类型执行其格式化。

借鉴前面说明使用标记来标识正在记录的 SQL 语句的示例,消息也可以利用。首先,定义消息。

public class SQLMessage implements Message {
  public enum SQLType {
      UPDATE,
      QUERY
  };

  private final SQLType type;
  private final String table;
  private final Map<String, String> cols;

  public SQLMessage(SQLType type, String table) {
      this(type, table, null);
  }

  public SQLMessage(SQLType type, String table, Map<String, String> cols) {
      this.type = type;
      this.table = table;
      this.cols = cols;
  }

  public String getFormattedMessage() {
      switch (type) {
          case UPDATE:
            return createUpdateString();
            break;
          case QUERY:
            return createQueryString();
            break;
          default;
      }
  }

  public String getMessageFormat() {
      return type + " " + table;
  }

  public Object getParameters() {
      return cols;
  }

  private String createUpdateString() {
  }

  private String createQueryString() {
  }

  private String formatCols(Map<String, String> cols) {
      StringBuilder sb = new StringBuilder();
      boolean first = true;
      for (Map.Entry<String, String> entry : cols.entrySet()) {
          if (!first) {
              sb.append(", ");
          }
          sb.append(entry.getKey()).append("=").append(entry.getValue());
          first = false;
      }
      return sb.toString();
  }
}

接下来,我们可以在应用程序中使用该消息。

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.util.Map;

public class MyApp {

    private Logger logger = LogManager.getLogger(MyApp.class.getName());
    private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");
    private static final Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE", SQL_MARKER);
    private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY", SQL_MARKER);

    public String doQuery(String table) {
        logger.entry(table);

        logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table));

        return logger.exit();
    }

    public String doUpdate(String table, Map<String, String> params) {
        logger.entry(params);

        logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, params);

        return logger.exit();
    }
}

请注意,与该示例的先前版本相比,doUpdate 中的 logger.debug 不再需要包装在 isDebugEnabled 调用中,因为创建 SQLMessage 的顺序与执行该检查的顺序相同。此外,所有 SQL 列的格式化现在都隐藏在 SQLMessage 中,而不是必须在业务逻辑中进行。最后,如果需要,可以编写过滤器和/或布局,以便在遇到 SQLMessage 时采取特殊操作。

FormattedMessage

FormattedMessage 是一个支持多个模式格式化程序的消息

不支持混合来自多个格式化程序的指定符。没有指定符的模式将使用上述任何格式化程序。

LocalizedMessage

LocalizedMessage 主要用于提供与 Log4j 1.x 的兼容性。通常,本地化的最佳方法是让客户端 UI 以客户端的语言环境呈现事件。

LocalizedMessage 包含一个 ResourceBundle,并允许消息模式参数成为捆绑包中消息模式的键。如果未指定捆绑包,LocalizedMessage 将尝试找到一个名为用于记录事件的记录器的捆绑包。从捆绑包中检索到的消息将使用 FormattedMessage 进行格式化。

LoggerNameAwareMessage

LoggerNameAwareMessage 是一个具有 setLoggerName 方法的接口。此方法将在事件构造期间被调用,以便消息在格式化消息时具有用于记录事件的记录器的名称。

MapMessage

MapMessage 包含一个包含字符串键和值的 Map。MapMessage 实现 FormattedMessage 并接受 "XML"、"JSON" 或 "JAVA" 的格式指定符,在这种情况下,Map 将被格式化为 XML、JSON 或如 java.util.AbstractMap.toString() 中所述。否则,Map 将被格式化为 "key1=value1 key2=value2 ..."

一些附加器对 MapMessage 对象进行了特殊使用

  • JMS 附加器 使用 MessageLayout 配置时,它会将 Log4j MapMessage 转换为 JMS javax.jms.MapMessage
  • JDBC 附加器 使用 MessageLayout 配置时,它会将 Log4j MapMessage 转换为 SQL INSERT 语句中的值。
  • MongoDB3 附加器MongoDB4 附加器 使用 MessageLayout 配置时,它会将 Log4j MapMessage 转换为 MongoDB 对象中的字段。

当附加器是 MessageLayout 意识时,Log4j 发送到目标的对象不是 Log4j 日志事件,而是自定义对象。

MessageFormatMessage

MessageFormatMessage 处理使用 转换格式 的消息。虽然此消息比 ParameterizedMessage 具有更大的灵活性,但它也大约慢两倍。

MultiformatMessage

MultiformatMessage 将具有 getFormats 方法和 getFormattedMessage 方法,这些方法接受并数组格式字符串。getFormats 方法可以被布局调用以向其提供有关消息支持的格式选项的信息。然后,布局可以使用一个或多个格式调用 getFormattedMessage。如果消息不识别格式名称,它将简单地使用其默认格式格式化数据。一个例子是 StructuredDataMessage,它接受 "XML" 的格式字符串,这将导致它将事件数据格式化为 XML 而不是 RFC 5424 格式。

ObjectMessage

通过调用对象的 toString 方法来格式化对象。从 Log4j 2.6 开始,尝试保持低垃圾或无垃圾的布局将调用 formatTo(StringBuilder) 方法而不是此方法。

ParameterizedMessage

ParameterizedMessage 处理在格式中包含 "{}" 以表示可替换令牌和替换参数的消息。

ReusableObjectMessage

在无垃圾模式下,此消息用于将记录的对象传递给布局和附加器。在功能上等效于 ObjectMessage

ReusableParameterizedMessage

在无垃圾模式下,此消息用于处理在格式中包含 "{}" 以表示可替换令牌和替换参数的消息。在功能上等效于 ParameterizedMessage

ReusableSimpleMessage

在无垃圾模式下,此消息用于将记录的字符串和 CharSequence 传递给布局和附加器。在功能上等效于 SimpleMessage

SimpleMessage

SimpleMessage 包含一个不需要格式化的 String 或 CharSequence。

StringFormattedMessage

StringFormattedMessage 处理使用与 java.lang.String.format() 兼容的 转换格式 的消息。虽然此 Message 比 ParameterizedMessage 更灵活,但它也慢 5 到 10 倍。

StructuredDataMessage

StructuredDataMessage 允许应用程序向 Map 添加项,并设置 id 以便根据 RFC 5424 将消息格式化为结构化数据元素。

ThreadDumpMessage

如果记录 ThreadDumpMessage,它将为所有线程生成堆栈跟踪。堆栈跟踪将包括任何持有的锁。

TimestampMessage

TimestampMessage 将提供一个在事件构建期间调用的 getTimestamp 方法。消息中的时间戳将用于代替当前时间戳。