SpringBoot(⼗)Logback配置详解
⼀.了解
log4j2不打印日志
简单地说,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作为⽇志框架,⽐如说 Akka,Apache Camel 等Logback 与 Log4J
实际上,这两个⽇志框架都出⾃同⼀个开发者之⼿,Logback 相对于 Log4J 有更多的优点
同样的代码路径,Logback 执⾏更快
更充分的测试
原⽣实现了 SLF4J API(Log4J 还需要有⼀个中间转换层)
内容更丰富的⽂档
⽀持 XML 或者 Groovy ⽅式配置
配置⽂件⾃动热加载
从 IO 错误中优雅恢复
⾃动删除⽇志归档
⾃动压缩⽇志成为归档⽂件
⽀持 Prudent 模式,使多个 JVM 进程能记录同⼀个⽇志⽂件
⽀持配置⽂件中加⼊条件判断来适应不同的环境
更强⼤的过滤器
⽀持 SiftingAppender(可筛选 Appender)
异常栈信息带有包信息
⼆.快速上⼿
想在 Java 程序中使⽤ Logback,需要依赖三个 jar 包,分别是 slf4j-api,logback-core,logback-classic。其中 slf4j-api 并不是 Logback 的⼀部分,是另外⼀个项⽬,但是强烈建议将 slf4j 与 Logback 结合使⽤。要引⽤这些 jar 包,在 maven 项⽬中引⼊以下3个 dependencies
1.
<dependency>
2.
<groupId>org.slf4j</groupId>
3.
<artifactId>slf4j-api</artifactId>
4.
<version>1.7.5</version>
5.
</dependency>
6.
<dependency>
7.
<groupId>ch.qos.logback</groupId>
8.
<artifactId>logback-core</artifactId>
9.
<version>1.0.11</version>
10.
</dependency>
11.
<dependency>
12.
<groupId>ch.qos.logback</groupId>
13.
<artifactId>logback-classic</artifactId>
14.
<version>1.0.11</version>
15.
</dependency>
第⼀个简单的例⼦
1.
package io.beansoft.logback.demo.universal;
2.
3.
import org.slf4j.Logger;
4.
import org.slf4j.LoggerFactory;
5.
6.
/**
7.
*
8.
*
9.
* @author beanlam
10.
* @date 2017年2⽉9⽇下午11:17:53
11.
* @version 1.0
12.
*
13.
*/
14.
public class SimpleDemo {
15.
16.
private static final Logger logger = Logger(SimpleDemo.class);
17.
18.
public static void main(String[] args) {
19.
logger.info("Hello, this is a line of log message logged by Logback");
20.
}
21.
}
注意到这⾥,代码⾥并没有引⽤任何⼀个跟 Logback 相关的类,⽽是引⽤了 SLF4J 相关的类,这边是使⽤ SLF4J 的好处,在需要将⽇志框架切换为其它⽇志框架时,⽆需改动已有的代码。
LoggerFactory的getLogger()⽅法接收⼀个参数,以这个参数决定 logger 的名字,这⾥传⼊了SimpleDemo这个类的 Class 实例,那么 logger 的名字便是SimpleDemo这个类的全限定类名:io.beansoft.logback.demo.universal.SimpleDemo
Logger,Appenders 与 Layouts
在 logback ⾥,最重要的三个类分别是
Logger
Appender
Layout
Logger 类位于 logback-classic 模块中,⽽ Appender 和 Layout 位于 logback-core 中,这意味着, Appender 和 Layout 并不关⼼ Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,⽇志信息才能被正常打印出来
分层命名规则
为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了⼀个分层概念。每个 logger 都有⼀个 name,这个 name 的格式与 Java 语⾔中的包名格式相同。这也是前⾯的例⼦中直接把⼀个 class 对象传进 Logger() ⽅法作为参数的原因。
logger 的 name 格式决定了多个 logger 能够组成⼀个树状的结构,为了维护这个分层的树状结构,每个 logger 都被绑定到⼀个 logger 上下⽂中,这个上下⽂负责厘清各个 logger 之间的关系。
例如,命名为io.beansoft的 logger,是命名为io.beansoft.logback的 logger 的⽗亲,是命名为io.beansoft.logback.demo的 logger 的祖先。
在 logger 上下⽂中,有⼀个 root logger,作为所有 logger 的祖先,这是 logback 内部维护的⼀个 logger,并⾮开发者⾃定义的 logger。
可通过以下⽅式获得这个 logger :
Logger rootLogger = Logger(org.slf4j.Logger.ROOT_LOGGER_NAME);
同样,通过 logger 的 name,就能获得对应的其它 logger 实例。
Logger这个接⼝主要定义的⽅法有:
1.
package org.slf4j;
2.
public interface Logger {
3.
4.
// Printing methods:
5.
public void trace(String message);
6.
public void debug(String message);
7.
public void info(String message);
8.
public void warn(String message);
9.
public void error(String message);
10.
}
⽇志打印级别
logger 有⽇志打印级别,可以为⼀个 logger 指定它的⽇志打印级别。
如果不为⼀个 logger 指定打印级别,那么它将继承离他最近的⼀个有指定打印级别的祖先的打印级别。
这⾥有⼀个容易混淆想不清楚的地⽅,如果 logger 先它的⽗亲,⽽它的⽗亲没有指定打印级别,那么它会⽴即忽略它的⽗亲,往上继续寻它爷爷,直到它到 root logger。因此,也能看出来,要使⽤ logback,必须为 root logger 指定⽇志打印级别。
⽇志打印级别从低级到⾼级排序的顺序是:
TRACE < DEBUG < INFO < WARN < ERROR
如果⼀个 logger 允许打印⼀条具有某个⽇志级别的信息,那么它也必须允许打印具有⽐这个⽇志级别更⾼级别的信息,⽽不允许打印具有⽐这个⽇志级别更低级别的信息。
举个例⼦:
1.
package io.beansoft.logback.demo.universal;
2.
3.
import org.slf4j.Logger;
4.
import org.slf4j.LoggerFactory;
5.
6.
import ch.qos.logback.classic.Level;
7.
8.
/**
9.
*
10.
*
11.
* @author beanlam
12.
* @date 2017年2⽉10⽇上午12:20:33
13.
* @version 1.0
14.
*
15.
*/
16.
public class LogLevelDemo {
17.
18.
public static void main(String[] args) {
19.
20.
//这⾥强制类型转换时为了能设置 logger 的 Level
21.
ch.qos.logback.classic.Logger logger =
22.
(ch.qos.logback.classic.Logger) Logger("com.foo");
23.
logger.setLevel(Level.INFO);
24.
25.
Logger barlogger = Logger("com.foo.Bar");
26.
27.
// 这个语句能打印,因为 WARN > INFO
28.
logger.warn("can be printed because WARN > INFO");
29.
30.
// 这个语句不能打印,因为 DEBUG < INFO.
31.
logger.debug("can not be printed because DEBUG < INFO");
32.
33.
// barlogger 是 logger 的⼀个⼦ logger
34.
// 它继承了 logger 的级别 INFO
35.
// 以下语句能打印,因为 INFO >= INFO
36.
barlogger.info("can be printed because INFO >= INFO");
37.
38.
// 以下语句不能打印,因为 DEBUG < INFO
39.
barlogger.debug("can not be printed because DEBUG < INFO");
40.
}
41.
}
打印结果是:
1.
00:27:19.251 [main] WARN com.foo - can be printed because WARN > INFO
2.
00:27:19.255 [main] INFO com.foo.Bar - can be printed because INFO >= INFO
Appender 和 Layout
在 logback 的世界中,⽇志信息不仅仅可以打印⾄ console,也可以打印⾄⽂件,甚⾄输出到⽹络流中,⽇志打印的⽬的地由 Appender 来决定,不同的 Appender 能将⽇志信息打印到不同的⽬的地去。
Appender 是绑定在 logger 上的,同时,⼀个 logger 可以绑定多个 Appender,意味着⼀条信息可以同时打印到不同的⽬的地去。例如,常见的做法是,⽇志信息既输出到控制台,同时也记录到⽇志⽂件中,
这就需要为 logger 绑定两个不同的 logger。
Appender 是绑定在 logger 上的,⽽ logger ⼜有继承关系,因此⼀个 logger 打印信息时的⽬的地 Appender 需要参考它的⽗亲和祖先。在logback 中,默认情况下,如果⼀个 logger 打印⼀条信息,那么这条信息⾸先会打印⾄它⾃⼰的 Appender,然后打印⾄它的⽗亲和⽗亲以上的祖先的 Appender,但如果它的⽗亲设置了additivity = false,那么这个 logger 除了打印⾄它⾃⼰的 Appender 外,只会打印⾄其⽗亲的Appender,因为它的⽗亲的additivity属性置为了 false,开始变得忘祖忘宗了,所以这个 logger 只认它⽗亲的 Appender;此外,对于这个logger 的⽗亲来说,如果⽗亲的 logger 打印⼀条信息,那么它只会打印⾄⾃⼰的 Appender中(如果有的话),因为⽗亲已经忘记了爷爷及爷爷以上的那些⽗辈了。
打印的⽇志除了有打印的⽬的地外,还有⽇志信息的展⽰格式。在 logback 中,⽤ Layout 来代表⽇志打印格式。⽐如说,PatternLayout 能够识别以下这条格式:
%-4relative [%thread] %-5level %logger{32} - %msg%n
然后打印出来的格式效果是:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
上⾯这个格式的第⼀个字段代表从程序启动开始后经过的毫秒数,第⼆个字段代表打印出这条⽇志的线程名字,第三个字段代表⽇志信息的⽇志打印级别,第四个字段代表 logger name,第五个字段是⽇志信息,第六个字段仅仅是代表⼀个换⾏符
参数化打印⽇志
经常能看到打印⽇志的时候,使⽤以下这种⽅式打印⽇志:
logger.debug("the message is " + msg + " from " + somebody);
这种打印⽇志的⽅式有个缺点,就是⽆论⽇志级别是什么,程序总要先执⾏"the message is " + msg + " from " + somebody这段字符串的拼接操作。当 logger 设置的⽇志级别为⽐ DEBUG 级别更⾼级别时,DEBUG 级别的信息不回被打印出来的,显然,字符串拼接的操作是不必要的,当要拼接的字符串很⼤时,这⽆疑会带来很⼤的性能⽩⽩损耗。
于是,⼀种改进的打印⽇志⽅式被⼈们发现了:
1.
if(logger.isDebugEnabled()) {
2.
logger.debug("the message is " + msg + " from " + somebody);
3.
}
这样的⽅式确实能避免字符串拼接的不必要损耗,但这也不是最好的⽅法,当⽇志级别为 DEBUG 时,那么打印这⾏消息,需要判断两次⽇志级别。⼀次是logger.isDebugEnabled(),另⼀次是logger.debug()⽅法内部也会做的判断。这样也会带来⼀点点效率问题,如果能到更好的⽅法,谁愿意⽆视⽩⽩消耗的效率。
有⼀种更好的⽅法,那就是提供占位符的⽅式,以参数化的⽅式打印⽇志,例如上述的语句,可以是这样的写法:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。