本文来写说下logback-spring.xml相关的知识与概念
对于xml日志文件的配置,大多数人第一次接触时有一种望而生畏的感觉,其实如果仔细分析,会发现核心的部分只有三个元素:appender、logger、root。
其中configuration是根元素,必须的;logger和root可视为同一类,都是日志组件;logger定义日志从哪里(包)获取以及级别;appender配置日志格式、如何过滤、文件处理等。property和contextName元素,分别用来定义变量和应用上下文名称,非必须。
先通过一个简单的日志模板,从视觉上感受一下:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %
logger{36} - %msg%npattern>
encoder>
appender>
<logger name="chapters.configuration" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
root>
configuration>
上述实例定义了控制台输出debug级别日志的配置。下面对相关的元素进行逐一讲解。
logback.xml配置文件的基本结构可以描述为configuration元素,包含零个或多个appender元素,后跟零个或多个logger元素,后跟最多一个root元素(也可以没有)。
根元素configuration有三个属性:
contextName元素,每一个日志组件(logger)都会关联到日志上下文,默认上下文名称是’default’,用于标识应用,如果多个应用输出到同一个地方,就有必要使用%contextName来区别。
上线文的配置直接在configuration元素下:
<configuration>
<contextName>HelloWorld-logcontextName>
configuration>
经过定义之后,在其他property属性或appender中便可通过“%contextName”来获取和使用该上下文名称了。
通过property元素可定义变量。它有name和value两个属性。变量可以使“${name}”来使用变量。作用类似于代码中的常量字符串,定义之后公共地方便可以统一使用。如日志文件名称前缀、日志路径、日志输出格式等。
<configuration>
<property name="log.path" value="./log" />
configuration>
上面便是定义了日志的根路径的变量。
如果是在Spring或SpringBoot项目当中,想让value值是通过配置文件获取,可使用springProperty来定义。
<springProperty scope="context" name="log.path"
source="catalina.base"/>
其中source指定的catalina.base便是在application.properties当中配置变量。此配置还是比较常用的,可以做到灵活区分环境。
appender组件用来定义日志输出格式,日志如何过滤以及日志文件的处理。appender的结构如下:
appender的属性有name和class。name指定appender名称,后面使用该appender是也是通过名称来指定。
class属性指定要实例化的appender类的完全限定名称。appender类默认有以下几种:
appender元素可以包含零个或一个layout元素,零个或多个encoder元素以及零个或多个filter元素。
实战中ConsoleAppender及RollingFileAppender使用较多,若需要自定义如把日志输出到消息队列,可以自定义实现AppenderBase接口。
ConsoleAppender上面已经有示例,主要作用就是将日志输出到控制台,并通过pattern元素指定了输出的格式。下面重点看一下RollingFileAppender的配置。
RollingFileAppender是FileAppender的子类,扩展了FileAppender,具有翻转日志文件的功能。
例如,RollingFileAppender可以记录到名为log.txt文件的文件,并且一旦满足某个条件,就将其日志记录目标更改为另一个文件。
RollingFileAppender通常包括File、filter,rollingPolicy,encoder和layout元素。
encoder用来指定日志的输出格式及编码等:
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS}
%contextName [%thread] %-5level %logger{36} - %msg%n" />
<appender>
<encoder>
<pattern>${FILE_LOG_PATTERN}pattern>
<charset class="java.nio.charset.Charset">UTF-8charset>
encoder>
appender>
file元素用来配置日志的路径和名称,一般把路径和文件名前缀定义到变量(property中)。
<encoder>
<file>${log.path}/log_error.logfile>
appender>
${log.path}获取的便是前面属性中定义的变量。
appender中可以有多个filter元素,比如用ThresholdFilter来过滤低于指定临界值的日志;用LevelFilter来过滤日志级别。以LevelFilter为例:
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUGlevel>
<OnMismatch>DENYOnMismatch>
<OnMatch>ACCEPTOnMatch>
filter>
其中level指定日志级别,onMath指定符合过滤条件的操作接收(ACCEPT),onMismatch指定不符合条件的拒绝(DENY)。
如果需要将不同级别的日志输出到不同的日志文件,那么就需要配置多个filter,每个filter像上面一样指定level级别:DEBUG,INFO,WARN和ERROR。
通常情况下,日志输出会配置三个,一个控制台输出用于开发阶段;一个INFO及以上级别的日志输出,可追踪相应的生产日志;一个单独ERROR级别的日志输出,方便快速检查出异常日志。
rollingPolicy用于配置滚动策略,支持TimeBasedRollingPolicy、SizeAndTimeBasedRollingPolicy、FixedWindowRollingPolicy、SizeBasedTriggeringPolicy。
分别是基于时间滚动、基于大小和时间滚动、固定窗口滚动和大小触发。其中FixedWindowRollingPolicy一般和SizeBasedTriggeringPolicy同时使用。下面以TimeBasedRollingPolicy为例,以天为单位输出日志,每天一个日志。
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/debug.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
fileNamePattern指定日志的路径及名称,此处是按日期输出,即%d{yyyy-MM-dd}格式。maxHistory表示日志最多保留天数,存活超过30天的日志会被删除。
logger用来设置某一个类或者某个包的日志输出级别、以及关联的appender。
logger包含三个属性:
logger通过1个或多个子节点appender-ref来控制日志的输出。
<logger name="org.springframework" level="WARN"/>
<logger name="com" level="debug" />
上面示例表示org.springframework包下的日志以warn级别输出,com包下的日志以debug级别输出。
<logger name="com.secbro2" level="info" additivity="true">
<appender-ref ref="CONSOLE" />
logger>
上面的示例对指定包指定appender进行日志控制,由于设置了info级别,additivity为true,而且关联CONSOLE的appender,因此info以上级别的日志会输出到控制台。
同时会把日志上传到父级,即root。若root也有配置CONSOLE的输出的话,会在控制台输出两次。additivity为false,则不会。
root元素配置根记录器。它是一个特殊的logger,是所有logger的根节点,只有一个属性level,默认为DEBUG 。
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="LOGSTASH" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
root>
level属性的值可以是不区分大小写的字符串TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。root元素可以包含零个或多个appender-ref元素;被引用的每个appender都被添加到根记录器中。
如果是基于SpringBoot项目,针对不同环境(profile)有不同的日志输出,比如开发(dev)环境只输出CONSOLE的,生产环境(prod)只输入info和error,那则可用到Spring支持的profile机制。对应的元素为springProfile。
profile的值与springboot中配置文件的spring.profiles.active值进行对照。
<springProfile name="dev">
<logger name="com" level="debug" />
<root level="info">
<appender-ref ref="CONSOLE" />
root>
springProfile>
<springProfile name="test,prod">
<logger name="com" level="info" />
<root level="info">
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
root>
springProfile>
当引入了springProfile,我们可以看到root元素可以出现多次了,但还是保证了一个环境只有一个root元素的要求。springProfile的name对应spring.profiles.active的值,多个值用逗号分隔。
如果项目中的日志采用的是基于ELK(Elasticsearch,Logstash,Kibana三个开源软件的缩写)来进行日志管理。则可以在pom文件中引入logstash-logback-encoder依赖。
<dependency>
<groupId>net.logstash.logbackgroupId>
<artifactId>logstash-logback-encoderartifactId>
<version>5.1version>
dependency>
然后在logback-spring.xml中配置对应的appender:
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debuglevel>
filter>
<destination>192.168.0.11:5061destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"project": "springboot-logback-elk"}customFields>
encoder>
appender>
该appender的使用与其他appender的使用无异。主要使用了LogstashTcpSocketAppender类来完成与Logstash的通信。其中destination为Logstash提供的服务地址。customFields为自定义的参数,便于Logstash识别日志是从哪个业务系统传输过来的。
某些时候为了了解logback配置文件加载情况,配置中对应的appender、logger的装载情况等,我们需要启用logback状态数据的输出。这也是logback官网强烈推荐的,可以帮助我们排除诊断一些问题。
启用状态数据输出有两种方式:
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
两种方式选其一即可。第一种方式的debug与配置文件中的日志级别没有关系,只用于表示输出状态数据。
前面讲到的appender,日志输出到文件是同步输出的,即每次输出都会直接写IO到磁盘文件。对于高并发的应用,会产生一定的阻塞,造成不必要的性能损耗。
logback提供了日志异步输出的AsyncAppender。处理方式也很简单,添加一个基于异步写日志的appender,并指向原配置的appender即可:
<appender name="ASYNCDEBUG" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0discardingThreshold>
<queueSize>1024queueSize>
<appender-ref ref="DEBUGFILE"/>
<includeCallerData>trueincludeCallerData>
appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNCDEBUG" />
//...
root>
AsyncAppender的实现原理是通过阻塞队列(BlockingQueue)来避免日志直接输出到文件,先把日志事件输出到BlockingQueue中,然后启动一个新的worker线程,主线程不阻塞,worker线程则从队列中获取需要写的日志,异步输出到对应的位置。
在日常使用日志时,可以通过最开始的示例定义Logger对象,然后调用其对应级别的日志输出。当然,如果采用Lombok的情况下,可直接类上使用@Slf4j注解来自动注入log属性,不用再声明:
private static final Logger log = LoggerFactory.getLogger(LogbackController.class);
替换为:
@Slf4j
@RestController
public class LogbackController {
}
其他内容不变。
而在日志输出格式也有多种,其中最不可取的形式是如下模式的日志输出:
Object entry = new SomeObject();
logger.debug("The entry is " + entry);
这种模式会导致即使采用info基本输出,debug中的对象toString和字符串拼装依旧会处理。建议采用如下模式输出:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
此时,如果日志输出级别为info,则不会处理对象的toString和字符串的拼接。
logback作者进行测试得出:第一种和第二种写法输出结果相同。但在禁用日志记录语句的情况下,第二种将比第一种写法优于至少30倍。
到此,关于logback日志框架的讲解告一段落,如果你还想了解更多底层原理和一些表达式,建议看一下官方的手册,虽然是英文的,但还是比较全面的。
本文用巨大篇幅,描述了logback日志常见的使用场景,想必读到此处时,原来在那复杂的日志配置文件已经变得十分简单吧。赶快尝试一下吧。用了两天写完这篇文章,多多支持,你懂得该怎么做的。