说说日志框架的起源及现状 比如说,公司中张三要开发一个大型系统,需要打印日志的功能,他的日志完善的过程如下:
System.out.println(“xxx”), 将关键数据打印在控制台;新增和去除一行日志的打印很麻烦;
使用自己写的日志框架来记录系统的一些关键信息,zhangsan-logging.jar;
接着他又把之前写的日志jar包加了几个高大上的功能,如异步模式、自动归档等,zhangsan-logging-good.jar;
公司又需要搭建新的系统,跟之前用的API不一样,张三需要给新的系统重新换上新的日志功能的实现包,zhangsan-logging-better.jar;
张三突然想到了jdbc与数据库驱动的设计方式,他写了一个统一的接口层(日志功能的一个抽象层logging-abstract.jar),然后他要做的就是给项目中导入具体的日志实现就可以了,张三之前写的日志框架都是实现的日志抽象接口;
其实目前市面上的日志框架也非常丰富,包括:
常见的日志抽象层
:
JCL(Jakarta Commons Logging) 不建议使用
jboss-logging 不建议使用
SLF4J(Simple Logging Facade for Java),推荐使用
常见的日志实现层
:
Log4j
Log4j2(Log4j的升级版)
Logback(Log4j的重置版),推荐使用
JUL(java.util.logging)
Spring Boot使用的日志 spring boot 的底层是spring框架,而spring框架默认是用JCL(Jakarta Commons Logging)做日志输出的。spring boot在日志功能上做了一层包装,选用的是SLF4J和Logback
。
如何在系统中使用SLF4J SLF4J的用户手册,参考 SLF4J用户手册
SLF4J及常用实现包的使用如下图:
在应用开发的时候,做日志输出的时候,不应该直接调用日志输出的实现类,而是调用日志抽象层的方法。在此之前,需要给系统导入slf4j和logback的jar包。
1 2 3 4 5 6 7 8 9 import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld { public static void main (String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World" ); } }
注意:每一个日志的实现框架都有自己的配置文件,使用slf4j作为日志抽象层之后,配置文件还是需要做成日志实现框架 的配置文件。
使用SLF4J统一不同框架的日志输出 开发一个系统难免会用到很多框架,例如开发A系统,用到了 spring(日志使用commons-logging)、hibernate(日志使用jboss-logging)、mybatis等框架 。那么问题来了,怎样统一日志记录?并且即使是使用别的框架,那么如何统一使用SLF4J和Logback进行日志的输出?
SLF4J的办法请参考使用SLF4J统一日志输出 ,用一个成语概括就是 偷天换日
,具体做法就是:
排除 spring的commons-logging 默认日志包(必须排除掉,否则与替换包中的类发生冲突);
使用 jcl-over-slf4j 替换原来的 commons-logging 包;
更多示例如下图所示:
Springboot底层的日志包依赖关系
springboot底层spring排除commons-logging依赖:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <exclusions > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > </exclusions > </dependency >
总结:
springboot底层也是使用slf4j+logback的方式进行日志记录的;
springboot也把其他形式的日志实现替换成了slf4j的形式(还记得偷天换日吗?);
所以springboot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候只需要把这个框架依赖的日志jar包排除掉即可。
Spring Boot 日志设置 日志功能默认配置 关于spring boot官方网站的日志介绍,请参考 spring boot官网日志章节介绍 。
测试spring boot的默认设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @SpringBootTest @RunWith (SpringRunner.class)public class Test01 { @Test public void test01 () { Logger logger = LoggerFactory.getLogger(getClass()); logger.trace("这是 trace ..." ); logger.debug("这是 debug ..." ); logger.info("这是 info ..." ); logger.warn("这是 warn ..." ); logger.error("这是 error ..." ); } }
spring boot底层日志默认的设置参考 spring-boot-2.1.3.RELEASE.jar:
1 2 3 package org.springframework.boot.logging.java;package org.springframework.boot.logging.log4j2;package org.springframework.boot.logging.logback;
日志功能的手动配置 在配置文件中手动设置日志的输出级别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #设置日志的输出级别 logging.level={com.keyllo.demo.controller: trace, com.keyllo.service: debug} #设置日志输出的文件 #如果不设置目录则默认输出到当前项目的根目录下,如 logging.file=spring.log #下面设置先检查/Users/xxx/Desktop文件夹存不存在,不存在的话就创建,然后将日志写到其中的spring.log文件 #logging.file=/Users/xxx/Desktop/spring.log #设置日志输出的路径 #logging.path 和 logging.file 使用其中的一个设置即可 #下面设置先检查/Users/xxx/Desktop文件夹存不存在,不存在的话就创建,然后将日志写到默认的spring.log文件 #logging.path=/Users/xxx/Desktop #设置控制台日志输出的格式 #logging.file 和 logging.path 都不设置的话,日志只输出到控制台 logging.partern.console="%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n" #设置文件中日志输出的格式 logging.partern.file="%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n"
切换底层日志的实现框架 我们前面说过,spring boot默认的日志实现是logback,如果我们想用log4j作为spring boot的底层日志实现(虽然这样做没有意义,因为正是因为log4j写的不好,log4j的作者才又开发了logback,但是我们还是想更深入地测试一下),该怎么做呢?
其实我们前面已经说过,我们只需要按照 slf4j的日志适配图进行相应包的排除和替换
即可。
借用 idea工具的 diagrams->show dependencies 功能,我们可以很方便地做到以下事情:
把logback的依赖全部干掉,加入logback转换层的依赖(可省略)
把log4j转换层log4j-to-slf4j依赖干掉
把log4j适配层slf4j-log4j12(依赖与log4j实现包)、实现层log4j包依赖加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <artifactId > logback-classic</artifactId > <groupId > ch.qos.logback</groupId > </exclusion > <exclusion > <artifactId > log4j-to-slf4j</artifactId > <groupId > org.apache.logging.log4j</groupId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > </dependency >
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-logging</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-log4j2</artifactId > </dependency >
【注】log4j.properties 示例:
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 # properties project=zdemo-springboot04 logpath=/Users/xxx/Documents/mytmp/logs # root logger #log4j.rootLogger=INFO, Console,File,RollingFile,DailyRollingFile log4j.rootLogger=INFO, Console ## console log log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.Threshold=INFO log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=[%d] [%-3r] [%t,%x] [%-5p] %l - %m%n ## file log log4j.appender.File=org.apache.log4j.FileAppender log4j.appender.File.Threshold=ERROR log4j.appender.File.layout=org.apache.log4j.PatternLayout log4j.appender.File.layout.ConversionPattern=[%d] [%-3r] [%t,%x] [%-5p] %l - %m%n log4j.appender.File.file=${logpath}/${project}.log log4j.appender.File.encoding=UTF-8 ## rolling file log / large log log4j.appender.RollingFile=org.apache.log4j.RollingFileAppender log4j.appender.RollingFile.Threshold=INFO log4j.appender.RollingFile.layout=org.apache.log4j.PatternLayout log4j.appender.RollingFile.layout.ConversionPattern=[%d] [%-3r] [%t,%x] [%-5p] %l - %m%n log4j.appender.RollingFile.file=${logpath}/${project}.log log4j.appender.RollingFile.encoding=UTF-8 log4j.appender.RollingFile.MaxFileSize=500MB log4j.appender.RollingFile.MaxBackupIndex=10 ## daily rolling file log / small log log4j.appender.DailyRollingFile=org.apache.log4j.RollingFileAppender log4j.appender.DailyRollingFile.Threshold=INFO log4j.appender.DailyRollingFile.layout=org.apache.log4j.PatternLayout log4j.appender.DailyRollingFile.layout.ConversionPattern=[%d] [%-3r] [%t,%x] [%-5p] %l - %m%n log4j.appender.DailyRollingFile.file=${logpath}/${project}.log log4j.appender.DailyRollingFile.encoding=UTF-8 ## jdbc log log4j.appender.JDBC01=org.apache.log4j.jdbc.JDBCAppender log4j.appender.JDBC01.Threshold=INFO log4j.appender.JDBC01.layout=org.apache.log4j.PatternLayout log4j.appender.JDBC01.driver=com.mysql.jdbc.Driver log4j.appender.JDBC01.url=jdbc:mysql://192.168.1.96:3306/test?useUnicode=true&characterEncoding=UTF-8 log4j.appender.JDBC01.user=zhangqingli log4j.appender.JDBC01.password=qweasd log4j.appender.JDBC01.sql=INSERT INTO T_LOG VALUES('%x','%d','%C','%p','%m') # %m the logger.info msg # %p DEBUG INFO WARN ERROR FATAL # %r time from app starting milliseconds # %c log's location class # %t log's thread name # %n enter char # %d log's date and time # %l log's location class, thread name and the line number in code # CREATE TABLE IF NOT EXISTS T_LOG( # USER_ID VARCHAR(20) NOT NULL, # DATED DATE NOT NULL, # LOGGER VARCHAR(50) NOT NULL, # LEVEL VARCHAR(10) NOT NULL, # MESSAGE VARCHAR(1000) NOT NULL # );
日志功能的自定义配置文件 如果不使用spring boot默认的日志设置,也可以定义自己的日志配置文件(日志配置文件放在类路径下):
Logback: logback-spring.xml
,logback-spring.groovy,logback.xml,logback.groovy
Log4j2: log4j2-spring.xml
,log4j2.xml
JUL: logging.properties
推荐使用带spring扩展名的日志配置文件,例如对于 logback:
【注】:logback-spring.xml 日志设置参考:
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 81 82 83 <?xml version="1.0" encoding="UTF-8"?> <configuration scan ="false" scanPeriod ="60 seconds" debug ="false" > <property name ="LOG_HOME" value ="/Users/zhangqingli/Documents/mytmp/logs" /> <property name ="appName" value ="zdemo-springboot04" > </property > <appender name ="stdout" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > </layout > </appender > <appender name ="appLogAppender" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${appName}.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern > <MaxHistory > 365</MaxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 100MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern > </layout > </appender > <logger name ="org.springframework" level ="debug" additivity ="false" > </logger > <root level ="info" > <springProfile name ="dev" > <appender-ref ref ="stdout" /> </springProfile > <appender-ref ref ="appLogAppender" /> </root > </configuration >