logback日志框架的使用

目录

Spring Boot集成slf4j+logback

导入依赖

配置文件

结合使用

日志追踪MDC使用

日志框架

Logback的架构

logback三大核心模块

logback三个主要类

Logger context

有效等级

方法打印的规则

Logger获取

Appender 与 Layout

appender的层级叠加性

Layout

Logback的过滤器

Logback底层实现


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>
    ​

结合使用

  • 设置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, LevelLogger, 消息,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发送到具体的目的地。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值