LogBack日志笔记

467 阅读2分钟

Spring默认的Logback日志记录

日志级别:

  • fatal(严重错误,可能导致系统崩溃)
  • error(错误信息,不会影响影响系统运行)
  • warn(错误警告,可能会发生问题)
  • info(系统运行的信息,数据连接、网络连接等)
  • debug(调试信息、一般在开发中使用)
  • tarce(追踪信息,记录程序的所有流程信息)

Spring Boot 默认的日志打印

  1. 默认的配置文件bean.xml,定义了默认的root为控制台输出>=INFO级别日志。
  2. 默认情况下SpringBoot将日志输出在控制台,不会写入到日志文件中,我们可以在application.properties或者application.yml中进行配置,但是只可以配置简单的场景、保存路径、日志格式等,对于复杂的场景只能自定义配置。
<?xml version="1.0" encoding="UTF-8"?>
<!--
Base logback configuration provided for compatibility with Spring Boot 1.1
-->
<included>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
	<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
	<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
	<root level="INFO">
		<appender-ref ref="CONSOLE" />
		<appender-ref ref="FILE" />
	</root>
</included>

自定义logback配置文件

不同的日志系统按照以下的命名规则就可以被SpringBoot加载

  1. Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
  2. Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
  3. Log4j2:log4j2-spring.xml, log4j2.xml
  4. JDK (Java Util Logging):logging.properties

注:Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置,放在 src/main/resources 下面即可,logback.xml加载早于application.yml

logback-spring.xml详解

根节点<configuration>

<configuration scan="true" scanPeriod="60 seconds" debug="false">  
    <!-- 其他配置省略-->  
</configuration>
  1. scan : 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  2. scanPeriod : 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  3. debug : 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

子节点<property>

<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
  1. 用来定义变量值的标签,有两个属性,name,value。
  2. name:指的是变量的名称
  3. value:指的是变量的值
  4. 定义好的变量值会插入到logger的上下文中,定义变量之后通过${}来读取变量

注:多环境配置下,通过 application.yml 传递参数过来,< property >取不到环境参数,得用< springProperty >。

<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="springBoot"/>

子节点<appender>

  1. appender用来格式化日志输出节点,有两个属性name和class。
  2. name:指定appender的名称
  3. class:指定日志是那种输出策略,常用的是控制台和文件输出策略。

日志输出策略

  1. ConsoleAppender:日志信息打印到控制台上,更加准确的说:使用System.out或者System.err方式输出。主要子标签有:encoder,target。
  2. FileAppender:用于将日志信息输出到文件中,主要子标签有:append,encoder,file
  3. RollingFileAppender:RollingFileAppender继承FileAppender类,能够动态的创建一个文件。也就是说:到满足一定的条件,就会创建一个新的文件,然后将日志写入到新的文件中。主要子标签:file,append,encoder,rollingPolicytriggerPolicy
  4. AsyncAppender:直接将日志写入文件。每次日志输出到文件都会进行一次磁盘IO,在多应用的时候这种效果会导致一定的线程运行延迟,所以可以采用异步的方式处理。,在使用logback的时候,推荐使用AsyncAppender异步记录日志。
queueSize:阻塞队列的最大容量。默认情况下,queueSize设置为256discardingThreshold:默认情况下,当阻塞队列剩余20%的容量时,它将丢弃级别跟踪、调试和信息事件,只保留级别警告和错误事件。要保留所有事件,请将discardingThreshold设置为0neverBlock:如果为false(默认值),则追加程序将阻止追加到完整队列,而不是丢失消息。设置为true时,附加程序只会丢弃消息,不会阻止您的应用程序。
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%thread] [%logger{50}] >>> %msg%n</pattern>
    </encoder>
  </appender>
 
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <discardingThreshold>0</discardingThreshold>
    <queueSize>512</queueSize>
    <appender-ref ref="FILE"/>
  </appender>
 
  <root level="DEBUG">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>

注: AsyncAppender异步记录ILoggingEvents,它仅充当事件分派器,因此必须引用另一个appender才能执行任何有用的操作。


