Log Java日志:(slf4j、log4j、logback、common-logging )
- slf4j 是规范/接口
- 日志实现:log4j、logback、common-logging
简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。
Logback 主要由三个模块组成:
- logback-core
- logback-classic
- logback-access
logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。
logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J。
logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与 HTTP 访问相关的功能。
根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:
- Logback:
logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
- Log4j:
log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
- Log4j2:
log4j2-spring.xml, log4j2.xml
- JDK (Java Util Logging):
logging.properties
Logback 与 Log4J
实际上,这两个日志框架都出自同一个开发者之手,Logback 相对于 Log4J 有更多的优点:
- 同样的代码路径,Logback 执行更快
- 更充分的测试
- 原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)
- 内容更丰富的文档
- 支持 XML 或者 Groovy 方式配置
- 配置文件自动热加载
- 从 IO 错误中优雅恢复
- 自动删除日志归档
- 自动压缩日志成为归档文件
- 支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件
- 支持配置文件中加入条件判断来适应不同的环境
- 更强大的过滤器
- 支持 SiftingAppender(可筛选 Appender)
- 异常栈信息带有包信息
添加依赖
<!-- slf4j + logback: -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 实现lsf4j接口并整合 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
添加配置
Automatic configuration with logback-test.xml or logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
这是和默认配置等效的一条配置。
使用logback
package chapters.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApp1 {
final static Logger logger = LoggerFactory.getLogger(MyApp1.class);
public static void main(String[] args) {
logger.info("Entering application.");
}
}
16:06:09.031 [main] INFO chapters.configuration.MyApp1 - Entering application.
注意到这里,代码里并没有引用任何一个跟 Logback 相关的类,而是引用了 SLF4J 相关的类,这就是使用 SLF4J 的好处,在需要将日志框架切换为其它日志框架时,无需改动已有的代码。
LoggerFactory
的 getLogger()
方法接收一个参数,以这个参数决定logger
的名字,这里传入了 MyApp1
这个类的 Class 实例,那么 logger 的名字便是 MyApp1
这个类的全限定类名:chapters.configuration.MyApp1
。
下面三种方法是等效的:
1. Logger logger = LoggerFactory.getLogger(MyApp1.class)
2. Logger logger = LoggerFactory.getLogger(MyApp1.class.getName())
3. Logger logger = LoggerFactory.getLogger("chapters.configuration.MyApp1")
让 Logback 打印出一些它自身的内部消息
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
日志打印级别
TRACE < DEBUG < INFO < WARN < ERROR
如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。
Logger,Appenders 与 Layouts
在后端logger系统中,有三个最基础的概念需要先熟悉:
- Logger 日志记录器 - 日志记录器就是一个普通的Java类而已。
- Appender 输出源 - 输出源是日志最终输出的地方,比如控制台或者文件
- Layout(Encoder) 布局 - 布局决定了日志的打印格式,比如使用
%r [%t] %-5p %c %x - %m%n
可以打印出467 [main] INFO org.apache.log4j.examples.Sort - Exiting main method.
这样的日志。
Logger 类位于 logback-classic 模块中, 而 Appender 和 Layout 位于 logback-core 中,
这意味着, Appender 和 Layout 并不关心 Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,日志信息才能被正常打印出来。
日志记录器 - logger
在logback中日志记录器是继承的,继承的规则是 com.hello.foo
会继承 com.hello
的日志配置,父子关系通过.
来分割,所以 com
是com.hello
的父节点。在logback中默认会有一个root-logger
(根 - 日志记录器)的存在,所有的其他日志记录器都会默认继承它的配置。
在配置文件中看到的:
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
就是“根”。所以当我们调用Logger logger = LoggerFactory.getLogger(App.class);
的时候,默认是从root-logger
那里继承了日志输出配置,而root-logger
默认的log打印级别是debug
,所以用logger.info
打印不出日志。
那么如何配置一个普通的日志记录器呢?这是非常简单的:
<logger name="com.hello" level="INFO"/>
这样便配置了一个名为com.hello
的日志记录器(一般会用包名作为日志记录器的名字),name
是必须配置的属性。
然后可以通过 Logger logger = LoggerFactory.getLogger("com.hello")
来得到这个日志记录器。
为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了一个 **分层 **概念。每个 logger 都有一个 name,这个 name 的格式与 Java 语言中的包名格式相同。
日志输出源 - Appenders
输出源配置把日志打印到控制台,输出源的 name
和 class
属性是必须配置的选项。
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
配置把日志打印到文件:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
有了输出源之后,就可以给logger配置输出源,一个logger可以配置多个输出源:
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
这里给 root-logger
配置了两个输出源,此时如果我们调用logger.debug
方法会发现控制台和根目录下的myApp.log
文件都打印了相同的日志。
在logback中,level的配置会被继承,但是appender的配置会被子logger保留。这么说有点抽象,看下面的例子:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.hello">
<appender-ref ref="STDOUT" />
</logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
这个配置会导致控制台打印两次:
11:38:06.068 [main] INFO com.hello.App - Hello world.Info
11:38:06.068 [main] INFO com.hello.App - Hello world.Info
当调用LoggerFactory.getLogger("com.hello.App")
的时候,它继承了来自 <logger name="com.hello">
和 root-logger
的输出源,而他们的输出源都是控制台,所以导致在控制台输出了两次。
解决办法是,要么在有继承关系的logger中配置不同的输出源(从业务上避免),要么在子logger中覆盖父节点中的配置。可以通过additivity="false"
配置实现:
<logger name="com.hello" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
additivity
属性告诉logback不要继承来自父类的设置。
布局 - Layout 和 编码 - Encoder
Layout主要用来把log事件转换成String。一般布局都是配置在 Appender
里面的:
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n" />
</layout>
</appender>
注意,上面的示例使用的Appender是org.apache.log4j.ConsoleAppender
,这是log4j的配置而不是这里讲的logback,这是因为在过去日志系统确实都是使用Layout来做日志转换的,但是由于一些 固有的问题 ,logback在Layout上面又封装了一层 - Encoder,表现在配置上就是这样的(这才是logback的配置):
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
但是Encoder的出现并不影响我们的配置,只是在形式上多了一个<encoder/>
标签。一般使用最多的是 PatternLayout
或PatternLayouEncoder
,他们的特点是提供了很多约定的标记,这些标记都以%
开头,比如logger名称、日志等级日期、线程名等。
-
%d{HH: mm:ss.SSS}
——日志输出时间 -
%thread
——输出日志的进程名字,这在Web应用以及异步任务处理中很有用 -
%-5level
——日志级别,并且使用5个字符靠左对齐 -
%logger{36}
——日志输出者的名字 -
%msg
——日志消息 -
%n
——平台的换行符
事实上我们在使用logback的时候很少见到直接使用layout,这是因为layout只能把一条log事件转化成String,layout不能控制log什么时候被写入文件,也不能做到批量处理。Encoder是被发明出来解决这些问题的。
目前为止Encoders只有一个实现 - PatternLayoutEncoder,它内部包含了一个PatternLayout,所以可以像使用PatternLayout一样使用Encoder。
常见配置
根节点<configuration>
包含的属性
- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
<configuration>
下面一共有2个属性,3个子节点,分别是:
属性一:设置上下文名称<contextName>
每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用<contextName>
设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName
来打印日志上下文名称。
<contextName>logback</contextName>
属性二:设置变量<property>
用来定义变量值的标签,<property>
有两个属性,name
和value
;其中name的值是变量的名称,value的值时变量定义的值。通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使${}
来使用变量。
<property name="log.path" value="E:\\logback.log" />
变量有三个作用域:
- local
- context
- system
local 作用域在配置文件内有效,context 作用域的有效范围延伸至 logger context,system 作用域的范围最广,整个 JVM 内都有效。
logback 在替换变量时,首先搜索 local 变量,然后搜索 context,然后搜索 system。
<property scope="context" name="nodeId" value="firstNode" />
也可以通过外部文件来定义:
<property file="src/main/java/chapters/configuration/variables1.properties" />
子节点一:<appender>
appender
用来格式化日志输出节点,有俩个属性name
和class
,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。
1、控制台输出ConsoleAppender:
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~
2、输出到文件RollingFileAppender
另一种常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。
RollingFileAppender用于切分文件日志:
<!--输出到文件-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
-
<fileNamePattern>logback.%d{yyyy-MM-dd}.log</fileNamePattern>
定义了日志的切分方式——把每一天的日志归档到一个文件中。 -
<maxHistory>30</maxHistory>
表示只保留最近30天的日志,以防止日志填满整个磁盘空间。同理,可以使用%d{yyyy-MM-dd_HH-mm}
来定义精确到分的日志切分方式。 -
<totalSizeCap>1GB</totalSizeCap>
用来指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志。
子节点二:<root>
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。
<root level="debug">
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
子节点三: <loger>
<loger>
用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>
。
<loger>
仅有一个name
属性,一个可选的level
和一个可选的addtivity
属性。
if表达式(条件化处理配置文件)
logback 允许在配置文件中定义条件语句,以决定配置的不同行为,
<!-- if-then form -->
<if condition="some conditional expression">
<then>
...
</then>
</if>
<!-- if-then-else form -->
<if condition="some conditional expression">
<then>
...
</then>
<else>
...
</else>
</if>
常用条件表达式函数
1. property('key')
2. isDefined('key')
3. isNull("key")
文件包含
可以使用 ≶include> 标签在一个配置文件中包含另外一个配置文件。
<configuration>
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
被包含的文件必须有以下格式:
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
支持从多种源头包含
- 从文件中包含
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
- 从 classpath 中包含
<include resource="includedConfig.xml"/>
- 从 URL 中包含
<include url="http://some.host.com/includedConfig.xml"/>
如果包含不成功,那么 logback 会打印出一条警告信息,如果不希望 logback 抱错,只需这样做:
<include optional="true" ..../>
添加一个 Context Listener
LoggerContextListener
接口的实例能监听 logger context 上发生的事件,比如说日志级别的变化,添加的方式如下所示:
<configuration debug="true">
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
....
</configuration>
多环境日志输出
据不同环境(prod:生产环境,test:测试环境,dev:开发环境)来定义不同的日志输出。
文件名称不是logback.xml,想使用spring扩展profile支持,要以logback-spring.xml命名
<!-- 测试环境+开发环境. 多个使用逗号隔开. -->
<springProfile name="test,dev">
<logger name="com.dudu.controller" level="info" />
</springProfile>
<!-- 生产环境. -->
<springProfile name="prod">
<logger name="com.dudu.controller" level="ERROR" />
</springProfile>