作为程序员,程序开发写代码一定少不了记录日志(没日志的代码都是耍流氓!)今天记录一下自己新搭建spring-boot项目后对日志的思考。
起因:项目是一个无页面,通过直接访问数据库,然后调用第三方api实现数据上传的简单应用。因为项目很小,所以在项目完成后,为了使jar包的大小更小(强迫症,想把无用的maven引用全部去掉),所以打包完成后对lib下的jar包进行查看,发现关于log的jar包很多,由此引发联想,难道一个简单的日志都要用这么多的jar包吗?
查看自己的代码,发现只用到了如下代码
commons-logging和slf4j是java中的日志门面,即它们提供了一套通用的接口,具体的实现可以由开发者自由选择。log4j和logback则是具体的日志实现方案。这是典型的门面模式
比较常用的搭配是commons-logging+log4j,slf4j+logback
spring默认使用commons-logging+log4j
spring-boot默认使用slf4j+logback
SLF4J是编译时绑定到具体的日志框架,性能优于采用运行时搜寻的方式的commons-logging
不需要使用logger.isDebugEnabled()来解决日志因为字符拼接产生的性能问题
logger.info("my name is {}", "medusar");
logger.info("my name is " + "medusar");
在效率上,第一行比第二行更高,因为如果当前日志级别是ERROR,第一行不会进行字符串拼接,而第二行,无论日志级别是什么,都会先进行字符串拼接。
所以为了解决这个问题,commons-logging等框架提供了下面的方式:
if (log.isDebugEnabled()){
log.debug("dddd"+"eee");
这些包的目的是什么?
答案是:统一日志记录。a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx等等,每个框架都是自己使用的日志框架,如何统一他们的?
将系统中其他日志框架先排除出去;
用中间包来替换原有的日志框架;
我们导入slf4j其他的实现
说白了就是,其他的框架还以为他们用的还是自己的日志框架,而实际上,他们的日志框架已经不存在了,存在的是slf4j替换过后的对原有框架的实现的中间包
形象的比喻是:春秋战国时期,各个诸侯纷纷自立为王,他们的臣子都拥戴自己的大王,忽然有一天,秦国的影密卫,将其中几个大王偷偷换成了自己的亲信,让他们代替这几个诸侯国的大王,然后将真正的大王们都杀了,做的神不知鬼不觉,他们的大臣们依然死心塌地的拥戴着自己的大王,并不知道大王已经是替身;实际上他们的大王已经都听命于秦国。表面是各国纷乱,而实际上他们已经实现了统一,都归秦国管理!!!
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
使用logback 配置文件是:
logback.xml:直接就被日志框架识别了。
logback-spring.xml:日志框架就不直接加载日志的配置项,由 SpringBoot 解析日志配置,可以使用 SpringBoot 的高级 Profile 功能。
logback-spring.xml完整配置文件:
logback的语法可以描述为:<configuration>元素包含零个或多个<appender>元素,后跟零个或多个<logger>元素,后跟至多一个<root>元素。
configuration :根标签
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true,在logback-spring.xml使用时可能会报错,所以不能一起使用
scanPeriod:默认情况下,将每分钟扫描一次配置文件的更改。可以以毫秒、秒、分钟或小时为单位指定值。未指定时间单位为毫秒
<configuration>
<!-- 用来引入外部资源文件,resource:表示资源路径 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 每个logger都关联到logger上下文,默认上下文名称为default,可以设置成其他名字,用于区分不同应用程序的记录,
一旦设置,不能修改,可以通过%contextName来打印日志上下文名称 -->
<contextName>logback</contextName>
<!-- 日志级别:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF -->
<!-- 自定义变量,使用${}来使用变量
name:变量名
value:变量值
scope:变量的作用范围-->
<property name="log_path" value="log" scope="context" />
<property name="log_pattern" value="%clr(%d{yy-MM-dd hh-mm-ss.SSS}){yellow} [%clr(%t){magenta}] [%clr(%p)] %clr(%C{1}.%M:%L) |:%clr(%m%n){blue}"/>
<property name="log_fileNamePattern" value="-%d{yyyy-MM-dd}.%i.log"/>
<property name="log_maxFileSize" value="10MB"/>
<property name="log_maxHistory" value="15"/>
<property name="dev_log_level" value="DEBUG" />
<property name="test_log_level" value="DEBUG" />
<property name="prod_log_level" value="INFO" />
<!-- 输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
appender标签:定义日志输出策略,包括日志文件名称,输出格式,日志滚动策略,日志文件大小等等,具体看下面说明
name:策略名称
class:策略类型
控制台输出:ch.qos.logback.core.ConsoleAppender
文件滚动输出:ch.qos.logback.core.rolling.RollingFileAppender
<file>:日志输出的路径及文件名
<encoder>:设置日志输出的格式和编码
<rollingPolicy>:设置日志切割策略
<fileNamePattern>:定义了日志的切割方式,切割后存放的文件名。
<maxFileSize>:触发策略,指定单个日志文件的上限大小,超限就会进行切割。
<maxHistory>:日志文件保留天数
<filter>:过滤规则
具体可看:http://logback.qos.ch/manual/configuration.html
http://logback.qos.ch/manual/appenders.html
<!-- 时间滚动输出 系统日志 -->
<appender name="SYSTEM" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_path}/app.log</file>
<encoder>
<pattern>${log_pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${logpath}/info/app${log_fileNamePattern}</fileNamePattern>
<maxFileSize>${log_maxFileSize}</maxFileSize>
<maxHistory>${log_maxHistory}</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- sql输出到sql.log文件 -->
<appender name="SQL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_path}/sql.log</file>
<encoder charset="UTF-8">
<pattern>${log_pattern}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log_path}/sql-${log_fileNamePattern}</fileNamePattern>
<maxFileSize>${log_maxFileSize}</maxFileSize>
<maxHistory>${log_maxHistory}</maxHistory>
</rollingPolicy>
</appender>
<!-- logger:记录器用来指定某一个包或者具体的类的日志打印级别。
name:指定某个包或者具体的类
level:指定日志输出级别。
additivity:如果没有指定level值,是否从最近的父标签继承level值,值为true或false-->
<logger name="org.springframework" level="WARN" />
<logger name="org.apache" additivity="false" level="WARN" />
<logger name="org.mybatis" additivity="false" level="DEBUG" />
<!-- 测试环境 -->
<springProfile name="test">
<root level="${test_log_level}">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- 开发环境 -->
<springProfile name="dev">
<root level="${dev_log_level}">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<logger name="com.boot.sql" level="DEBUG" additivity="false">
<appender-ref ref="SQL" />
</logger>
<root level="${prod_log_level}">
<appender-ref ref="SYSTEM" />
</root>
</springProfile>
</configuration>
使用log42 配置文件是(pom排除其他,引入中间包):
log4j2.xml
log4j2-spring.xml
使用jul 配置文件是(pom排除其他,引入中间包,存在已知的类加载问题,这些问题会导致从“可执行 jar”运行时出现问题。建议在从“可执行 jar”运行时尽可能避免使用。):
logging.properties
补充,以前我们使用spring的时候,Logging是spring中唯一强制的外部依赖,spring中默认使用的日志是commons-logging
,简称JCL
,这里说的强制性,是因为在spring-core
这个模块中引入了该依赖。不过,引入了该依赖,也无需做任何其他的配置,它是日志门面
,它内部会有自己的算法去找日志门面的实现类,比如log4j
,如果说没有引入其他日志依赖,它默认就会去找JDK自带的java.util.logging
简称jul
作为其日志实现类
log4j1.x版本已经停止更新了,log4j2.x习惯性叫做`log4j2`。(大家还记得前段时间关于log4j漏洞的事情吧)
更换spring的common-logging日志系统
依赖配置,排除commons-logging
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
这个时候如果运行程序会抛异常,因为我们把 Spring 依赖的 commons-logging 排除了, 而这个依赖是必须有的,不是可选的。
加入转换包
这个转换包就相当于我们上面说的那个转换器,spring会调用jcl-over-slf4j,spring以为调用的还是commons-logging,但是实际上commons-logging已经被我们删除掉了,并且jcl-over-slf4j还可以将输出转换为slf4j类型的输出,又因为我们前面引入了logback的依赖,lagback是slf4j的实现,所以最终输出的就是logback的日志系统
<!-- 其他日志框架的中间转换包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
复制代码