阅读 653

MyBatis入坑之配置LogBack

MyBatis配置LogBack

MyBatis官方示例是配置Log4j或者使用控制台输出实现,我在这里学习一下LogBack的使用和配置,原因是Logback是SpringBoot的默认值日志配置,多多少少有一些不同,这里直接上简单的配置信息

  • MyBatis的核心配置文件中的配置

<configuration>     <settings>         <setting name="logPrefix" value="com.hanpang.mapper."/>         <!-- <setting name="日志监控前缀的属性" value="包名前缀对应logback.xml中的配置"/> -->     </settings> </configuration> 复制代码

  • logback.xml配置,设置了com.hanpang.mapper为debug模式,对应上面的前缀

<?xml version="1.0" encoding="UTF-8"?> <configuration>     <!--         CONSOLE :表示当前的日志信息是可以输出到控制台的。     -->     <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">         <encoder>             <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>         </encoder>     </appender>       <logger name="com.hanpang.mapper" level="DEBUG" additivity="false">         <appender-ref ref="Console"/>     </logger>         <!--       level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF      , 默认debug       <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。       -->     <root level="INFO">         <appender-ref ref="Console"/>     </root> </configuration> 复制代码

这样配置就好用了,下面是对LogBack的基础配置


SpringBoot Logback

Logback 是Springboot的默认日志配置,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成:

  • logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制

  • logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J

  • logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与 HTTP 访问相关的功能

1.LogBack关键类

在 logback 里,最重要的三个类分别是

  • Logger 类位于 logback-classic 模块中

  • Appender 位于 logback-core 模块中

  • Layout 位于 logback-core 模块中

Appender 和 Layout 并不关心 Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,日志信息才能被正常打印出来。

2.Logger分层命名规则

为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了一个分层概念。每个 logger 都有一个name,这个 name 的格式与 Java 语言中的包名格式相同。可以直接把一个 class 对象传进 LoggerFactory.getLogger() 方法作为参数的原因。

logger 的 name 格式决定了多个 logger 能够组成一个树状的结构,为了维护这个分层的树状结构,每个 logger 都被绑定到一个 logger 上下文中,这个上下文负责理清各个 logger 之间的关系。

例如, 命名为 com.hanpang 的 logger,是命名为 com.hanpang.logback 的 logger 的父亲,是命名为 com.hanpang.logback.demo 的 logger 的祖先。

在 logger 上下文中,有一个 root logger,作为所有 logger 的祖先,这是 logback 内部维护的一个 logger,并非开发者自定义的 logger,可通过以下方式获得这个 logger :

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); 复制代码

同样,通过 logger 的 name,就能获得对应的其它 logger 实例:

package org.slf4j;  public interface Logger {   // Printing methods:    public void trace(String message);   public void debug(String message);   public void info(String message);    public void warn(String message);    public void error(String message);  } 复制代码

3.日志打印级别

感觉这个地方没有什么好说的,大家都应了解的东西。

logger 有日志打印级别,可以为一个 logger 指定它的日志打印级别。 如果不为一个 logger 指定打印级别,那么它将继承离他最近的一个有指定打印级别的祖先的打印级别。这里有一个容易混淆想不清楚的地方,如果 logger 先找它的父亲,而它的父亲没有指定打印级别,那么它会立即忽略它的父亲,往上继续寻找它爷爷,直到它找到 root logger。因此,也能看出来,要使用 logback, 必须为 root logger 指定日志打印级别

日志打印级别从低级到高级排序的顺序是:TRACE < DEBUG < INFO < WARN < ERROR

如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。

package com.hanpang.logback.demo.universal;   import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /**  * @author pangsir  */ 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.hanpang");         logger.setLevel(Level.INFO);                  Logger barlogger = LoggerFactory.getLogger("com.hanpang.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");     } } //打印结果说明 /** 00:27:19.251 [main] WARN  com.foo - can be printed because WARN > INFO 00:27:19.255 [main] INFO  com.foo.Bar - can be printed because INFO >= INFO */ 复制代码

3.获取Logger

在 logback 中,每个 logger 都是一个单例,调用 LoggerFactory.getLogger 方法时,如果传入的 logger name 相同,获取到的 logger 都是同一个实例。

在为 logger 命名时,用类的全限定类名作为 logger name 是最好的策略,这样能够追踪到每一条日志消息的来源。

4.Appender 和 Layout

在 logback 的世界中,日志信息不仅仅可以打印至 console,也可以打印至文件,甚至输出到网络流中,日志打印的目的地由 Appender 来决定,不同的 Appender 能将日志信息打印到不同的目的地去。

Appender 是绑定在 logger 上的,同时,一个 logger 可以绑定多个 Appender,意味着一条信息可以同时打印到不同的目的地去。例如,常见的做法是,日志信息既输出到控制台,同时也记录到日志文件中,这就需要为 logger 绑定两个不同的 logger。

Appender 是绑定在 logger 上的,而 logger 又有继承关系,因此一个 logger 打印信息时的目的地 Appender 需要参考它的父亲和祖先。在 logback 中,默认情况下,如果一个 logger 打印一条信息,那么这条信息首先会打印至它自己的 Appender,然后打印至它的父亲和父亲以上的祖先的 Appender,但如果它的父亲设置了 additivity = false,那么这个 logger 除了打印至它自己的 Appender 外,只会打印至其父亲的 Appender,因为它的父亲的 additivity 属性置为了 false,开始变得忘祖忘宗了,所以这个 logger 只认它父亲的 Appender;此外,对于这个 logger 的父亲来说,如果父亲的 logger 打印一条信息,那么它只会打印至自己的 Appender中(如果有的话),因为父亲已经忘记了爷爷及爷爷以上的那些父辈了。

打印的日志除了有打印的目的地外,还有日志信息的展示格式。在 logback 中,用 Layout 来代表日志打印格式。比如说,PatternLayout 能够识别以下这条格式:%-4relative [%thread] %-5level %logger{32} - %msg%n 然后打印出来的格式效果是:176 [main] DEBUG com.hanpang.HelloWorld2 - Hello world.

上面这个格式的第一个字段代表从程序启动开始后经过的毫秒数,第二个字段代表打印出这条日志的线程名字,第三个字段代表日志信息的日志打印级别,第四个字段代表 logger name,第五个字段是日志信息,第六个字段仅仅是代表一个换行符。

5.参数打印日志

经常能看到打印日志的时候,使用以下这种方式打印日志:

logger.debug("the message is " + msg + " from " + somebody); 复制代码

这种打印日志的方式有个缺点,就是无论日志级别是什么,程序总要先执行 "the message is " + msg + " from " + somebody 这段字符串的拼接操作。当 logger 设置的日志级别为比 DEBUG 级别更高级别时,DEBUG 级别的信息不回被打印出来的,显然,字符串拼接的操作是不必要的,当要拼接的字符串很大时,这无疑会带来很大的性能白白损耗。

于是,一种改进的打印日志方式被人们发现了:

if(logger.isDebugEnabled()) {    logger.debug("the message is " + msg + " from " + somebody); } 复制代码

这样的方式确实能避免字符串拼接的不必要损耗,但这也不是最好的方法,当日志级别为 DEBUG 时,那么打印这行消息,需要判断两次日志级别。一次是logger.isDebugEnabled(),另一次是 logger.debug() 方法内部也会做的判断。这样也会带来一点点效率问题,如果能找到更好的方法,谁愿意无视白白消耗的效率。

推荐方式:提供占位符的方式,以参数化的方式打印日志,例如上述的语句,可以是这样的写法:

logger.debug("the message {} is from {}", msg, somebody); 复制代码

这样的方式,避免了字符串拼接,也避免了多一次日志级别的判断。

6.LogBack内部执行过程

当应用程序发起一个记录日志的请求,例如 info() 时,logback 的内部运行流程如下所示

  1. 获得过滤器链条

  2. 检查日志级别以决定是否继续打印

  3. 创建一个 LoggingEvent 对象

  4. 调用 Appenders

  5. 进行日志信息格式化

  6. 发送 LoggingEvent 到对应的目的地

7.LogBack配置

logback 提供的配置方式有以下几种:

  • 编程式配置

  • xml 格式

  • groovy 格式

logback 在启动时,根据以下步骤寻找配置文件:

  1. 在 classpath 中寻找 logback-test.xml文件

  2. 如果找不到 logback-test.xml,则在 classpath 中寻找 logback.groovy 文件

  3. 如果找不到 logback.groovy,则在 classpath 中寻找 logback.xml文件

  4. 如果上述的文件都找不到,则 logback 会使用 JDK 的 SPI 机制查找 META-INF/services/ch.qos.logback.classic.spi.Configurator 中的 logback 配置实现类,这个实现类必须实现 Configuration 接口,使用它的实现来进行配置

  5. 如果上述操作都不成功,logback 就会使用它自带的 BasicConfigurator 来配置,并将日志输出到控制台

logback-test.xml 一般用来在测试代码中打日志,如果是 maven 项目,一般把 logback-test.xml 放在 src/test/resources 目录下。maven 打包的时候也不会把这个文件打进 jar 包里。logback 启动的时候解析配置文件大概需要 100 毫秒的时间,

7.1 默认配置

前面有提到默认的配置,由 BasicConfiguator 类配置而成,这个类的配置可以用如下的配置文件来表示:

<configuration>   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     <!-- encoders are assigned the type          ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->     <encoder>       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>     </encoder>   </appender>     <root level="debug">     <appender-ref ref="STDOUT" />   </root> </configuration> 复制代码

如果 logback 在启动时,解析配置文件时,出现了需要警告的信息或者错误信息,那 logback 会自动先打印出自身的状态信息。

(1)可以在配置文件中,指定 configuration 的 debug 属性为 true

<configuration debug="true">     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">         <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder              by default -->         <encoder>             <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n             </pattern>         </encoder>     </appender>       <root level="debug">         <appender-ref ref="STDOUT" />     </root> </configuration> 复制代码

(2)设置执行一个监听进行处理

<configuration>   <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />     ... the rest of the configuration file   </configuration> 复制代码

7.2 配置文件自动热加载

要使配置文件自动重载,需要把 scan 属性设置为 true,默认情况下每分钟才会扫描一次,可以指定扫描间隔:

<configuration scan="true" scanPeriod="30 seconds" >    ... </configuration>  复制代码

注意扫描间隔要加上单位,可用的单位是 milliseconds,seconds,minutes 和 hours。如果只指定了数字,但没有指定单位,这默认单位为 milliseconds。

在 logback 内部,当设置 scan 属性为 true 后,一个叫做 ReconfigureOnChangeFilter 的过滤器就会被牵扯进来,它负责判断是否到了该扫描的时候,以及是否该重新加载配置。Logger 的任何一个打印日志的方法被调用时,都会触发这个过滤器,所以关于这个过滤器的自身的性能问题,变得十分重要。logback 目前采用这样一种机制,当 logger 的调用次数到达一定次数后,才真正让过滤器去做它要做的事情,这个次数默认是 16,而 logback 会在运行时根据调用的频繁度来动态调整这个数目。

8.配置文件格式说明

根节点是 configuration,可包含0个或多个 appender,0个或多个 logger,最多一个 root。

8.1 配置 logger 节点

在配置文件中,logger 的配置在<logger> 标签中配置,<logger> 标签只有一个属性是一定要的,那就是 name,除了 name 属性,还有 level 属性,additivity 属性可以配置,不过它们是可选的。 level 的取值可以是 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF, INHERITED, NULL, 其中 INHERITEDNULL 的作用是一样的,并不是不打印任何日志,而是强制这个 logger 必须从其父辈继承一个日志级别。 additivity 的取值是一个布尔值,true 或者 false。

<logger> 标签下只有一种元素,那就是 <appender-ref>,可以有0个或多个,意味着绑定到这个 logger 上的 Appender。

8.2 配置 root 节点

<root> 标签和 <logger> 标签的配置类似,只不过 <root> 标签只允许一个属性,那就是 level 属性,并且它的取值范围只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF<root> 标签下允许有0个或者多个 <appender-ref>

8.3 配置 appender 节点

<appender> 标签有两个必须填的属性,分别是 name 和 class,class 用来指定具体的实现类。<appender> 标签下可以包含至多一个 <layout>,0个或多个 <encoder>,0个或多个 <filter>,除了这些标签外,<appender> 下可以包含一些类似于 JavaBean 的配置标签。

<layout> 包含了一个必须填写的属性 class,用来指定具体的实现类,不过,如果该实现类的类型是 PatternLayout 时,那么可以不用填写。<layout> 也和 <appender> 一样,可以包含类似于 JavaBean 的配置标签。 <encoder> 标签包含一个必须填写的属性 class,用来指定具体的实现类,如果该类的类型是 PatternLayoutEncoder ,那么 class 属性可以不填。 如果想要往一个 logger 上绑定 appender,则使用以下方式:

<logger name="HELLO" level="debug">     <appender-ref ref="FILE" />     <appender-ref ref="STDOUT" /> </logger> 复制代码

8.4 设置 ContextName

<configuration>   <contextName>myAppName</contextName>   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     <encoder>       <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>     </encoder>   </appender>     <root level="debug">     <appender-ref ref="STDOUT" />   </root> </configuration> 复制代码

8.5 变量替换

在 logback 中,支持以 ${varName} 来引用变量

定义变量可以直接在 logback.xml 中定义变量

<configuration>     <property name="USER_HOME" value="/home/sebastien" />     <appender name="FILE" class="ch.qos.logback.core.FileAppender">     <file>${USER_HOME}/myApp.log</file>     <encoder>       <pattern>%msg%n</pattern>     </encoder>   </appender>     <root level="debug">     <appender-ref ref="FILE" />   </root> </configuration> 复制代码

外部文件也支持 classpath 中的文件

<configuration>     <property resource="resource1.properties" />     <appender name="FILE" class="ch.qos.logback.core.FileAppender">      <file>${USER_HOME}/myApp.log</file>      <encoder>        <pattern>%msg%n</pattern>      </encoder>    </appender>      <root level="debug">      <appender-ref ref="FILE" />    </root> </configuration>


作者:胖先森
链接:https://juejin.cn/post/7026136275514032164


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