参考
背景
- logback来源于log4j,有着更好的性能与更好的速度,包含了很多独特的特性
- logback包含3个部分
- logback-core:核心部分,是否两个部分的基础
- logback-classic:log4j的改进优化
- logback-access:与tomcat等servlet服务器继承提供远程访问
logback基本使用
package chapters.introduction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld1 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
logger.debug("hello world");
}
}
需要引入的依赖包:classpath 添加 slf4j-api.jar、logback-core.jar 以及 logback-classic.jar。
- 根据前一节发展历程可知:slf4j-api对应于slf4j的门面,而logback-core与logback-classic则是用于做基本实现包
- LoggerFactory与Logger都是SLf4j中的类
Logback的系统运行状态
package chapters.introduction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
public class HelloWorld2 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2");
logger.debug("Hello world");
// 打印内部的状态
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
}
}
22:39:20.241 [main] DEBUG chapters.introduction.HelloWorld1 - hello world
22:39:20,179 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
22:39:20,179 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
22:39:20,179 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
22:39:20,194 |-INFO in ch.qos.logback.classic.BasicConfigurator@28c97a5 - Setting up default configuration.
- Logback通过一个状态系统来报告本身的状态信息,发生在logback生命周期中的事件可以通过StatusManager来获取
- 注意Loggercontext来源于包classical中
- StatusPrinter打印上下文
- 根据打印结果可以看出寻找配置的过程
- 默认配置会创建一个ConsoleAppender,可以根据情况输出到入文件,TCP以及console等各种日志目的地。用户可以根据自己的情况创建Appender。
Logback的架构
1.Logback主要分为三个类:logger、Apennder、layout。其中classic中才有logger,而core中包含后两者,且无logger的概念
LoggerContext
- 日志的优点相对于直接输入流在于它可以输出一些日志,又不输出一部分日志。存在一个日志空间用于管理其中的所有日志,所有logger都受这个空间所管理。
- 一个Logger被当做一个实体,命名敏感遵循一个树状规则。
- 日志空间基于类似于包名,com.zmm中的logger就是com.zmm.child的父级别。是com.zmm.child.son的祖先级别。注意此处的包名是大小写敏感的。
- root logger 作为 logger 层次结构的最高层。它是一个特殊的 logger,因为它是每一个层次结构的一部分。每一个 logger 都可以通过它的名字去获取。例:
- Logger接口中主要的方法有
package org.slf4j;
public interface Logger {
public void trace(String message);
public void debug(String message);
public void info(String message);
public void warn(String message);
public void error(String message);
}
Logger Level
- 日志分为不同的等级,这些等级定义在ch.qos.logback.classic.Level,是不能够继承的类
- 如果Logger没有指定一个层级,就会继承祖先的层级直到找到一个明确的祖先层级,root的默认层级为DEBUG
- Logger打印的方法需要大于等于当前Logger的等级。所以关键在于日志等级排序。各级别的排序为:TRACE < DEBUG < INFO < WARN < ERROR。
- 例子如下:
public static void main(String[] args) {
// ch.qos.logback.classic.Logger 可以设置日志的级别
// 获取一个名为 "com.foo" 的 logger 实例
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.foo");
// 设置 logger 的级别为 INFO
logger.setLevel(Level.INFO);
// 这条日志可以打印,因为 WARN >= INFO
logger.warn("警告信息");
// 这条日志不会打印,因为 DEBUG < INFO
logger.debug("调试信息");
// "com.foo.bar" 会继承 "com.foo" 的有效级别
Logger barLogger = LoggerFactory.getLogger("com.foo.bar");
// 这条日志会打印,因为 INFO >= INFO
barLogger.info("子级信息");
// 这条日志不会打印,因为 DEBUG < INFO
barLogger.debug("子级调试信息");
}
Logger对象获取
通过 LoggerFactory.getLogger() 可以获取到具体的 logger 实例,名字相同则返回的 logger 实例也相同。(工厂模式),这样生产的同样名称的logger对象是相同的。
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
x与y是相同的对象,并且即使后生成父对象,任然会生成上述的继承等级。
推荐使用但类的全限定名来对 logger 进行命名,是目前最好的方式,没有之一。方便看出打印日志所在的类
Appender与layout
- Appender:对于Logback框架来说输出目的地即为Appender,一个logger可以有多个Appender。logger可以调用addAppender()来增加Appender,对于每一个日志都会输出到所有Appender
- Appender:具有叠加性,如果 root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来。如果再给一个叫做 L 的 logger 添加了一个 file appender,那么 L 以及 L 的子级 logger 都可以在文件和控制台打印日志。
- Layout:日志输出格式,PatternLayout通过格式化输出日志类似于C语言的Printf函数。
日志打印性能
参数化日志
- 会有日志中参数拼接的损耗
- 推荐使用logger.isDebugEnabled()进行判断
- 日志格式化的方法:logger.debug(“asaf{}”,xx);这样子的方法能够在不打印日志的时候提高性能
底层设计
如下简述名为com.zmm的logger.info()的流程
- 过滤器责任链,通过每一条日志请求信息,进行过滤,如果过滤器返回的是Filter.DENY,那么这条日志就被丢弃,如果是FilterReply.neutral则会继续执行下一步。如果为ACCEPT则直接执行第3步
- 应用基本规则:比较应用有效级别与日志请求级别,如果符合则进入下一步,否则就丢弃日志
- 创建事件对象:LoggerEvent对象被创建,此对象存在此次日志打印的所有相关信息,包含当前时间,当前线程以及当前类的各种信息
- 输入日志:当事件创立完毕,Logback会调用该logger的上下文中的所有Appender,并且调用该Appender.doAppend()。其中append()方法是线程安全的。
- 格式化日志新消息:appender对象负责格式化日志,但是如果比较复杂的格式化会给layout对象进行格式化返回一个字符串,比如SocketAppender并不会把日志时间转变为一个字符串,而是进行序列化,所以不需要layout。
- 发送到指定介质。
UML流程如下