Log4j 2 API
Message
虽然Log4j 2提供了Logger的方法来接收String和Object,但它们最终都将在Message对象中被捕获并与日志事件相关联。应用程序可以自由定义它们自己的Message并传递给Logger。尽管这可能看上去比将消息格式和参数传递给事件要成本更高,但测试显示在现代的JVM中,创建和销毁事件的成本很低,特别是在Message中概括复杂的任务,而不是在应用中。另外,当使用接受String和Object的方法时,所基于的Message对象只有在配置的全局Filter或Logger的日志级别允许消息被处理时才会被创建。
考虑应用程序有一个Map对象包含{"Name" = "John Doe", "Address" = "123 Main St.", "Phone" = "(999) 555-1212"},还有一个User对象含有一个返回“jdoe”的getId方法。开发者希望添加一条信息化的消息来返回“User John Doe has logged in using id jdoe”。做法是:
logger.info("User {} has logged in using id {}", map.get("Name"), user.getId());
虽然这样做没有什么错,但随着对象的复杂程度提高,输出会变得越来越困难。作为替代,可以使用Message:
logger.info(new LoggedInMessage(map, user));
在这种替代做法中,格式化被委托给LoggedInMessage对象的getFormattedMessage方法。尽管在这里创建了一个新对象,但在被进行格式化之前不会调用LoggedInMessage中的任何方法。这特别适用于当对象的toString方法不能提供你希望在日志中展示的信息的场景。
Message的另一个优点是它简化了写Layout。在其他的日志框架中,Layout必须通过单独的参数循环,并且明确在遇到的对象上面要做什么。而通过Message,Layout能够可选地将格式化委托给Message,或者基于对应的Message自己做格式化。
借鉴此前说明Marker来记录SQL语句的例子,Message也可以起作用。首先,定义Message:
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();
}
}
下面我们可以在应用中使用Message:
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(param);
logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table));
return logger.exit();
}
public String doUpdate(String table, Map<String, String> params) {
logger.entry(param);
logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, parmas);
return logger.exit();
}
}
注意在这个例子中,与此前的版本相比,doUpdate中的logger.debug不再需要被包装在isDebugEnabled判断中,因为SQLMessage的创建与该检查动作是同等成本的。而且,所有SQL列的格式化现在都被隐藏到SQLMessage里,而不是出现在业务逻辑中。最后,如果需要,可以在遇到SQLMessage时使用Filter和/或Layout执行特殊动作。
FormattedMessage
传递给FormattedMessage的消息模式会被首先检查是否为一个正确的java.text.MessageFormat模式。如果是,会用一个MessageFormatMessage来格式化。如果不是,会继而检查是否包含合法的String.format()规范模式。如果有,会用一个StringFormattedMessage来格式化。最后,如果这些都不匹配,就用一个ParameterizedMessage来格式化。
LocalizedMessage
LocalizedMessage主要提供与Log4j 1.x的兼容性。一般来讲,最好的本地化是让客户端UI将事件交给客户端本地。
LocalizedMessage合并了一个ResourceBundle并且允许用消息模式参数作为消息模式在bundle中的key。如果没有声明bundle,LocalizedMessage会尝试用记录事件的Logger名称来定位一个bundle。从bundle中获取的消息会用FormattedMessage进行格式化。
LoggerNameAwareMessage
LoggerNameAwareMessage是一个带有setLoggerName方法的接口。该方法将在事件构造期间被调用,这样Message就会在被格式化时持有用来记录事件的Logger名称。
MapMessage
一个MapMessage包含一个String的key和value的Map。MapMessage实现了FormattedMessage并接受一个“XML”的格式声明,以便让Map作为XML被格式化。否则,Map将会以“key1=value1 key2=value2...”的方式被格式化。
MessageFormatMessage
MessageFormatMessage接管使用转换格式的消息。尽管这个Message比ParameterizedMessage更加灵活,但它也同样更慢两倍。
MultiformatMessage
一个MultiformatMessage会有一个getFormats方法和一个接收格式字符串数组的getFormattedMessage方法。Layout会调用getFormats方法来得知该Message支持什么样子的格式。然后Layout可以通过一种或多种格式来调用getFormattedMessage方法。如果Message没有识别格式名称,则会用简单的默认格式来格式化数据。StructuredDataMessage就是个例子,它接收一个“XML”的格式字符串来让它将事件数据作为XML格式化,而替换掉RFC 5424格式。
ObjectMessage
调用toString方法来格式化对象。从Log4j 2.6开始,Layout尝试在调用formatTo(StringBuilder)
方法时成为低垃圾或无垃圾的。
ParameterizedMessage
ParameterizedMessage处理格式中包含“{}”的消息,作为一个可替换的占位符,并接收用来替换的参数。
ReusableObjectMessage
在无垃圾模式中,这个Message用来将日志记录的对象传递给Layout和Appender。功能上与ObjectMessage相同。
ReusableParameterizedMessage
在无垃圾模式中,这个Message用来处理格式中包含“{}”的消息,作为一个可替换的占位符,并接收用来替换的参数。功能上与ParameterizedMessage相同。
ReusableSimpleMessage
在无垃圾模式中,这个Message用来将记录的String和CharSequence传递给Layout和Appender。功能上与SimpleMessage相同。
SimpleMessage
SimpleMessage包含一个无需格式化的String或CharSequence。
StringFormattedMessage
StringFormattedMessage处理包含适用于java.lang.String.format()
的转换格式。尽管它比ParameterizedMessage要更灵活,但却要更慢5到10倍。
StructuredDataMessage
StructuredDataMessage允许应用程序将信息添加到Map中并设置一个ID以让消息作为结构化数据进行格式化,依照RFC 5424。
ThreadDumpMessage
一个ThreadDumpMessage,如果记录的话,会为所有线程生成栈追踪信息。如果运行在Java 6以上的环境,站信息还将包含持有的所有锁。
TimestampMessage
一个TimestampMessage会提供在事件构造期间被调用的getTimestamp方法。Message中的时间戳会被作为当前时间戳而替换。