1. 日志门面:JCL , slf4j
我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似spring,mybatis等其他的第三方框架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会出来日志体系的混乱。所以我们需要借鉴JDBC的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。就是说使用了日志门面,更改日志框架的时候就不需要修改底层代码;日志门面与日志实现是接口与实现类的关系;
常见的日志门面 :
JCL、slf4j
常见的日志实现:
JUL、log4j、logback、log4j2
SLF4J的使用
简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。
package com.bjpowernode.slf4j.test01;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.junit.Test;
public class SLF4JTest01 {
/*@Test
public void test01(){
*//*
入门案例
SLF4J对日志的级别划分
trace、debug、info、warn、error五个级别
trace:日志追踪信息
debug:日志详细信息
info:日志的关键信息 默认打印级别
warn:日志警告信息
error:日志错误信息
在没有任何其他日志实现框架集成的基础之上
slf4j使用的就是自带的框架slf4j-simple
slf4j-simple也必须以单独依赖的形式导入进来
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
@Test
public void test02(){
*//*
我们输出动态的信息时
也可以使用占位符的形式来代替字符串的拼接
我们有些时候输出的日志信息,需要我们搭配动态的数据
有可能是信息,有可能是数据库表中的数据
总之我们这样做最大的好处就是能够让日志打印变得更加灵活
如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性查
我们的日志打印是支持以替代符的形式做日志信息拼接的
一般情况下,几乎所有的日志实现产品,都会提供这种基础功能
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
String name = "zs";
int age = 23;
//logger.info("学生信息-姓名:"+name+";年龄:"+age);
//logger.info("学生信息-姓名:{},年龄:{}",new Object[]{name,age});
logger.info("学生信息-姓名:{},年龄:{}",name,age);
}
@Test
public void test03(){
*//*
日志对于异常信息的处理
一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式)
我们会根据异常信息提取出有用的线索,来调试bug
但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题
在控制台上其实也会提供相应的异常或者错误信息的输出
但是这种错误输出方式(输出的时间,位置,格式...)都是服务器系统默认的
我们可以通过日志技术,选择将异常以日志打印的方式,进行输出查看
输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义
*//*
//System.out.println(123);
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
//打印栈追踪信息
//e.printStackTrace();
logger.info("XXX类中的XXX方法出现了异常,请及时关注信息");
//e是引用类型对象,不能根前面的{}做有效的字符串拼接
//logger.info("具体错误是:{}",e);
//我们不用加{},直接后面加上异常对象e即可
logger.info("具体错误是:",e);
}
}
@Test
public void test04(){
*//*
集成其他日志实现之前
观察官网图
SLF4J日志门面,共有3种情况对日志实现进行绑定
1.在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的
值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的
使用的时候,也是需要导入依赖,自动绑定到slf4j门面上
如果不导入,slf4j 核心依赖是不提供任何实现的
2.logback和simple(包括nop)
都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计
那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接
值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录(后续课程)
3.log4j和JUL
都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计
通过适配桥接的技术,完成的与日志门面的衔接
试着将logback日志框架集成进来
测试1:
在原有slf4j-simple日志实现的基础上,又集成了logback
通过测试,日志是打印出来了 java.lang.ClassNotFoundException: aaa
通过这一句可以发现SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
虽然集成了logback,但是我们现在使用的仍然是slf4j-simple
事实上只要出现了这个提示
SLF4J: Class path contains multiple SLF4J bindings.
在slf4j环境下,证明同时出现了多个日志实现
如果先导入logback依赖,后导入slf4j-simple依赖
那么默认使用的就是logback依赖
如果有多个日志实现的话,默认使用先导入的实现
测试:
将slf4j-simple注释掉
只留下logback,那么slf4j门面使用的就是logback日志实现
值得一提的是,这一次没有多余的提示信息
所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了
通过这个集成测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变
这就是日志门面给我们带来最大的好处
在底层真实记录日志的时候,不需要应用去做任何的了解
应用只需要去记slf4j的API就可以了
值得一提的是,我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看
我们其实使用的仍然是slf4j日志门面,至于日志是log4j打印的(或者是logback打印的)
都是由slf4j进行操作的,我们不用操心
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
logger.info("具体错误是:",e);
}
}
@Test
public void test05(){
*//*
使用slf4j-nop
表示不记录日志
在我们使用slf4j-nop的时候
首先还是需要导入实现依赖
这个实现依赖,根据我们之前所总结出来的日志日志实现种类的第二种
与logback和simple是属于一类的
通过集成依赖的顺序而定
所以如果想要让nop发挥效果,禁止所有日志的打印
那么就必须要将slf4j-nop的依赖放在所有日志实现依赖的上方
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
logger.info("具体错误是:",e);
}
}
@Test
public void test06(){
*//*
接下来我们来绑定log4j
由于log4j是在slf4j之前出品的日志框架实现
所以并没有遵循slf4j的API规范
(之前集成的logback,是slf4j之后出品的日志框架实现
logback就是按照slf4j的标准指定的API,所以我们导入依赖就能用)
如果想要使用,需要绑定一个适配器
叫做slf4j-log4j12
再导入log4j的实现
测试:
log4j:WARN No appenders could be found for logger (com.bjpowernode.slf4j.test01.SLF4JTest01).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
虽然日志信息没有打印出来,那么根据警告信息可以得出:
使用了log4j日志实现框架
提示appender没有加载,需要在执行日志之前做相应的加载工作(初始化)
我们可以将log4j的配置文件导入使用
测试结果为log4j的日志打印,而且格式和级别完全是遵循log4j的配置文件进行的输出
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
@Test
public void test07(){
*//*
接下来我们来适配JDK14
与上一个测试log4j导入适配器一样
JUL也是slf4j之前出品的日志实现框架
所以也需要相应的适配器
适配器导入之后,JUL日志实现是不用导入依赖的
因为JUL,是JDK内置的
从测试结果来看,是JUL的日志打印,默认是info级别日志的输出
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
@Test
public void test08(){
*//*
绑定多个日志实现,会出现警告信息
通过源码来看看其原理(看看slf4j的执行原理)
进入到getLogger
看到Logger logger = getLogger(clazz.getName());
进入重载的getLogger
ILoggerFactory iLoggerFactory = getILoggerFactory(); 用来取得Logger工厂实现的方法
进入getILoggerFactory()
看到以双重检查锁的方式去做判断
执行performInitialization(); 工厂的初始化方法
进入performInitialization()
bind()就是用来绑定具体日志实现的方法
进入bind()
看到Set集合Set<URL> staticLoggerBinderPathSet = null;
因为当前有可能会有N多个日志框架的实现
看到staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
进入findPossibleStaticLoggerBinderPathSet()
看到创建了一个有序不可重复的集合对象
LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现
看到Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
我们主要观察常量STATIC_LOGGER_BINDER_PATH
通过常量我们会找到类StaticLoggerBinder
这个类是以静态的方式绑定Logger实现的类
来自slf4j-JDK14的适配器
进入StaticLoggerBinder
看到new JDK14LoggerFactory();
进入JDK14LoggerFactory类的无参构造方法
看到java.util.logging.Logger.getLogger("");
使用的就是jul的Logger
接着观察findPossibleStaticLoggerBinderPathSet
看到以下代码,表示如果还有其他的日志实现
while(paths.hasMoreElements()) {
URL path = (URL)paths.nextElement();
将路径添加进入
staticLoggerBinderPathSet.add(path);
}
回到bind方法
表示对于绑定多实现的处理
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
如果出现多日志实现的情况
则会打印
Util.report("Class path contains multiple SLF4J bindings.");
总结:
在真实生产环境中,slf4j只绑定一个日志实现框架就可以了
绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息
*//*
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}*/
@Test
public void test09(){
*//*
需求:
假设我们项目一直以来使用的是log4j日志框架
但是随着技术和需求的更新换代
log4j已然不能够满足我们系统的需求
我们现在就需要将系统中的日志实现重构为 slf4j+logback的组合
在不触碰java源代码的情况下,将这个问题给解决掉
首先将所有关于其他日志实现和门面依赖全部去除
仅仅只留下log4j的依赖
测试的过程中,只能使用log4j相关的组件
此时需要将日志替换为slf4j+logback
我们既然不用log4j了,就将log4j去除
将slf4j日志门面和logback的日志实现依赖加入进来
这样做,没有了log4j环境的支持,编译报错
这个时候就需要使用桥接器来做这个需求了
桥接器解决的是项目中日志的重构问题,当前系统中存在之前的日志API,可以通过桥接转换到slf4j的实现
桥接器的使用步骤:
1.去除之前旧的日志框架依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.添加slf4j提供的桥接组件
log4j相关的桥接器
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
桥接器加入后,代码编译就不报错了
测试:
日志信息输出
输出格式为logback
证明了现在使用的确实是slf4j门面+logback实现
在重构之后,就会为我们造成这样一种假象
使用的明明是log4j包下的日志组件资源
但是真正日志的实现,却是使用slf4j门面+logback实现
这就是桥接器给我们带来的效果
注意:
在桥接器加入之后,适配器就没有必要加入了
桥接器和适配器不能同时导入依赖
桥接器如果配置在适配器的上方,则运行报错,不同同时出现
桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义
*//*
Logger logger = LogManager.getLogger(SLF4JTest01.class);
logger.info("info信息");
}
@Test
public void test10(){
*//*
在配置了桥接器之后,底层就是使用slf4j实现的日志
分析其中原理
通过getLogger
进入Log4jLoggerFactory
Logger newInstance = new Logger(name); 新建logger对象
进入构造方法
protected Logger(String name) {
super(name);
}
点击进入父类的构造方法
Category(String name) {
this.name = name;
this.slf4jLogger = LoggerFactory.getLogger(name);
if (this.slf4jLogger instanceof LocationAwareLogger) {
this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
}
}
在这个Category构造方法中,核心代码
this.slf4jLogger = LoggerFactory.getLogger(name);
LoggerFactory来自于org.slf4j
*//*
Logger logger = LogManager.getLogger(SLF4JTest01.class);
}
}
2. 日志实现
: LUL(JAVA原生的日志框架使用时不需要引入第三方类库)、logback(springboot2.0后官方推荐使用)、log4j、log4j2
JUL日志实现
@Test public void testQuick() throws Exception {
// 1.创建日志记录器对象
Logger logger = Logger.getLogger("com.itheima.log.JULTest");
// 2.日志记录输出
logger.info("hello jul");
logger.log(Level.INFO, "info msg");
String name = "jack";
Integer age = 18;
logger.log(Level.INFO, "用户信息:{0},{1}", new Object[]{name, age});
}}
LOG4J2日志实现
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。
第一种<!-- Log4j2 门面API-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
第二种<!--使用slf4j作为日志的门面,使用log4j2来记录日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--为slf4j绑定日志实现 log4j2的适配器 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
package com.bjpowernode.log4j2.test01;
//import org.apache.logging.log4j.LogManager;
//import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test01 {
@Test
public void test01(){
/*
入门案例
单纯的使用Log4j2的 门面+实现
Log4j2和log4j提供了相同的日志级别输出
默认为error级别信息的打印
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration
表示我们还没有建立自己的配置文件
如果没有建立配置文件,则使用默认的配置
*/
/*Logger logger = LogManager.getLogger(Log4j2Test01.class);
logger.fatal("fatal信息");
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");*/
}
@Test
public void test02(){
/*
使用配置文件
log4j2是参考logback创作出来的,所以配置文件也是使用xml
log4j2同样是默认加载类路径(resources)下的log4j2.xml文件中的配置
根标签,所有日志相关信息,都是在根标签中进行配置
<Configuration status="debug" monitorInterval="数值"></Configuration>
在根标签中,可以加属性
status="debug" 日志框架本身的日志输出级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于5秒
*/
/*Logger logger = LogManager.getLogger(Log4j2Test01.class);
logger.fatal("fatal信息");
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");*/
}
@Test
public void test03(){
/*
虽然log4j2也是日志门面,但是现在市场的主流趋势仍然是slf4j
所以我们仍然需要使用slf4j作为日志门面,搭配log4j2强大的日志实现功能,进行日志的相关操作
接下来我们配置的就是当今市场上的最强大,最主流的日志使用搭配方式:
slf4j+log4j2
1.导入slf4j的日志门面
2.导入log4j2的适配器
3.导入log4j2的日志门面
4.导入log4j2的日志实现
执行原理:
slf4j门面调用的是log4j2的门面,再由log4j2的门面调用log4j2的实现
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test04(){
/*
将日志输出到文件中
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test05(){
/*
日志的拆分
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
for (int i = 0; i < 2000; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
@Test
public void test06(){
/*
异步日志实现(单独分配线程做日志的记录)
方式1:使用AsyncAppender的方式
1.添加异步日志依赖
2.在Appenders标签中,对于异步进行配置
使用Async标签
3.rootlogger引用Async
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
//日志的记录
for (int i = 0; i < 2000; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
//系统业务逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("------------------");
}
}
@Test
public void test07(){
/*
异步日志实现(单独分配线程做日志的记录)
方式2:使用AsyncLogger的方式
全局异步
所有的日志都是异步的日志记录,在配置文件上不用做任何的改动
只需要在类路径resources下添加一个properties属性文件,做一步配置即可
文件名要求是:log4j2.component.properties
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
//日志的记录
for (int i = 0; i < 2000; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
//系统业务逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("------------------");
}
}
@Test
public void test08(){
/*
异步日志实现(单独分配线程做日志的记录)
方式2:使用AsyncLogger的方式
混合异步:
可以在应用中同时使用同步日志和异步日志,这使得日志的配置及输出会更加的灵活
需求:
假设我们现在有自定义的logger -- com.bjpowernode
让自定义的logger是异步的
让rootlogger是同步的
注意:
在做测试前,一定要将全局的异步配置注释掉
对于当前的logger,Log4j2Test01.class
Log4j2Test01本身就是在我们自定义的logger路径下的
注意:
如果使用异步日志
AsyncAppender、AsyncLogger不要同时出现,没有这个需求,效果也不会叠加
如果同时出现,那么效率会以AsyncAppender为主
AsyncLogger的全局异步和混合异步也不要同时出现,没有这个需求,效果也不会叠加
*/
Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);
//日志的记录
for (int i = 0; i < 2000; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
//系统业务逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("------------------");
}
}
}
log4j2默认加载classpath下的 log4j2.xml 文件中的配置。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!--
配置全局通用属性
-->
<properties>
<property name="logDir">D://test</property>
</properties>
<!-- 配置appender -->
<Appenders>
<!-- 配置控制台输出 -->
<Console name="consoleAppender" target="SYSTEM_OUT">
</Console>
<!-- 配置文件输出-->
<File name="fileAppender" fileName="${logDir}//log4j2.log">
<!-- 配置文件输出格式 -->
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
</File>
<!--
按照指定规则来拆分日志文件
fileName:日志文件的名字
filePattern:日志文件拆分后文件的命名规则
$${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹
例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
rollog-%d{yyyy-MM-dd-HH-mm}-%i.log
为文件命名的规则:%i表示序号,从0开始,目的是为了让每一份文件名字不会重复
-->
<RollingFile name="rollingFile" fileName="${logDir}/rollog.log"
filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!-- 日志消息格式 -->
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
<Policies>
<!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
<OnStartupTriggeringPolicy/>
<!-- 按照文件的大小进行拆分 -->
<SizeBasedTriggeringPolicy size="10KB"/>
<!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 配置异步日志 -->
<!--<Async name="myAsync">
<!– 将控制台输出做异步的操作 –>
<AppenderRef ref="consoleAppender"/>
</Async>-->
</Appenders>
<!-- 配置logger -->
<Loggers>
<!-- 自定义logger,让自定义的logger为异步logger -->
<!--
includeLocation="false"
表示去除日志记录中的行号信息,这个行号信息非常的影响日志记录的效率(生产中都不加这个行号)
严重的时候可能记录的比同步的日志效率还有低
additivity="false"
表示不继承rootlogger
-->
<AsyncLogger name="com.bjpowernode" level="trace"
includeLocation="false" additivity="false">
<!-- 将控制台输出consoleAppender,设置为异步打印 -->
<AppenderRef ref="consoleAppender"/>
</AsyncLogger>
<!-- 配置rootlogger -->
<Root level="trace">
<!-- 引用Appender -->
<!--<AppenderRef ref="consoleAppender"/>-->
<!--<AppenderRef ref="fileAppender"/>-->
<!--<AppenderRef ref="rollingFile"/>-->
<!--<AppenderRef ref="myAsync"/>-->
<AppenderRef ref="consoleAppender"/>
</Root>
</Loggers>
</Configuration>
Logback日志实现:
Logback主要分为三个模块:
logback-core:其它两个模块的基础模块
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j
APIlogback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
package com.bjpowernode.logback.test01;
import ch.qos.logback.core.ConsoleAppender;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
public class LOGBACKTest01 {
@Test
public void test01(){
/*
入门案例
logback有5种级别的日志输出
分别是
trace < debug < info < warn < error
通过信息打印,默认的日志级别是debug,trace信息没有打印出来
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test02(){
/*
Logback配置文件的使用
在resources下面,创建一份配置文件,命名为logback.xml
一切配置都是在根标签中进行操作的
<configuration>
</configuration>
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test03(){
/*
在实际的生产环境中,我们更希望将日志信息保留在文件中
在文件中,默认是以追加日志的形式做记录
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test04(){
/*
将日志输出成为一个html文件
这个html文件是由logback来帮我们控制样式以及输出的格式
内容由我们自己来指定
初始测试:样式不是很好看,需要将换行,空格都去除掉
在实际生产环境中,如果日志不是很多,建议使用html的方式去进行日志的记录
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test05(){
/*
日志拆分和归档压缩
重要标签来源:
查看源码
RollingFileAppender类中找到rollingPolicy属性
SizeAndTimeBasedRollingPolicy类中找到maxFileSize属性
这些属性在类中都是以set方法的形式进行的赋值
我们在配置文件中配置的信息,其实找到的都是这些属性的set方法
在TimeBasedRollingPolicy找到
static final String FNP_NOT_SET =
"The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";
只要我们要使用到日志的拆分
FileNamePattern属性是必须要使用到了
*/
for (int i = 0; i < 1000; i++) {
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
@Test
public void test06(){
/*
我们可以在appender中添加过滤器
以此对日志进行更细粒度的打印
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test07(){
/*
按照我们当前的代码执行顺序
代码肯定是按照从上向下的顺序执行
上面的代码完全执行完毕后,才会执行下面的代码
由此得出会出现的问题:
只要是在记录日志,那么系统本身的功能就处于一种停滞的状态
当日志记录完毕后,才会执行其他的代码
如果日志记录量非常庞大的话,那么我们对于系统本身业务代码的执行效率会非常低
所以logback为我们提供了异步日志的功能
配置方式:
1.配置异步日志
在异步日志中引入我们真正需要输出的appender
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="consoleAppender"/>
</appender>
2.在rootlogger下引入异步日志
<appender-ref ref="asyncAppender"/>
所谓异步日志的原理是:
系统会为日志操作单独的分配出来一根线程,原来用来执行当前方法的主线程会继续向下执行
线程1:系统业务代码执行
线程2:打印日志
两根线程争夺CPU的使用权
在实际项目开发中,越大的项目对于日志的记录就越庞大
为了保证项目的执行效率,异步日志是一个很好的选择
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
//日志打印操作
for (int i = 0; i < 100; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
//系统本身业务相关的其他操作
System.out.println("1----------------------");
System.out.println("2----------------------");
System.out.println("3----------------------");
System.out.println("4----------------------");
System.out.println("5----------------------");
}
@Test
public void test08(){
/*
自定义logger
*/
Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test09(){
/*
关于logback补充:
1.异步日志:
可配置属性
配置了一个阈值
当队列的剩余容量小于这个阈值的时候,当前日志的级别 trace、debug、info这3个级别的日志将被丢弃
设置为0,说明永远都不会丢弃trace、debug、info这3个级别的日志
<discardingThreshold>0</discardingThreshold>
配置队列的深度,这个值会影响记录日志的性能,默认值就是256
<queueSize>256</queueSize>
关于这两个属性,一般情况下,我们使用默认值即可
不要乱配置,会影响系统性能,了解其功能即可
2.关于不同的日志实现,配置文件也是不同的
例如:
log4j经常使用的是properties属性文件
logback使用的是xml配置文件
如果我们遇到了一种情况,就是需要将以前的log4j,改造为使用logback
应该如何处理
我们可以使用工具
访问logback官网
找到log4j.properties转换器
只要是二者兼容的技术,才会被翻译
如果是log4j独立的技术,logback没有,或者是有这个技术但是并不兼容转义
那么这个工具则不会为我们进行翻译
如果是遇到简单的配置,我们可以使用工具
*/
}
}
logback.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
配置文件通用属性
<property name="" value=""></property>
所谓配置文件中的通用属性是为了让接下来的配置更加方便引用
通过以${name}的形式,方便的取得value值
通过取得的value值可以做文件的其他配置而使用
-->
<!--
我们在此可以先做日志输出格式相关的配置
%-10level 级别 案例为设置10个字符,左对齐
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期
%c 当前类全限定名
%M 当前执行日志的方法
%L 行号
%thread 线程名称
%m或者%msg 信息
%n 换行
以property的形式将日志输出格式配置成为文件的通用的属性
那么下面我们配置的输出方式中,就可以重复的引用该配置(以下的配置,对于输出格式就不用配置多次了)
-->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property>
<property name="pattern1" value="[%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m%n"></property>
<!-- 配置文件的输出路径 -->
<property name="logDir" value="D://test"></property>
<!-- 配置文件的appender 普通文件-->
<appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
<!-- 引入文件位置 -->
<file>${logDir}/logback.log</file>
<!-- 设置输出格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 配置控制台appender -->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!--
表示对于日志输出目标的配置
默认:System.out 表示以黑色字体输出日志
设置:System.err 表示以红色字体输出日志
-->
<target>
System.err
</target>
<!--
配置日志输出格式
手动配置格式的方式
直接引入上述的通用属性即可
-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 格式引用通用属性配置 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 配置文件的appender html文件 -->
<appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">
<file>${logDir}/logback.html</file>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>${pattern1}</pattern>
</layout>
</encoder>
</appender>
<!-- 配置文件的appender 可拆分归档的文件 -->
<appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 输入格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 引入文件位置 -->
<file>${logDir}/roll_logback.log</file>
<!-- 指定拆分规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
<fileNamePattern>${logDir}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!-- 按照文件大小来进行拆分 -->
<maxFileSize>1KB</maxFileSize>
</rollingPolicy>
</appender>
<!-- 配置控制台的appender 使用过滤器 -->
<appender name="consoleFilterAppender" class="ch.qos.logback.core.ConsoleAppender">
<target>
System.err
</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 配置过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志的输出级别 -->
<level>ERROR</level>
<!-- 高于level中设置的级别,则打印日志 -->
<onMatch>ACCEPT</onMatch>
<!-- 低于level中设置的级别,则屏蔽日志 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 配置异步日志 -->
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="consoleAppender"/>
</appender>
<!--
日志记录器
配置root logger
level:配置日志级别
可以同时配置多个appender,做日志的多方向输出
-->
<root level="ALL">
<!-- 引入appender -->
<!--<appender-ref ref="roll"/>-->
<!--<appender-ref ref="consoleFilterAppender"/>-->
<!--<appender-ref ref="consoleAppender"/>-->
<appender-ref ref="asyncAppender"/>
</root>
<!--
additivity="false"
表示不继承rootlogger
-->
<logger name="com.bjpowernode" level="info" additivity="false">
<!-- 在自定义logger中配置appender -->
<appender-ref ref="consoleAppender"/>
</logger>
</configuration>
SpringBoot日志实现
springboot框架在企业中的使用越来越普遍,springboot日志也是开发中常用的日志系统。springboot默认就是使用SLF4J作为日志门面,logback作为日志实现来记录日志。
额外添加 lombok 依赖 然后再使用 slf4j 会更加方便,以下是对比
一,直接使用slf4j
@Slf4j
@RestController
public class slf4jController {
Logger logger = LoggerFactory.getLogger(slf4jController.class);
@RequestMapping
public void test1(){
logger.info(“直接使用dlf4j情况下,打印日志”);
}
}
二,使用lombok依赖下的slf4j
@Slf4j
@RestController
public class slf4jController {
@RequestMapping
public void test1(){
log.info(“使用lombok情况下,打印日志”);
}
}
/*使用logback*/
<dependency>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
/*使用log4j2*/
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除掉原始依赖 以此去除logback引用-->
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--添加log4j2依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
package com.bjpowernode.springbootlog;
import org.apache.logging.log4j.LogManager;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootlogApplicationTests {
@Test
public void test01() {
/*
入门案例:
springboot日志具体实现
级别测试
默认是info级别
logback的风格输出(默认使用的是logback的日志实现)
*/
Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test02() {
/*
使用log4j2的日志实现
观察桥接器是否起作用
结果:仍然是slf4j+logback
证明桥接器是起作用的
*/
org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringbootlogApplicationTests.class);
logger.info("info信息");
}
@Test
public void test03() {
/*
application.properties(yml)是springboot的核心配置文件(用来简化开发使用)
我们也可以通过该配置文件,修改日志相关的配置
*/
Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test04() {
/*
将日志输出到文件中
使用logging.file.path来配置文件路径下的文件夹(logging.file直接配置文件的形式已经过时,不使用)
在配置的文件夹下,日志文件生成的名字为spring.log
*/
Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
@Test
public void test05() {
/*
如果是需要配置日志拆分等相对高级的功能
那么application.properties就达不到需要了
需要使用日志实现相应的配置文件
例如我们现在使用的是logback日志实现
那么就需要在类路径resources下,配置logback.xml
由于log4j2性能的强大
当今市场上越来越多的项目选择使用slf4j+log4j2的组合
springboot默认使用的是slf4j+logback的组合
我们可以将默认的logback替换成为log4j2
1.启动器依赖,间接的依赖logback
所以需要将之前的环境中,logback的依赖去除掉
2.添加log4j2依赖
3.将log4j2的配置文件log4j2.xml导入到类路径resources下面
*/
/*Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
for (int i = 0; i < 2000; i++) {
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}*/
Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
logger.error("error信息");
logger.warn("warn信息");
logger.info("info信息");
logger.debug("debug信息");
logger.trace("trace信息");
}
}
application.properties
logging.level.com.bjpowernode=trace
logging.pattern.console=%d{yyyy-MM-dd} [%level] -%m%n
//日志输出地址
logging.file.path=D:/test/springbootlog
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!-- 配置控制台输出 -->
<Console name="consoleAppender" target="SYSTEM_ERR">
</Console>
</Appenders>
<!-- 配置logger -->
<Loggers>
<Root level="trace">
<AppenderRef ref="consoleAppender"/>
</Root>
</Loggers>
</Configuration>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<target>
System.err
</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 配置文件的appender 可拆分归档的文件 -->
<appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 输入格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 引入文件位置 -->
<file>D:/test/roll_logback.log</file>
<!-- 指定拆分规则 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
<fileNamePattern>D:/test/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!-- 按照文件大小来进行拆分 -->
<maxFileSize>1KB</maxFileSize>
</rollingPolicy>
</appender>
<logger name="com.bjpowernode" level="info" additivity="false">
<appender-ref ref="roll"/>
</logger>
</configuration>