目录
Spring Boot集成slf4j+logback
导入依赖
-
spring-boot-starter有传递依赖,可以不导包
-
<!--slf4j--> <dependency> <!--被logback-classic传递依赖--> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <!--logback--> <dependency> <!--被下面两个依赖传递依赖--> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> </dependency>
配置文件
-
SpringBoot默认使用resource下的文件
-
logback-spring.xml
-
logback-spring.groovy
-
logback.xml
-
logback.groovy
-
-
通过logging.config=classpath:config/log-config.xml
-
logback-spring.xml
-
<?xml version="1.0" encoding="UTF-8"?> <!-- debug="true":打印logback的内部日志信息 默认false--> <!-- scan="true":配置文件改变会重新加载 默认true 新配置存在错误 会回退--> <!-- scanPeriod="60 seconds":检测配置文件是否修改的间隔,默认单位是 毫秒。 scan="true"的时候默认60秒--> <!-- packagingData="true":打印jar包的名称以及版本号,计算成本高,默认false--> <configuration debug="true" scan="true" scanPeriod="60 seconds" packagingData="false"> <!-- 定义变量name为k,value为v,通过${name}来使用--> <property name="APP_NAME" value="myAppName"/> <property name="PROJECT_NAME" value="TEST_PROJECT"/> <property name="LOG_DIR" value="logs"></property> <property name="LOG_NAME" value="test_log"/> <!-- 设置上下文名称,默认为default--> <contextName>${APP_NAME}</contextName> <!-- 获取时间戳字符串,key为该timestamp的名字,datePattern为输出格式,遵循SimpleDateFormat--> <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/> <!-- 配置多个状态监听器--> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> <!-- name="STDOUT":appender的名称--> <!-- class:指定类的全限定名用于实例化--> <!-- ConsoleAppender 控制台日志组件--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!-- 格式化日志信息--> <encoder> <!-- 日志输出格式: -X号: X信息输出时左对齐; %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL, %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 %r: 输出自应用启动到输出该log信息耗费的毫秒数 %c: 输出日志信息所属的类目,通常就是所在类的全名 %t: 输出产生该日志事件的线程名 %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main (TestLog4.java:10) %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像Java servlets这样的多客户多线程的应用中。 %%: 输出一个”%”字符 %F: 输出日志消息产生时所在的文件名称 %L: 输出代码中的行号 %m: 输出代码中指定的消息,产生的日志具体信息 %M: 输出方法名 %n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行 --> <pattern> <!-- 项目名 时间 有效等级 所在文件名称 方法名 线程名:行号 消息 --> [${PROJECT_NAME}] [%d] [%level] [%F] [%M] [%t:%L] [%m]%n </pattern> <!-- 将pattern格式化字符串插入到日志文件顶部--> <outputPatternAsHeader>true</outputPatternAsHeader> <!-- 指定编码类型--> <charset>UTF-8</charset> </encoder> <!-- 设置输出流:System.out 或者 System.err,默认是 System.out--> <target>System.out</target> <!-- 只打印INFO级别的记录--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <!-- 配置符合过滤条件的操作--> <onMatch>ACCEPT</onMatch> <!-- 配置不符合过滤条件的操作--> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- FileAppender 文件日志组件--> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <!-- 被写入的文件名或路径--> <file>${LOG_DIR}/${LOG_NAME}.log</file> <!-- true:日志追加到文件尾部 false:清空现存文件,默认true--> <append>true</append> <encoder> <pattern>[${PROJECT_NAME}] [%d] [%level] [%F] [%M] [%t:%L] [%m]%n</pattern> </encoder> <!-- true:日志安全的写入,效率低,默认false--> <prudent>false</prudent> <!-- 只打印有效等级>=INFO的记录--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> </appender> <!-- RollingFileAppender 滚动记录文件日志组件--> <appender name="ROLL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/${LOG_NAME}.log</file> <!-- 滚动策略--> <!-- 根据日期和文件大小记录--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 根据时间每天改变一次--> <fileNamePattern>${LOG_DIR}/${LOG_NAME}_%d{yyyy-MM-dd}_%i.log</fileNamePattern> <!-- 根据文件大小--> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>50MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!-- 最大文件保留天数 days--> <maxHistory>30</maxHistory> <!-- 日志文件的上限大小,超过1GB则删除旧日志,需要maxHistory作为第一条件--> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>[${PROJECT_NAME}] [%d] [%level] [%F] [%M] [%t:%L] [%m]%n</pattern> </encoder> </appender> <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/${LOG_NAME}.log</file> <!-- 根据日期和文件大小记录--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 按天轮转 --> <fileNamePattern>${LOG_DIR}/${LOG_NAME}_%d{yyyy-MM-dd}_%i.log</fileNamePattern> <maxFileSize>50MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>[${PROJECT_NAME}] [%d] [%level] [%F] [%M] [%t:%L] [%m]%n</pattern> </encoder> </appender> <!-- 异步输出 --> <appender name="ASYNCDEBUG" class="ch.qos.logback.classic.AsyncAppender"> <!-- 默认如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志,若要保留全部日志,设置为0 --> <discardingThreshold>0</discardingThreshold> <!-- 更改默认的队列的最大容量,该值会影响性能.默认值为256 --> <queueSize>1024</queueSize> <!-- 添加附加的appender,最多只能添加一个 --> <appender-ref ref="DEBUGFILE"/> <!-- 是否包含调用者的信息,默认为false--> <includeCallerData>true</includeCallerData> <!-- 队列满之后是否丢弃信息 默认 false false不会丢弃,但是阻塞 true 不会阻塞,丢弃信息--> <neverBlock>true</neverBlock> </appender> <!-- root logger 根logger 只有level这个属性--> <!-- level="info":设置打印级别--> <root level="info"> <!-- 开发,测试:打印控制台--> <springProfile name="dev,test"> <!-- 指定appender,ref为appender的name属性--> <appender-ref ref="CONSOLE"/> </springProfile> <appender-ref ref="ROLLING"/> </root> <!-- 自定义logger--> <!-- name="com.xyh.mapper.UserMapper":指定该logger所指定的类或者包--> <!-- level="debug":设置打印级别--> <!-- additivity="true":是否像上级传递打印信息,层级叠加性--> <logger name="com.xyh.mapper.UserMapper" level="debug" addtivity="true"> </logger> </configuration>
-
https://segmentfault.com/a/1190000017911612
-
通过http传输到日志服务器
-
-
可以通过appender来指定日志记录的地点,第三方的或者自定义appender等
结合使用
-
设置logger
-
在xml当中进行配置<logger>
-
在yml中进行配置logging.level
-
通过注解@Slf4j
-
通过LoggerFactory获取logger
-
-
@Slf4j// 第一种获取方式 默认INFO public class Demo01 { // 第二种获取方式 根据配置文件 若没配置也是INFO private static final Logger logger = LoggerFactory.getLogger(Demo01.class); public static void main(String[] args) { // 第一种使用 log.trace("trace"); log.debug("debug"); log.info("info"); log.warn("warn"); log.error("error"); // 第二种使用 System.out.println(logger.getClass()); logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); } }
日志追踪MDC使用
MDC:Mapped Diagostic Contexts
映射诊断上下文
slf4j提供的一个API,主要负责在多线程的环境下进行日志调用链路的追踪
-
配置文件
-
通过格式化内设置 %X{keyName} 来输出指定MDC的key
-
<property name="LOG_PATTERN" value="[${PROJECT_NAME}] [%d] [%level] [%F] [%M] [%t:%L] %X{txId} [%m]%n"/>
-
-
Demo
-
Logger logger = LoggerFactory.getLogger(UserController.class); @RequestMapping(value = "/test", method = RequestMethod.GET, produces = "application/json; charset=utf-8") public void testController() { // 当前日志的Id String txId = UUID.randomUUID().toString(); // 设置MDC的KeyName为txId MDC.put("txId", txId); String debug = "DEBUG"; String info = "INFO"; String warn = "WARN"; logger.debug("testController-{}", debug); logger.info("testController-{}", info); logger.warn("testController-{}", warn); log.debug("testController-{}", debug); log.info("testController-{}", info); log.warn("testController-{}", warn); MDC.clear(); }
-
日志框架
问题追踪:通过日志不仅仅包括我们程序的一些bug,也可以在安装配置时,通过日志可以发现问题。
状态监控:通过实时分析日志,可以监控系统的运行状态,做到早发现问题、早处理问题。
安全审计:审计主要体现在安全上,通过对日志进行分析,可以发现是否存在非授权的操作。
commons-logging
apache最早提供的日志接口
避免与具体的日志方案耦合
-
类似于JDBC的API接口,具体的JDBC Rriver实现由个数据库提供商实现。通过统一接口解耦,不过内部实现了一些简单的日志方案。
Log4j
Logging for Java,经典的日志解决方案
内部将日志系统抽象封装成了Logger、appender、pattern等实现
通过配置文件轻松实现日志系统的管理和多样化配置
slf4j
Simple Logging Facade for Java。
对不同日志框架提供的封装。
可以在部署时不修改配置便可接入一种日志实现方案。
-
和commons-logging类似,但是有两个额外特点
-
支持多个参数,并通过{}占位符进行替换,避免书写logger.isXXXXEnabled判断
-
OSGI机制更好兼容支持。
-
logback
一款通用可靠、快速灵活的日志框架
作为Log4j的替代和sl4j组成了新的日志系统完整的实现。
-
关键路径上执行速度时log4j的10倍,同时内存消耗更少
Log4j2
Log4j2时Log4j的升级版,对Log4j 1.x进行了改进
-
同时修正了logback固有的架构问题
-
并改进了许多Logback所具备的功能
总结
-
slf4j和commons-logging是一种抽象的接口
-
Log4j、log4j2和logback是他们的实现
-
在一般使用是采用slf4j+Log4j2或者slf4j+logback
Logback的架构
logback三大核心模块
logback的基本架构足够通用,在不同情况下可以进行应用
目前主要分为三个模块
-
logback-classic:log4j的一个改良版本,同时整合了对slf4j的支持,实现了slf4j API
-
logback-access:Servlet容器继承提供通过Http来访问日志的功能
-
logback-core:其他两个模块的基础模块
logback三个主要类
logback建立在三个主要类之上
Logger(记录器)、Appender(附加器)、Layout(布局)
三个类的组件相互协同工作,使开发人员根据消息类型和级别记录消息,并控制这些消息的格式和报告位置
-
Logger为logback-classic下的接口
-
Appender和Layout为logback-core下的接口
-
logback-core没有Logger的概念
Logger context
-
日志API优势在于可以禁止某些日志的输出,同时不会妨碍其他日志输出。通过一个假定的日志空间,该空间包含所有可能的日志语句,根据开发人员设定来进行分类。
-
在logback-classic中,分类是logger的一部分,每个logger都依附在LoggerContext上,他负责生成logger,并通过树状层级结构进行管理
-
Logger的命名规范
大小写敏感
根据 . 符号进行分级
-
例如:com.foo是com.ffo.Bar的父级
-
com则是com.ffo.Bar的祖先
-
-
-
root logger为logger层次结构的最高层。
-
每一个logger都可以通过他的名字获取
-
-
logger 通过
org.slf4j.LoggerFactory
类的静态方法getLogger
去获取
有效等级
-
logger呗分为不同等级TRACE, DEBUG, INFO, WARN, ERROR,在
ch.qos.logback.classic.Level
类之中,被 final修饰,不能被继承-
level 定义简直就是一门艺术, 好的定义应该遵循以下原则:
-
debug:完整详细的记录流程的关键路径. 应该用于开发人员比较感兴趣的跟踪和调试信息, 生产环境中正常都不会打开debug状态
-
info:应该简洁明确让管理员确认状态。记录相当重要的,对于最终用户和系统管理员有意义的消息。关键系统参数的回显、后台服务的初始化状态、需要系统管理员知会确认的关键信息都需要使用INFO级别
-
warn:能清楚的告知所有人发生了什么情况.能引起人的重视,指示潜在问题,但不一定需要处理。
-
error:系统出现了异常或不期望出现的问题,希望及时得到关注的处理。需要注意的一个点,不是所有的异常都需要记录成error。
-
-
一般使用Marker对象
对于一个给定的名为 L 的 logger,它的有效层级为从自身往父级一直回溯到root logger,直到找到第一个不为空的层级作为自己的层级。
-
root logger有一个默认层级 DEBUG
方法打印的规则
基本选择规则
日志的打印级别为 p,Logger 实例的级别为 q,如果 p >= q,则该条日志可以打印出来。
各级别的排序为:TRACE < DEBUG < INFO < WARN < ERROR。
-
在下面的表格中,第一列表示的是日志的打印级别,用 p 表示。第一行表示的是 logger 的有效级别,用 q 表示。行列交叉处的结果表示由基本选择规则得出的结果。
Logger获取
通过
LoggerFactory.getLogger()
可以获取到具体的 logger 实例,名字相同则返回的 logger 实例也相同。父级 logger 总是优于子级 logger,并且父级 logger 会自动寻找并关联子级 logger,即使父级 logger 在子级 logger 之后实例化。
类的全限定名来对 logger 进行命名
Appender 与 Layout
logback 允许日志在多个地方进行输出
输出目的地叫做 appender。appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中
一个logger可以有多个appender
-
logger 通过
addAppender
方法来新增一个 appender
appender的层级叠加性
logger L 的日志输出语句会遍历 L 和它的父级中所有的 appender。
这就是所谓的 appender 叠加性(appender additivity)
-
appender 从 logger 的层级结构中去继承叠加性。
-
例如
-
root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来
-
L 的 logger 添加了一个 file appender,那么 L 以及 L 的子级 logger 都可以在文件和控制台打印日志
-
-
可以通过设置 additivity = false 来改写默认的设置,这样 appender 将不再具有叠加性。
-
L的上级P设置了additivity=false,那么L则会在L和P之间的appender输出,包括L和P本身
-
Layout
Layout 日志格式化
定义日志的输出格式,通过给appender添加layout
appender 的作用是将格式化后的日志输出到指定的目的地
-
例:PatternLayout 通过格式化串 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 会将日志格式化成如下结果:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一个参数表示程序启动以来的耗时,单位为毫秒。第二个参数表示当前的线程号。第三个参数表示当前日志的级别。第四个参数是 logger 的名字。“-” 之后是具体的日志信息。
Logback的过滤器
Regular过滤器
继承Filter抽象类,本质上由一个decide()方法组成,接收Event实例作为参数,返回状态
DENY:丢弃
NEUTRAL:继续处理
ACCEPT:跳过剩下过滤器,直接处理
TurboFilter
继承TurboFilter抽象类,同样是主要依赖于decide()方法,返回状态
不同点在于TurboFilter对象被绑定在Logger context中,它会在event生成之前进行过滤,对于一些一定会被过滤的记录节省了创建Event对象的资源
Logback底层实现
第一步:获取过滤器链
TurboFilter过滤器存在则 调用
设置一个上下文阈值或者根据相关日志请求信息
Marker, Level, Logger, 消息,Throwable 来过滤某些事件
-
根据响应类型
-
FilterReply.DENY:丢弃该日志请求
-
FilterReply.NEUTRAL:执行下一步
-
FilterRerply.ACCEPT:跳到第三步
-
第二步:应用基本选择规则
比较有效级别和日志请求的级别
-
日志请求被禁止,请求禁止则丢弃该日志请求
-
否则进行下一步
第三步:创建一个LoggingEvent对象
日志通过过滤器,logback会创建一个ch.qos.logback.classic.LoggingEvent 对象
该对象包含日志请求所有相关的参数
-
请求的 logger
-
日志请求的级别
-
日志信息
-
与日志一同传递的异常信息
-
当前时间
-
当前线程
-
以及当前类的各种信息
-
MDC
第四步:调用appender
创建LoggingEvent对象之后调用可用的
appender的doAppend()方法
appender继承了AppenderBase抽象类,同时实现了doAppend()方法(线程安全),同时调用自定义过滤器。
第五步:格式化输出
调用的Appender格式化LoggingEvent,部分会委托给Layout将LoggingEvent实例格式化为字符串返回。
-
部分Appender(例如 SocketAppender),会将LoggingEvent进行序列化。因此它们没有也不需要Layout
第六步:发送LoggingEvent
当日志事件完全格式化之后,通过每个appender发送到具体的目的地。