在Java项目中,为了记录和跟踪程序的运行状态,我们通常会记录一些日志,那如何正确的打印日志呢?楼主看到很多同学还是用System.out.println()
这种原始的方式,在高并发的情况下,这样玩你的程序真的会崩盘。
因为你可以看看其的源码实现是一种同步的方式,效率会比较低。除此之外,也不利于保存和查看。当然如果你说是为了方便在开发时查看日志,其实使用日志框架来记录也可以实现在控制台输出,供你在开发时使用。
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
Log4j
是Apache
的一个开源项目,它为日志的输出提供了完美的解决方案,利用它可以在项目中高效的控制日志和归档。下面我们开看看日志输出中的级别问题。
日志的级别
我们项目中通常使用的日志级别从高到低分别为:ERROR
、WARN
、INFO
、DEBUG
。如果你定义项目的日志输出级别为INFO
,则WARN
和ERROR
都可以输出,而DEBUG则不会输出。如果你是在调试阶段,可以定义输出级别为DEBUG
。
log4j
,logback
,slf4j
的关系
slf4j
是一种日志框架的抽象,它能使你方便的切换不同日志框架,你可以把slf4j
理解为日志框架的一种抽象接口,遵循这个接口标准的话,具体的实现你可以随便切换。
log4j
,logback
都是提供了日志解决方案的框,其中logback
也是log4j
的作者们开发出来的,不过遗憾的是,他们是为了取代log4j才去开发logback
的。log4j
不是slf4j
的原生实现,需要中间有个适配层,而logback
则完全遵从slf4j
的接口标准,所以logback可以提供更好的性能。
既然logback
可以提供更好的性能,下面就主要介绍logback
在项目中如何使用。
配置文件位置
配置文件名称为:logback.xml
spring-boot
默认使用logback
作为日志框架,不过都是按照默认配置来定义日志的,如果你需要覆盖默认配置,定义项目自己的配置文件,名称为:logback-spring.xml
不管是logback.xml
还是spring-boot
项目中的logback-spring.xml
文件,它们都应该放在项目的resource目录下。
配置文件的定义
根结点configuration
<configuration scan="true" scanPeriod="60 seconds" debug="false">
</configuration>
scan
属性如果设置为true
,在配置文件发生变化时,会自动加载,默认时false
。
scanPeriod
属性只有scan
属性设置为true
才会生效,表示扫描周期,默认为60s,注意要设置时间单位。
debug
为true
时可以打印出logback内部日志信息,从而实时查看logback运行状态。
公共属性定义
<property name="APP_Name" value="hadu_test"
<property name="LOG_PATH" value="logs"/>
<property name="LOG_ARCHIVE" value="${LOG_PATH}/archive"/>
<timestamp key="timestamp-by-second" datePattern="yyyy-MM-dd HH:mm:ss"/>
<contextName>${APP_Name}</contextName>
其中property
是定义变量,可以在上下文通过${LOG_PATH}
这种形式来引用。timestamp
是用来获取时间戳的,遵循java.txt.SimpleDateFormat的格式。contextName
是用来定义上下文的,从而可以区分不同的应用程序。
子节点appender
<appender name="File-Appender" class="ch.qos.logback.core.FileAppender">
<file>${LOG_PATH}/logfile-${timestamp-by-second}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
appender
是负责写日志的组件,其中name
定义当前appender
的名称,class
指定当前appender
的全限定名称。
file
定义了输出文件的名称,可以是绝对路径,也可以是相对路径。还可以定义append
来决定是否是追加日志,默认true
,表示追加。
encoder
定义了日志输出的格式化信息,其中pattern
定义了每一条日志输出的格式。outputPatternAsHeader
决定是否将格式作为日志的头第一条输出。
日志格式化中,%d{yyyy-MM-dd HH:mm:ss.SSS}
表示时间的格式化,[%thread]
为产生日志的线程名称,%-5level
输出日志的级别,%logger{36}
输出日志的logger名称,36表示最多可以输出多少字节数,如果为0则只输出类名,如果大于1,则至少输出类的缩写路径和类名,如果不带{}
限制,则输出全限定名称。%msg
表示日志输出信息。
很多时候,我们希望能每天生成一个日志文件,并且之前的日志能够归档,那我们可以用下面的appender
。
<appender name="RollingFile-Appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/rollingfile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_ARCHIVE}/rollingfile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!--归档文件保留周期,这里30表示30天,超过后最老的日志被删除-->
<maxHistory>30</maxHistory>
<!--定义日志最大量,超过了最老的日志将被删除,这里是10G-->
<totalSizeCap>10240MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</pattern>
</encoder>
</appender>
如果你项目记录的日志比较多,又不希望记录日志影响主程序的性能,不妨试试异步日志记录:
<!--异步appender日志,只需要把需要异步的appender放到Async-Appender中-->
<appender name="Async-Appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志. -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的大小,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="RollingFile-Appender"/>
</appender>
异步日志记录appender
的原理其实很简单,其持有一个BlockingQueue
的缓冲池,在接收到日志事件时,异步日志的appender
会调用append
方法将事件放到缓冲池,于此同时,异步日志的appender
会启动一个Worker线程,从BlockingQueue
缓冲池提取出来交给真正的appender
去处理。上面的queueSize
用来定义队列的大小。discardingThreshold
是抛弃策略,默认时,如果队列的80%已满,则会丢弃TRACT
、DEBUG
、INFO
级别的日志,为了不丢弃日志,可以设置为0。
子节点logger
logger
是为了给特定路径下的日志设置单独的输出级别,例如:
<logger name="com.netease.kaola.gaoshengli.learnspringboot.controller" level="info">
<!--日志记录文件的appender-->
<!--<appender-ref ref="File-Appender"/>-->
<!--归档文件的appender-->
<!--<appender-ref ref="RollingFile-Appender"/>-->
<!--选择异步方式-->
<appender-ref ref="Async-Appender"/>
</logger>
其中name
可以是一个类或者一个包,如果是包,则对包下面所有类生效。还有addtivity
属性用来设置是否向上级传递日志信息,默认是true
。
子节点root
root
字节点其实也是一种特殊的logger
,普通的logger
如果没有定义level
这样的属性,会从root
继承。
case
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<property name="APP_Name" value="hadu_test"/>
<property name="LOG_PATH" value="logs"/>
<property name="LOG_ARCHIVE" value="${LOG_PATH}/archive"/>
<timestamp key="timestamp-by-second" datePattern="yyyy-MM-dd HH:mm:ss"/>
<contextName>${APP_Name}</contextName>
<appender name="Console-Appender" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{1} -%msg%n</pattern>
</layout>
</appender>
<appender name="File-Appender" class="ch.qos.logback.core.FileAppender">
<file>${LOG_PATH}/logfile-${timestamp-by-second}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
<appender name="RollingFile-Appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/rollingfile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_ARCHIVE}/rollingfile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!--归档文件保留周期-->
<maxHistory>1</maxHistory>
<!--定义日志最大量,超过了最老的日志将被删除-->
<totalSizeCap>10240MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</pattern>
</encoder>
</appender>
<!--异步appender日志,只需要把需要异步的appender放到Async-Appender中-->
<appender name="Async-Appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.-->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="RollingFile-Appender"/>
</appender>
<!--不会向上传递,IndexController的日志不会输出到root的appender-->
<logger name="com.netease.kaola.gaoshengli.learnspringboot.controller.IndexController" level="INFO" additivity="false">
<!--日志记录文件的appender-->
<!--<appender-ref ref="File-Appender"/>-->
<!--归档文件的appender-->
<!--<appender-ref ref="RollingFile-Appender"/>-->
<!--选择异步方式-->
<appender-ref ref="Async-Appender"/>
</logger>
<!--会向上传递,包下的所有日志会输出到root的appender-->
<!--<logger name="com.netease.kaola.gaoshengli.learnspringboot" level="INFO">-->
<!--<!–日志记录文件的appender–>-->
<!--<!–<appender-ref ref="File-Appender"/>–>-->
<!--<!–归档文件的appender–>-->
<!--<!–<appender-ref ref="RollingFile-Appender"/>–>-->
<!--<!–选择异步方式–>-->
<!--<appender-ref ref="Async-Appender"/>-->
<!--</logger>-->
<root level="INFO">
<appender-ref ref="Console-Appender"/>
</root>
<!--<logger name="com.netease.kaola.gaoshengli.learnspringboot.controller" level="ERROR" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--<appender-ref ref="FILE"/>-->
<!--</logger>-->
</configuration>
上面的例子是以异步的方式输出日志,com.netease.kaola.gaoshengli.learnspringboot.controller.IndexController
的日志只会异步的输出到对应的异步appender
,并且不会上传,所以这个类的日志不会在控制行里输出,而其他类的日志由于没有单独定义appender
,都会走到root
这里,都在Console-Appender
输出。
以上,这篇文章应该可以让你如何在项目里正确使用日志了。