子节点:< loger>

  1. 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定appender。
  2. 仅有一个name属性,一个可选的level和一个可选的addtivity属性。
  3. name:用来指定受此loger约束的某一个包或者具体的某一个类。
  4. level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF。
  5. addtivity:是否向上级loger传递打印信息。默认是true。

loger在实际使用的时候有两种情况

  • 带有loger的配置,不指定级别,不指定appender
<logger name="com.xf.controller.TestLogController"/>

作用: 将TestLogController类的日志的打印,并没有设置打印级别,所以继承它的上级的日志级别“INFO”;没有设置addtivity,默认为true,将此loger的打印信息向上级传递;没有设置appender,此loger本身不打印任何信息。
解释: < root level=“INFO”>将root的打印级别设置为“INFO”,指定了名字为“console”的appender。当执行TestLogController类的testLog方法时,将级别为“INFO”及大于“INFO”的日志信息传递给root,本身并不打印;root接到下级传递的信息,交给已经配置好的名为“console”的appender处理,“console” appender 将信息打印到控制台;

  • 带有多个loger的配置,指定级别,指定appender
<configuration>
    <logger name="com.xf.controller.TestLogController" level="WARN" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

作用: 控制com.xf.controller.TestLogController类的日志打印,打印级别为“WARN”;additivity属性为false,表示此loger的打印信息不再向上级传递;指定了名字为“console”的appender;
解释: 执行TestLogController类的testLog方法时,将级别为“WARN”及大于“WARN”的日志信息交给此loger指定的名为“console”的appender处理,在控制台中打出日志,不再向上级root传递打印信息。\

注:当然如果你把additivity="false"改成additivity="true"的话,就会打印两次,因为打印信息向上级传递,logger本身打印一次,root接到后又打印一次。


子节点:< root >

  1. root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。level默认是DEBUG。
  2. level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF。
  3. 可以包含零个或多个元素,标识这个appender将会添加到这个loger。
<root level="debug">
  <appender-ref ref="console" />
  <appender-ref ref="file" />
</root>

标签介绍:

file:

被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。


target

设置为System.out还是System.err方式输出。默认值为System.out。


append:

如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。


encoder:

主要工作:1、将一个event事件转换成一组byte数组,2、将转换后的字节数据输出到文件中。

几个重要的encode:

1.PatternLayoutEncoder:patternLayoutEncoder类既有layout将一个事件转化为字符串,又有将字符串写入到文件中的作用,它是encoder标签的默认类实例。

<encoder>
    <pattern>
        %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %X{requestId} %logger{10} [%file:%line] %msg%n
    </pattern>
    <charset>utf8</charset>
</encoder>

filter:

logback具有过滤器支持。logbcak允许给日志记录器appender配置一个或多个Filter(或者给整体配置一个或多个TurboFilter),来控制:当满足过滤器指定的条件时,才记录日志(或不满足条件时,拒绝记录日志)。logback支持自定义过滤器,当然logback也自带了一些常用的过滤器,在绝大多数时候,自带的过滤器其实就够用了,一般是不需要自定义过滤器的。

logback提供的过滤器支持主要分两大类:

  1. ch.qos.logback.core.filter.TurboFilter ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志。
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>ERROR</level>
</filter>
  1. ch.qos.logback.classic.turbo.LevelFilter 如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高,所以我们使用下面的策略,可以避免输出 Error 的日志。
<filter class="ch.qos.logback.classic.filter.LevelFilter">
    <!--过滤 Error-->
    <level>ERROR</level>
    <!--匹配到就禁止-->
    <onMatch>DENY</onMatch>
    <!--没有匹配到就允许-->
    <onMismatch>ACCEPT</onMismatch>
</filter>

3.FilterReply有三种枚举值:

  • DENY:表示不用看后面的过滤器了,这里就给拒绝了,不作记录。
  • NEUTRAL:表示需不需要记录,还需要看后面的过滤器。若所有过滤器返回的全部都是NEUTRAL,那么需要记录日志。
  • ACCEPT:表示不用看后面的过滤器了,这里就给直接同意了,需要记录。

