前言
我们开发项目过程中,日志是必不可少的,但是我们一直在使用,却很少考虑其原理,本文就是填补此项空白。
目录
介绍
日志框架通常采用门面模式来设计,也就是说开发在使用时直接使用api,而不关心具体实现细节,在java项目中,主要有以下对应产品:
门面 | 实现 |
---|---|
jcl/SLF4j/jboss-logging | Log4j JUL(java.util.logging) Log4j2 Logback |
本文主要介绍slf4j+logback这个主流的日志搭档。
使用
slf4j-api
上文我们说到slf4j-api是门面,也就是它不是具体的实现,我们来测试下看看是否如此。
创建空工程,maven添加依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
编写代码
static final Logger logger = LoggerFactory.getLogger(Demo.class);
public static void main(String[] args) {
logger.info("测试");
}
我们会发现控制台报错:
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#noProviders for further details.
这个就是因为日志工程无法找到实现。下面我们先通过添加一个简单的实现(slf4j-simple)来查看运行情况。
slf4j-simple
maven依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
代码保持不变,我们可以发现控制台输出变了:
[main] INFO com.zlqian.slf4j.demo.Demo - 测试
我们打开slf4j-simple的jar包,可以发现META-INF/services下面有个文件“org.slf4j.spi.SLF4JServiceProvider”。这就是基于java的spi技术提供的服务实现。
这里不做spi技术的具体介绍。
多个日志实现服务
我们在上面的测试中已经添加了一种实现:slf4j-simple,我们继续添加logback实现看看会咋样:
添加maven依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.0-alpha5</version>
</dependency>
发现控制台输出:
SLF4J: Class path contains multiple SLF4J providers.
SLF4J: Found provider [org.slf4j.simple.SimpleServiceProvider@72ea2f77]
SLF4J: Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@33c7353a]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual provider is of type [org.slf4j.simple.SimpleServiceProvider@72ea2f77]
[main] INFO com.zlqian.slf4j.demo.Demo - 测试
这意思是什么呢?就是说classPath中发现多个日志实现服务(基于spi机制发现),取第一个日志实现服务。
logback
不进行配置
一般日志都是通过配置,输出到文件,如果我们不进行配置直接运行,我们看下输出:
10:37:27.100 [main] INFO com.zlqian.slf4j.demo.Demo - 测试
我们发现能够在控制台输出,这个是什么原因呢?logback有一个状态管理器StatusManager,我们通过它来查看当前的logback的状态。
编写代码:
logger.info("测试");
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
StatusPrinter.print(loggerContext);
查看控制台输出:
10:44:55.085 [main] INFO com.zlqian.slf4j.demo.Demo - 测试
10:44:55,047 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
10:44:55,047 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
10:44:55,047 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
10:44:55,049 |-INFO in ch.qos.logback.classic.BasicConfigurator@306a30c7 - Setting up default configuration.
看到这里我们知道原因了,由于没有配置,所以默认采用ConsoleAppender进行日志打印。
logback 架构
logback有三个核心模块logback-core logback-classic logback-access.
core是logbacck的基础,classic继承core实现了slf4j-api。access是用于http容器中的,这里暂时不讨论access。
核心类
logback中3个核心类,这3个类协作完成日志的格式化输出。
类 | 所属模块 |
---|---|
Logger | classic |
Appender | core |
Loyout | core |
Logger
获取Logger
通过LoggerFactory.getLogger(name)获取Logger,如果name一样获取到的是同一个引用;
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
x 和 y 2者是相同的Logger。
LoggerContext
用于生成Logger以及将它们按照树形结构组织。logger之间按照名称存在着父子关系。
例如名称为com.zlqian的Logger 是 com.zlqian.test的Logger的父关系。
这关系有啥用呢?
Logger是设置日志level,如果一个Logger没有设置level,就会依次向父Logger继承。
最上层的Logger名称:org.slf4j.Logger.ROOT_LOGGER_NAME。
Appender
logback将日志输出到多个源,每个输出源就是一个Appender。logback支持很多中Appender。
根据官网文档: console, files, remote socket servers, to MySQL, PostgreSQL, Oracle and other databases, JMS, and remote UNIX Syslog daemons.
如果将需要打印的信息交给Logger,那么该Logger中的Appender都将会输出,并且Logger的父Logger中的Appenders也都输出。
不过这点可以进行关闭,设置 additivity flag 为 false。
Layout
logback允许用户设置其日志输出格式,例如将日志格式设置为:"%-4relative [%thread] %-5level %logger{32} - %msg%n",那么其日志输出就如下:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
流程
过滤器
从流程图中我们可以看到在使用Logger打印的时候,首先会从LoggerContext中获取TurboFilter。
实际上logback中有2类过滤器: Filter和TurboFilter,后者就是上图中的过滤器。
它们的区别在于:
作用时机 | 配置 | |
---|---|---|
TurboFilter | 在创建LoggingEvent前 | 全局配置 |
Filter | 在创建LoggingEvent后 | 在Appender中配置 |
由于以上区别,因此TurboFilter的性能更加高,而Filter配置更加灵活,不同的Appender可以配置不同的FIlter。
Layout
从流程图中,我们发现Appender会通过doLayout方法进行日志格式化。Layout负责将日志事件抓成string,如果想要自定义一个Layout,需要继承ch.qos.logback.core.LayoutBase,实现将ILoggingEvent转换成String。
MDC
目前server端多通过多线程来提高并发处理,logback为了提高多线程环境下,输出的日志更便于诊断,提供了Mapped Diagnostic Context技术,其核心原理就是通过ThreadLocal技术,请求过来时,通过在线程中设置请求的标识,在Appender输出时会根据MDC中的标识设置进行输出。
参考
http://logback.qos.ch/manual/introduction.html