1.概述
发展史:
java日志体系中是现有的log4j,后面才有了JDK自带的jul,两者是两套体系,互不兼容。后来为了不同日志体系之间的兼容,apache推出了jcl日志门面。后来log4j的作者因为和apache理念不合,离开apache后推出了一个全新的日志实现——logback,以及一个全新的日志门面——slf4j。最后apache优化了log4j推出了log4j2。
日志框架的核心问题:
日志是用来记录应用的一些运行信息的。假设没有日志框架,我们要在应用里手动实现日志相关功能,我们需要关注些什么?其实仔细想想无非两点:
- 记录哪些信息?
- 记录到哪里去?
当然作为日志框架来说,为了方便使用,它还要关注一点就是:
- 如何进行方便的配置
以上三点核心问题,我们看作为日志框架的开山鼻祖的log4j是怎样解决的:
log4j给出的答案是:
- 记录哪些信息——日志级别(level)
- 记录到哪里去——提供不同的输出方式(appender),文件、控制台、其它等等
- 如何进行方便的配置——除硬编码外,提供配置文件
2.jul
jul是JDK自带的日志框架,作者之前有一篇文章专门讲过,可以移步:
【JAVA日志框架】JUL,JDK原生日志框架详解。-CSDN博客
3.log4j
3.1.概述
log4j最后一个版本是12年发布的,如今apache已经停止对其维护,现在的项目基本上是不会选用log4j来作为日志框架了,但是作为第一款日志框架,其很多设计思想和体系架构,均成为了标准,后面的日志框架其实都是效仿的log4j。
依赖:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
3.2.日志级别
日志级别:
import org.apache.log4j.Logger; import org.junit.Test; public class test { private static Logger logger=Logger.getLogger(test.class); @Test public void test1(){ logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); logger.fatal("fatal"); } }
3.3.配置
如果直接像上面一样使用,会报错:
原因很简单,log4j没有进行初始化的默认配置,需要手动去进行配置,才能使用。
log4j的配置,主要就是配置appender,以下将展示几个实际工程中常用的appender:
ConsoleAppender
FileAppender
DailyRollingFileAppender,将每天的日志单独写成一个文件,即每天一个日志文件。
1.ConsoleAppender:
#控制台的appender,stdout是自定义的一个appender的名字。
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#自定义的这个appender用什么方式来输出,这里用System.out
log4j.appender.stdout.Target=System.out
#自定义的这个appender的日志级别
log4j.appender.stdout.Threshold=INFO
#自定义的这个appender的日志的格式
#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n
2.FileAppender:
#文件appender
log4j.appender.a1=org.apache.log4j.FileAppender
#自定义的这个appender用什么方式来输出,这里用System.out
log4j.appender.a1.File=${user.home}/LogDemo/log4j/a1.log
#自定义的这个appender的日志级别
log4j.appender.a1.Threshold=INFO
#自定义的这个appender的日志的格式
#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message
log4j.appender.a1.layout=org.apache.log4j.PatternLayout
log4j.appender.a1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n
3.DailyRollingFileAppender:
#将每天的日志单独写成一个文件,即每天一个日志文件的appender,stdout是自定义的一个appender的名字。
log4j.appender.a2=org.apache.log4j.DailyRollingFileAppender
#自定义的这个appender用什么方式来输出,这里用System.out
log4j.appender.a2.File=${user.home}/LogDemo/log4j/a1.log
#自定义的这个appender的日志级别
log4j.appender.a2.Threshold=INFO
#自定义的这个appender的日志的格式
#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message
log4j.appender.a2.layout=org.apache.log4j.PatternLayout
log4j.appender.a2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n
光是配置好了,还不能用,要把配置的appender关联到根logger上去:
#日志级别以及appender
log4j.rootLogger=Info,stdout,a1,a2
将配置文件放到classpath下面去即可,log4j会去classpath下找配置文件:
再运行就可以看到效果:
4.日志门面
4.1.jcl+log4j
4.1.1.使用
jcl也是apache推出的一个日志门面,jcl可能听起来比较陌生,它的另一个名字大家就会觉得很熟悉了——commons-logging。jcl最后一次更新是14年,后面apache放弃了对jcl的维护,所以jcl和log4j一样都不太会在新项目里面被使用,学jcl主要是理解它的思想。
依赖:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
jcl默认使用jul进行输出,其支持的日志级别和log4j一样:
package com.eryi; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; public class test { private static Log log= LogFactory.getLog(test.class); @Test public void test1(){ log.trace("trace"); log.debug("debug"); log.info("info"); log.warn("warn"); log.error("error"); log.fatal("fatal"); } }
要切换成log4j,直接引入log4j的依赖即可:
<dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies>
然后记得把log4j的配置文件放到classpath下面。
然后执行效果如下,可以看到是log4j输出的:
4.1.2.原理
其实源码没什么看头,核心就是去获取log的时候,这个log的底层实现用什么?
jcl里面写死了要适配的所有日志框架的logger的类全路径:
private static final String[] classesToDiscover = new String[]{"org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog"};
去load这些实现类,然后用反射实例化出来作为底层提供能力的内核。获取实现类的时候用了一个简单的模板模式,提供能力的时候用了一个简单的适配器模式,经此而已。
4.2.sl4j+logback
4.2.1.使用
sl4j是目前常用的日志门面,logback是目前常用的日志框架。
依赖:
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies>
slf4j和jcl使用上是相似的,只是工程的叫法不同,jcl里面叫LogFactory,slf4j里面叫LoggerFactory:
package com.eryi; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class test { private static Logger logger= LoggerFactory.getLogger(test.class); @Test public void test1(){ logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); } }
输出结果:
4.2.2.配置
logback默认会去classpath里面寻找配置文件。
配置文件示例:
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="10 seconds"> <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <contextName>logback</contextName> <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> <property name="LOG_SAVE_PATH" value="logs"></property> <!-- 定义输出日志记录格式 --> <property name="DEFAULT_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger] - (%F:%L\\) : %msg%n"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <Pattern>${DEFAULT_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!-- 时间滚动输出 level为 INFO 日志 --> <appender name="RollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_SAVE_PATH}/console.log</file> <encoder> <pattern>${DEFAULT_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志归档路径以及格式 --> <fileNamePattern>${LOG_SAVE_PATH}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留单位数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录info级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 WARN 日志 --> <appender name="RollingFileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_SAVE_PATH}/warn.log</file> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_SAVE_PATH}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录warn级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 ERROR 日志 --> <appender name="RollingFileError" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_SAVE_PATH}/error.log</file> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_SAVE_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留单位数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录ERROR级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!--additivity:是否继承root节点,默认是true继承。默认情况下子Logger会继承父Logger的appender, 也就是说子Logger会在父Logger的appender里输出。 若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。--> <logger name="org.springframework" level="INFO" additivity="false"> <appender-ref ref="CONSOLE"/> <appender-ref ref="RollingFileInfo"/> </logger> <logger name="org.springframework.security" level="INFO"></logger> <Logger name="org.apache.ibatis.io.DefaultVFS" level="INFO"/> <Logger name="org.apache.ibatis.io" level="INFO"/> <!-- 设置日志输出级别,从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF--> <root level="ALL"> <appender-ref ref="CONSOLE"/> <appender-ref ref="DEFAULT_FILE"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </configuration>
5.适配
由于api均不统一,jcl兼容log4j,slf4j兼容logback,当想交叉组合,比如slf4j+log4j时,需要用到适配包,各门面和各实现之间的适配关系如下图所示,用到的时候去查一下即可: