logback–进阶–02–Logger,Appenders 和 Layouts
代码位置
https://gitee.com/DanShenGuiZu/learnDemo/tree/master/logback-learn
1、介绍
- Logback依赖于三个主要类:Logger,Appender 和 Layout。
- Logger 类是 logback-classic 模块的一部分
- Appender和Layout接口是 logback-core 模块的一部分。但是logback-core没有Logger记录器的概念。
2、Logger
- 在 logback-classic 中,Logger 是有继承关系的。
- 每个单独的 logger 都会关联到一个 LoggerContext,LoggerContext 负责制造 logger, 并将它们按树状结构排列。
- logger 记录器是带有名称的 Logger 对象,它们的名称区分大小写,并且遵循层级命名规则。
2.1、Logger 名称层次规则
- 记录器的名称层级规则跟 “.” 有关,它们有父亲或者祖先的关系。
- 例如:
- 命名为 com.nobody 的 logger 是命名为 com.nobody.User 的 logger 的父亲
- java 是 java.util 的父亲,是 java.util.List 的祖先。
2.2、root logger
位于 Logger 层次结构的顶部。我们可以按其名称获取到它
public static void main(String[] args) {
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
rootLogger.info("rootLogger:{}", rootLogger.getName());
}
2.3、获取一个 Logger 对象
我们一般用 类的全限定名称作为 logger 名称,获取一个 Logger 对象,用于类中打印日志
public class Demo5 {
public static void main(String[] args) {
//用 类的全限定名称作为 logger 名称,获取一个 Logger 对象
Logger logger = LoggerFactory.getLogger(Demo5.class);
logger.info("Hello Logback!");
}
}
2.4、通过LoggerFactory.getLogger 获取相同名字的logger记录器,都是返回同一对象
public class Demo5 {
public static void main(String[] args) {
//用 类的全限定名称作为 logger 名称,获取一个 Logger 对象
Logger logger = LoggerFactory.getLogger(Demo5.class);
Logger logger2 = LoggerFactory.getLogger("fei.zhou.logbacklearn.business.demo.Demo5");
logger.info("logger :"+logger.hashCode()+"");
logger.info("logger2:"+logger2.hashCode()+"");
}
}
2.5、有效级别
- 有效级别也称为日志级别继承规则。
- 日志级别种类有TRACE,DEBUG,INFO,WARN 和 ERROR,它们在 ch.qos.logback.classic.Level中定义。
- 在 logback 中,Level 类是 final 的,不能被继承的。
- 级别按以下顺序排序:TRACE < DEBUG < INFO < WARN < ERROR。
- 如果没有为给定的 logger 记录器分配一个级别,那么它将从其最接近的祖先那里继承一个已分配的级别,比如一个 logger 的有效级别等于其层次结构中的第一个非空级别,它从其本身开始,在层次结构中向上扩展直到 root logger。
- 为了确保所有 logger 记录器最终都可以继承到级别,root logger 始终具有分配的级别。
- 默认情况下,此级别是 DEBUG。
- 打印方法决定记录请求的级别。
- 如果 L 是一个 logger 实例,则语句 L.info(“…”) 是一条级别为 INFO 的记录语句。
- 记录请求的级别只有高于或等于其 logger 的有效级别时被称为被启用,否则,称为被禁用。
- 假设记录请求级别为 p,其 logger 的有效级别为 q,只有则当 p>=q 时,该请求才会被执行。
- 如果 L 是一个 logger 实例,则语句 L.info(“…”) 是一条级别为 INFO 的记录语句。
2.6、有效级别测试
public class Demo5 {
public static void main(String[] args) {
// 获取一个名为 "com.foo" 的 logger 对象,并且转换为 ch.qos.logback.classic.Logger
// 这样我们能为它设置级别
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
//设置info级别
logger.setLevel(Level.INFO);
// 可以执行,因为 WARN >= INFO
logger.warn("可以执行,因为 WARN >= INFO");
// 不能执行,因为 DEBUG < INFO.
logger.debug("不能执行,因为 DEBUG < INFO");
// 获取一个名为 "com.boo.Bar" 的 logger 对象,没有设置级别,根据继承关系,继承"com.foo"的 logger的级别 INFO
Logger barLogger = LoggerFactory.getLogger("com.foo.Bar");
// 根据级别继承关系,可以执行,因为 INFO >= INFO.
barLogger.info("根据级别继承关系,可以执行,因为 INFO >= INFO.");
// 根据级别继承关系,不能执行,因为 DEBUG < INFO.
barLogger.debug("根据级别继承关系,不能执行,因为 DEBUG < INFO.");
}
}
3、Appenders
- appender 也叫做输出目标,将日志记录请求打印到目的地。
- 一个 logger 可以与多个 appender 绑定。
3.1、appender 种类
- 控制台
- 文件
- 远程 socket 服务
- MySQL
- PostgreSQL
- Oracle
- JMS
- 远程 UNIX Syslog 守护程序
3.2、日志输出流程
一个可以执行的日志打印请求,会将日志输出到当前 logger 关联的 appender,并且会根据层级关系输出到所有上层级 logger 所关联的 appender 中。
我们可以通过将 logger 的可叠加性标志additivity flag设置为 false,覆盖此默认行为,这样不会将日志输出到更高层级 logger 的 appender 中。
3.3、案例
假如有个 logger X.Y.Z,默认会将日志输出到 X.Y.Z,X.Y,X 这三个 logger 所关联的 appender 中。
1. 如果将 X.Y 这个 logger 的 additivity flag 设置为 false,则 X.Y.Z logger 打印的日志只会输出到 X.Y.Z 和 X.Y。
2. 如果将 X.Y.Z logger 的 additivity flag 设置为 false,则 X.Y.Z logger 打印的日志只会输出到 X.Y.Z 。
3、Layouts
- 用于自定义日志输出格式
- 通过将 layout 和 appender 相关联来实现
- layout 负责根据用户的需求格式化日志记录请求
- appender 负责将格式化后的日志输出发送到目的地。
3.1、案例
如果 patternLayout 配置为 “%-4relative [%thread] %-5level %logger{32} - %msg%n”,将输出类似以下格式日志信息:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
- 第1个字段:自程序启动以来经过的毫秒数。
- 第2个字段:发出日志请求的线程名称。
- 第3个字段:日志请求的级别。
- 第4个字段:与日志请求关联的 logger 的名称。
- “-” 之后:日志信息。
3.2、参数化的日志
有些日志打印方法允许使用多个参数。这些带有多个参数的打印方法能提高性能,同时最大程度提高代码可读性。
例如,以下写法,为了构造 debug 方法的 msg 参数,会将整数 i 和 entry[i] 字符串都转换为字符串,并连接中间字符串,从而产生开销。不管最终此行代码是否会打印。
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
为避免参数构造成本的一种方法,是在打印日志前,判断此 logger 是否开启此级别。
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
这样,如果我们禁用了 debug 级别,则不会产生 msg 参数构造的开销。但是,如果 logger 开启了 debug 级别,则将产生两次开销,一次是判断 debugEnabled,一次是 debug 打印。实际上,debugEnabled 这种开销微不足道,因为它是实际日志打印请求所花费时间的不到1%。
不过有更好的选择,基于消息格式的方法,例如如下所示:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有在 debug 打印语句是开启的情况下,logger 才会格式化消息,并用 entry 代替 {}。也就是说,当禁用 debug 级别时,是不会产生 msg 参数构造的开销的。
以下两行将产生完全相同的输出。但是,在禁用日志记录语句的情况下,第二个将比第一个好至少30倍。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
如果需要传递三个或更多参数,您可以这样写:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);