浅谈Log之Logback

logback 同样是由log4j的作者设计完成的,拥有更好的特性,是用来取代 log4j 的一个日志框架。logbackslf4j 的原生实现 。

logbacklog4j 的大多数概念类似,这里不作赘述。

认识 Logback

日志架构

涉及 说明
logback-core 其它两个模块的基础模块。包含AppenderLayout接口 。
logback-classic 它是 log4j 的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统(如 log4j 或 JDK1.4 Logging) 。包含 Logger 对象 。
logback-access 访问模块与Servlet容器集成提供通过Http来访问日志的功能。

核心对象

Logger

命名层次结构

和Log4j相同的命名规范和继承关系。

如果记录器的名称后跟一个点是后代记录器名称的前缀,则称该记录器是另一个记录器的祖先。如果记录器本身和后代记录器之间没有祖先,则称记录器是子记录器的父节点。

Appender

AppenderLogger 具有一样的继承关系。

Appender 控制日志输出目标,例如:控制台,文件,远程套接字服务器,数据库,JMS和远程UNIX Syslog守护程序等。

Layout

Layout 负责定制输出格式,它是通过 Appender 相关联来实现的。

支持对象

LoggerContext

各个logger 都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各logger。

Level

  • 如果给定的记录器没有分配级别,那么它将 从具有指定级别的最近祖先继承 一个级别。
  • 如果Logger请求的级别高于或等于其Logger的有效级别,则为启动,反之禁用。
级别(优先级:desc) 描述
OFF 最高级别,用于关闭日志。
FATAL 指明非常严重的错误事件,可能会导致应用终止执行。
ERROR 指明错误事件,但应用可能还能继续运行。
WARN 指明潜在的有害状况。
INFO 指明描述信息,从粗粒度上描述了应用运行过程。
DEBUG 指明细致的事件信息,对调试应用最有用。
TRACE 比 DEBUG 级别的粒度更细。
ALL 所有级别,包括定制级别。

使用 Logback

Logback的默认配置

  • 尝试在 classpath 下查找文件 logback-test.xml
  • 若文件不在,则查找 logback.xml
  • 若两个文件都不在,使用 BasicConfigurator 默认配置,日志输出到控制台。
1
2
3
4
5
6
<!--logback(自动依赖:logback-core,slf4j-api)-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>

logback.xml

appender.encoding 在高版本中不再需要,使用会产生异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0" encoding="UTF-8"?>
<!-- 根节点:
scan: 配置文件改变时,是否重新加载,默认为true;
scanPeriod: 扫描文件间隔时间,在 scan="true" 后生效,默认单位为毫秒;
debug: 是否打印logback的内部日志信息,默认为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义变量: 在下文中可以使用 ${} 使用该变量 -->
<property name="logfolder" value="/users/gushi/developer/log/demo" />

<!-- appender: 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoding>UTF-8</encoding>
<!-- 对日志进行格式化 -->
<encoder>
<pattern>[%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n</pattern>
</encoder>
<!-- 输出>=DEBUG级别的日志(因为mybatis的sql日志级别为DEBUG) -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>

<!-- appender: 滚动记录日志,先记录指定文件,再按照滚动策略将日志记录到其他文件 -->
<appender name="demo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${logfolder}/demo.log</File>
<!-- 是否追加日志,默认为true,置为false会先清空 -->
<append>true</append>
<!-- 滚动策略: 根据文件大小和时间来制定 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${logfolder}/daily/daily-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 归档文件最大保留数 -->
<MaxHistory>10</MaxHistory>
<!-- 最大文件大小 -->
<maxFileSize>100MB</maxFileSize>
<!-- 总大小 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n</pattern>
</encoder>
</appender>

<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${logfolder}/demo.log</File>
<!-- 滚动策略: 根据时间来制定 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logfolder}/demo.log.%d{yyyy-MM-dd}.gz</fileNamePattern>
<append>true</append>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n</pattern>
</encoder>
<!--级别过滤-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 设置具体包/类的打印级别,以及指定appender
name: 约束某个包/类;
level: 打印级别,和大小写无关,默认为DEBUG;
additivity:是否继承上级的级别,默认为true; -->
<logger name="org.demo" additivity="false" level="INFO" >
<appender-ref ref="demo" />
<appender-ref ref="console"/>
</logger>

<!-- mybatis log 日志 (因为mybatis的sql日志级别为DEBUG)-->
<logger name="org.demo.dao" level="DEBUG"/>

<!-- 根loger: 所有logger的上级,默认为DEBUG -->
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="error"/>
</root>

</configuration>

*.java

1
2
3
4
5
6
7
8
9
10
11
12
package org.Demo;

import org.slf4j.Logger;

public class Demo{
// 获取与类同名的日志对象
private final static Logger logger = LoggerFactory.getLogger(HelloJob.class);

public void log(){
logger.info("用户**访问了***");
}
}

附录

优化

logback 已自身优化,不用考虑因为使用日志功能而降低业务执行效率。注意使用占位符即可。

关闭日志时(多参数日志)

java中对String的直接拼接的消耗是巨大的。

由于日志级别Level的限制,一些日志可能不会输出,但是构造消息参数依然会消耗巨大成本。

1
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

方式一

即使输出,所需时间也增加不到 1%

1
2
3
if(logger.isDebugEnabled()) { 
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

方式二

推荐 使用类似 占位符 的方式,在日志被禁用的情况下,至少优化了30倍。

1
2
3
4
5
6
logger.debug("The new entry is {}.", entry);

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

开启日志记录时(默认继承)

Logger对象在创建时已明确了日志级别,不会动态继承。在基于有效级别接受或拒绝请求之前,记录器可以做出准瞬时决定,而无需咨询其祖先(不需要遍历记录器层次结构。)。

实际记录(格式化和写入输出设备)

已优化Layout和Appdender,一些logback组件已被重写几次以提高性能。

记录到本地计算机上的文件时,实际记录的典型成本约为9到12微秒。登录到远程服务器上的数据库时,它会持续几毫秒。