第二章 Logback的架构(二)
Logger, Appenders 和 Layouts
Appenders 和 Layouts
基于日志记录器选择性地启用或禁用日志记录请求只是其中的一部分功能。Logback允许将日志记录请求输出到多个目标。在Logback术语中,输出目标被称为Appender。
目前,已经存在适用于控制台、文件、远程套接字服务器、MySQL、PostgreSQL、Oracle及其他数据库、JMS以及远程Unix系统日志守护进程的Appender。
一个Logger可以添加多个appender。
addAppender
方法可以将Appender添加到给定的Logger中。每个启用的日志请求都会被转发到给定的Logger的Appender中,以及Logger层次结构的更高级别的Appender中。
换句话说,Appender是通过日志记录器层次结构累加继承的。比如,一个输出到控制台的Appender被添加到根Logger中,那么所有的启用的日志请求至少都会在控制台打印输出;
此外,如果一个输出到文件的Appender被添加到一个名为L的Logger中,那么所有L的启用日志请求以及L子级的启用日志请求都会输出到文件中并在控制台打印输出。
当然,我们可以通过设置Logger的additivity
标记为false
来取消这种默认的累计输出模式。
下面是控制Appender可加性的规则总结。
假如我们有一个叫L的Logger,L的日志输出会被发送到L以及其父级包含的所有Appender中。然而,如果L有一个父级Appender P,P的`additivity`标记为`false`,那么L的日志输出
会定向输出到L以及其到父级P为止所包含的所有Appender中。Logger的`additivity`标记默认都是true。
下表是一个示例:
Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |
---|---|---|---|---|
root | A1 | not applicable | A1 | Since the root logger stands at the top of the logger hierarchy, the additivity flag does not apply to it. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of “x” and of root. |
x.y | none | true | A1, A-x1, A-x2 | Appenders of “x” and of root. |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders of “x.y.z”, “x” and of root. |
security | A-sec | false | A-sec | No appender accumulation since the additivity flag is set to false. Only appender A-sec will be used. |
security.access | none | true | A-sec | Only appenders of “security” because the additivity flag in “security” is set to false. |
很多时候,用户希望不仅仅是自定义日志的输出路径,同样希望自定义日志的输出格式。这可以通过配置Appender关联的Layout来完成。Layout负责根据用户的意愿格式化日志记录请求,
而Appender则负责将格式化的输出发送到其目的地。PatternLayout
是Logback中标准的Layout实现,它允许用户根据类似于C语言printf函数的转换模式指定输出格式。
比如,PatternLayout
设置转换参数为%-4relative [%thread] %-5level %logger{32} - %msg%n
时,会有以下输出:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一部分字段表示程序运行到现在所经过的毫秒数,第二个部分字段表示发出日志请求的线程,第三部分字段表示日志的级别,第四部分字段表示日志的输出位置,-
字符后是日志的消息内容。
参数化日志记录
在logback-classic中,日志记录器实现了SLF4J的日志记录接口,一些日志记录器方法允许接收多个参数。这些打印方法变体的主要目的是提高性能并最小化对代码可读性的影响。
比如一些日志打印这样书写:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
这样确实会有一些消息参数构建的成本,比如类型转换(数值和对象转字符串)以及字符串连接。无论我们是否真的需要输出这条信息,都需要消耗这个成本。
一个可能的解决方法是使用判断语句来避免参数构建的成本。以下是一个例子。
if(logger.isDebugEnabled()) {logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果日志没有启用DEBUG级别,那么参数构造的开销就没有了。从另一方面讲,启用DEBUG级别时,代码将会有两次判断日志是否是可用的,
第一次是在isDebugEnabled()
方法中,第二次是在debug()
方法中。实际上,这种开销并不重要,因为判断的时间不到实际日志请求时间的1%。
更好的替代方法
基于消息格式化存在一种更便捷的方式,假如entry
是一个对象:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有当日志被允许输出的时候,才会开始执行消息格式化,并使用entry
的字符串替换{}
。换句话说,在禁用日志语句时,这种形式不会产生参数构建的成本。
以下两行将产生完全相同的输出。但是,如果禁用日志记录语句,则第二种变体的表现至少比第一种变体好30倍。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
两个参数的同样可以,示例:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
如果需要传递三个或更多参数,还可以使用varargs(Object…)变体。例如,您可以编写:
logger.debug("Value {} was inserted between {} and {}.", newVal, below, above);
请注意,varargs变体需要创建Object[]实例的开销。