Filter与TurboFilter自带的几种常用过滤器:

  1. LevelFilter:来源于Filter,对指定level的日志进行记录(或不记录),对不等于指定level的日志不记录(或进行记录),常用。
  2. ThresholdFilter:来源于Filter,对大于或等于指定level的日志进行记录(或不记录),对小于指定level的日志不记录(或进行记录) 提示:info级别是大于debug的,常用
  3. EvaluatorFilter:来源Filter,对满足指定表达式的日志进行记录(或不记录),对不满足指定表达式的日志不作记录(或进行记录),常用
  4. MDCFilter:来源TurboFilter,若MDC域中存在指定的key-value,则进行记录,否者不作记录,常用 注:TurboFilter的性能是优于Filter的,这是因为TurboFilter的作用时机是在创建日志事件ILoggingEvent对象之前,而Filter的作用时机是在创建之后。若一个日志注定是会被过滤掉不记录的,那么创建ILoggingEvent对象(包括后续的参数组装方法调用等)这个步骤无疑是非常消耗性能的。

rollingPolicy:

  • TimeBasedRollingPolicy:它根据时间来制定滚动策略.时间滚动策略可以基于时间滚动按时间生成日志。 这个策略出现的原因就是对时间滚动策略的一个补充,使其不仅按时间进行生成而且考虑到文件大小的原因,因为在基于时间的滚动策略生成的日志文件,只是对一段时间总的日志大小做了限定,但是没有对每个日志文件的大小做限定,这就会造成个别日志文件过大,后期传递,阅读困难的问题,所以就有了这第二个策略。
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logFile.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover -->
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- keep 30 days' worth of history capped at 3GB total size -->
      <maxHistory>30</maxHistory>
      <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender> 

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>
  • SizeAndTimeBasedRollingPolicy:基于大小和时间的滚动策略,这个策略的出现,我个人猜测是因为需要日志文件保持为某个特定的数量,防止滚动测策略导致过多的日志文件出现。这个策略出现得配合triggeringPolicy,给一个什么时候日志滚动一次的控制,这部分是跟上面两种策略所不一样的地方。
<configuration>
  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>mylog.txt</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <!-- rollover daily -->
      <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
       <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
       <maxFileSize>100MB</maxFileSize>    
       <maxHistory>60</maxHistory>
       <totalSizeCap>20GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="ROLLING" />
  </root>
</configuration>
  • FixedWindowRollingPolicy:基于固定窗口的滚动策略
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>test.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>tests.%i.log.zip</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>3</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>5MB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
        
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>
  1. fileNamePattern:这是一个强制的标签。他的值可以包含:文件的名称、适当的%d转 换说明符。这个%d说明符可以包含一个【日期和时间】的模式。其中【模式】类似于 【SimpleDateFormat】类。如果这个【模式】没有写的话,默认就是【yyyy-MM-dd】的模式。 转换文件的名称从fileNamePattern中得到.
  2. maxHistory:这是一个可选的标签。以异步方式删除较旧的文件,例如,如果您指定每月滚动,并将maxHistory设置为6,则将保留6个月的归档文件,并删除6个月以上的文件。
  3. totalSizeCap:这是一个可选的标签。这是所有日志文件的总大小空间。当日志文件的空间超过了设置的最大 空间数量,就会删除旧的文件。注意:这个标签必须和maxHistory标签一起使用。 4.cleanHistoryOnStart:如果设置为true,则将在追加程序启动时执行归档删除。默认情况下,此属性设置为false。

多环境配置<springProfile>

<springProfile>标签允许你自由的包含或排除基于激活的Spring profiles的配置的一部分。在元素的任何地方都支持Profile部分。使用name属性来指定哪一个profile接受配置。多个profiles可以用一个逗号分隔的列表来指定。

<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev, staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

配置示例

