springboot整合logback用于日志输出
我们项目的基本框架已经完成,http请求已经可以访问,现在给我们的框架添加日志记录的功能并能将每天的记录记录到文件中去
在这里,我们使用logback日志组件来进行项目的日志管理
(一).logback简介
Logback是由Log4j的创始人设计的另一种开源日志组件,当前分为以下几个模块:
logback-core:是所有模块的基础
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API,使我们可以在其他日志系统,如log4j和JDK14 Logging中进行转换
logback-access:访问模块和Servlet容器集成,提供通过Http来访问日志的功能
(二) .整合示例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
在resource中添加logback.xml配置文件
这里我们输出了两个日志文件,第一个日志文件输出了所有的信息,包括普通的info信息、sql 以及报错信息,相当于我们项启动时控制台输出的信息
第二个日志文件只输出所有的报错信息(error文件)
<?xml version="1.0" encoding="UTF-8"?>
<!--scan为true时,如果配置文件发生改变将会进行重新加载 -->
<!-- scanPeriod属性设置监测配置文件修改的时间间隔,默认单位为毫秒,在scan为true时才生效 -->
<configuration debug="false" scan="false" scanPeriod="1 seconds">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="../logs" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- Console 输出设置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender> <!-- 不用彩色控制台输出 -->
<!--<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">-->
<!--<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">-->
<!--<!–格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符–>-->
<!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
<!--</encoder>-->
<!--</appender>-->
<!-- 按照每天生成日志文件 -->
<appender name="DAYINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/bailievable_info.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<!--<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender> <appender name="DAYERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/bailievable_error.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender> <!-- 将控制指定name包下的所有类的日志的打印,通过level设置打印级别 -->
<logger name="org.springframework" level="ERROR"/>
<logger name="org.apache.commons" level="ERROR"/>
<logger name="org.mybatis" level="ERROR"/>
<logger name="microsoft.exchange.webservices.data" level="ERROR"/>
<logger name="org.apache.ibatis.io.ResolverUtil" level="ERROR"/>
<logger name="org.apache.ibatis.logging" level="ERROR"/>
<logger name="com.alibaba.druid" level="ERROR"/>
<logger name="org.apache.http" level="ERROR" />
<logger name="com.creditease.bixin.dao" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
<appender-ref ref="DAYINFO" />
</logger> <!-- 日志输出级别 --> <root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="DAYERROR" />
<appender-ref ref="DAYINFO" />
</root>
</configuration>
接下来启动项目,然后访问请求
我们发现日志打印出来了,接下来我们去看看文件有没有生成
我们发现日志文件也顺利生成了。接下来让我们了解一些logback的原理和规则
Logger,Appenders 与 Layouts
在 logback 里,最重要的三个类分别是
- Logger
- Appender
- Layout
Logger 类位于 logback-classic 模块中, 而 Appender 和 Layout 位于 logback-core 中,这意味着, Appender 和 Layout 并不关心 Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,日志信息才能被正常打印出来。
分层命名规则
为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了一个 分层 概念。每个 logger 都有一个 name,这个 name 的格式与 Java 语言中的包名格式相同。这也是前面的例子中直接把一个 class 对象传进 LoggerFactory.getLogger() 方法作为参数的原因。
logger 的 name 格式决定了多个 logger 能够组成一个树状的结构,为了维护这个分层的树状结构,每个 logger 都被绑定到一个 logger 上下文中,这个上下文负责厘清各个 logger 之间的关系。
例如, 命名为 io.beansoft
的 logger,是命名为 io.beansoft.logback
的 logger 的父亲,是命名为 io.beansoft.logback.demo
的 logger 的祖先。
在 logger 上下文中,有一个 root logger,作为所有 logger 的祖先,这是 logback 内部维护的一个 logger,并非开发者自定义的 logger。
可通过以下方式获得这个 logger :
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
日志打印级别
logger 有日志打印级别,可以为一个 logger 指定它的日志打印级别。
如果不为一个 logger 指定打印级别,那么它将继承离他最近的一个有指定打印级别的祖先的打印级别。这里有一个容易混淆想不清楚的地方,如果 logger 先找它的父亲,而它的父亲没有指定打印级别,那么它会立即忽略它的父亲,往上继续寻找它爷爷,直到它找到 root logger。因此,也能看出来,要使用 logback, 必须为 root logger 指定日志打印级别。
日志打印级别从低级到高级排序的顺序是:TRACE < DEBUG < INFO < WARN < ERROR
如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。
举个例子:
package io.beansoft.logback.demo.universal; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /**
*
*
* @author beanlam
* @date 2017年2月10日 上午12:20:33
* @version 1.0
*
*/
public class LogLevelDemo { public static void main(String[] args) { //这里强制类型转换时为了能设置 logger 的 Level
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
logger.setLevel(Level.INFO); Logger barlogger = LoggerFactory.getLogger("com.foo.Bar"); // 这个语句能打印,因为 WARN > INFO
logger.warn("can be printed because WARN > INFO"); // 这个语句不能打印,因为 DEBUG < INFO.
logger.debug("can not be printed because DEBUG < INFO"); // barlogger 是 logger 的一个子 logger
// 它继承了 logger 的级别 INFO
// 以下语句能打印,因为 INFO >= INFO
barlogger.info("can be printed because INFO >= INFO"); // 以下语句不能打印,因为 DEBUG < INFO
barlogger.debug("can not be printed because DEBUG < INFO");
}
}
打印结果是:
WARN com.foo - can be printed because WARN > INFO
INFO com.foo.Bar - can be printed because INFO >= INFO
logback 内部运行流程
当应用程序发起一个记录日志的请求,例如 info() 时,logback 的内部运行流程如下所示
- 获得过滤器链条
- 检查日志级别以决定是否继续打印
- 创建一个
LoggingEvent
对象 - 调用 Appenders
- 进行日志信息格式化
- 发送
LoggingEvent
到对应的目的地
有关性能问题
关于日志系统,人们讨论得最多的是性能问题,即使是小型的应用程序,也有可能输出大量的日志。打印日志中的不当处理,会引发各种性能问题,例如太多的日志记录请求可能使磁盘 IO 成为性能瓶颈,从而影响到应用程序的正常运行。在合适的时候记录日志、以更好的方式发起日志请求、以及合理设置日志级别方面,都有可能造成性能问题。
关于性能问题,以下几个方面需要了解
- 建议使用占位符的方式参数化记录日志
- logback 内部机制保证 logger 在记录日志时,不必每一次都去遍历它的父辈以获得关于日志级别、Appender 的信息
- 在 logback 中,将日志信息格式化,以及输出到目的地,是最损耗性能的操作