<?xl versmion="1.0" encoding="utf-8" ?>
<configuration>
    //
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!--    logback-spring.xml 引入配置文件中的变量(如需使配置有效需要将配置写在xml中 否则就是使用defaultValue)
     这里appollo无效除非在代码里面自定义Appender引入appollo -->
    <springProperty scope="context" name="MY_LOG_PATH" source="log.path" defaultValue=""/>
    <springProperty scope="context" name="MY_LOG_LEVEL" source="log.level" defaultValue="INFO"/>
    <property name="LOG_PATH_PRE" value="${MY_LOG_PATH}"/>
    <property name="LOG_LEVEL" value="${MY_LOG_LEVEL}"/>
    <property name="APP_NAME" value="app_name"/>
    <property name="LOG_PATH" value="${LOG_PATH_PRE}/home/admin/${APP_NAME}/logs"/>
    <property name="M_LOG_PATH" value="${LOG_PATH_PRE}/home/admin/${APP_NAME}/monitor/biz_logs"/>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -- [%thread] %-5level %X{requestId} %logger{36}.%M - %msg%n"/>

    <springProfile name="production,stage,testing">

        <appender name="INFO_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/info.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <encoder>
                <pattern>${LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>500MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>10GB</totalSizeCap>
            </rollingPolicy>
        </appender>
        <appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/error.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <encoder>
                <pattern>${LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>500MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>10GB</totalSizeCap>
            </rollingPolicy>
        </appender>
        <appender name="SUNFIRE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${M_LOG_PATH}/sunfire.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${M_LOG_PATH}/sunfire.log.%d{yyyy-MM-dd}.%i</fileNamePattern>
                <maxFileSize>500MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>10GB</totalSizeCap>
            </rollingPolicy>
            <encoder>
                <pattern>${LOG_PATTERN}</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>

        <appender name ="INFO_ASYNC_APPENDER" class= "ch.qos.logback.classic.AsyncAppender">
            <discardingThreshold>0</discardingThreshold>
            <queueSize>512</queueSize>
            <appender-ref ref="INFO_APPENDER"/>
        </appender>
        <appender name ="ERROR_ASYNC_APPENDER" class= "ch.qos.logback.classic.AsyncAppender">
            <discardingThreshold>0</discardingThreshold>
            <queueSize>512</queueSize>
            <appender-ref ref="ERROR_APPENDER"/>
        </appender>
        <appender name ="SUNFIRE_ASYNC_APPENDER" class= "ch.qos.logback.classic.AsyncAppender">
            <discardingThreshold>0</discardingThreshold>
            <queueSize>512</queueSize>
            <appender-ref ref="SUNFIRE_APPENDER"/>
        </appender>

        <logger name="SUNFIRE" level="INFO" additivity="false">
            <appender-ref ref="SUNFIRE_ASYNC_APPENDER"/>
        </logger>

        <root level="${LOG_LEVEL}">
            <appender-ref ref="INFO_ASYNC_APPENDER"/>
            <appender-ref ref="ERROR_ASYNC_APPENDER"/>
        </root>

    </springProfile>

    <springProfile name="dev">
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>
                    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %X{requestId} %logger{10} [%file:%line] %msg%n
                </pattern>
            </encoder>
        </appender>

        <root level="INFO">
            <appender-ref ref="STDOUT"/>
        </root>
    </springProfile>

</configuration>

sunfire工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.Map;

public class SunfireLog {

    private Logger logger = LoggerFactory.getLogger("SUNFIRE");

    private Map<String, Object> tags;

    public static SunfireLog newSunfireLog() {
        return new SunfireLog();
    }

    private SunfireLog() {
        tags = new LinkedHashMap<>();
    }

    public SunfireLog addTag(String key, Object value) {
        tags.put(key, value);
        return this;
    }

    public void info(String msg) {
        logger.info(buildMsg(msg));
    }

    public void info() {
        logger.info(buildMsg("content"));
    }

    private String buildMsg(String msg) {
        StringBuilder tagBuilder = new StringBuilder();
        for (Map.Entry<String, Object> e : tags.entrySet()) {
            tagBuilder.append(e.getKey()).append("=").append(e.getValue()).append(",");
        }
        return tagBuilder.append("content=").append(msg).toString();
    }
}

日志记录Key值枚举

public class SunfireMeta {
    
    public static final String LOG_KEY = "logKey";
    
    public static final String RESULT_TAG = "result";
  
    public static final String RESULT_SUCCESS = "Y";
    
    public static final String RESULT_FAILURE = "N";
    
    public static final String COST_TIME_TAG = "costTime";
}