SpringBoot技术实践-Slf4j日志框架

2,576 阅读5分钟

一、Slf4j日志框架

1.1 Slf4j日志使用

  1. SpringBoot环境搭建之后将默认集成slf4j日志框架,通过下面方式获取
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RestController
public class LogInfoController {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogInfoController.class);
}
  1. 日志记录有5个级别,优先级由低到高
    • logger.trace("trace 消息");
    • logger.debug("debug 消息");
    • logger.info("info 消息");
    • logger.warn("warn 消息");
    • logger.error("error 消息");
  2. SpringBoot底层已经实现了框架,直接使用即可
  3. SpringBoot默认输出info级以上级别的消息,如果是main则是debug及以上日志
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogUserCore {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogUserCore.class);

    public static void main(String[] args) {
        LOGGER.trace("trace日志");
        LOGGER.debug("debug日志");
        LOGGER.info("info日志,可以使用括号作为占位符,内容【{}】", "info日志");
        LOGGER.warn("warn日志");
        try {
            Integer.parseInt("a");
        } catch (NumberFormatException e) {
            /// LOGGER.error("error日志,错误信息【{}】,最后面可以直接放异常", e.getMessage(), e);
            LOGGER.error("error日志,错误信息【{}】,最后面可以直接放异常", e.getMessage());
        }
    }
}

  1. logback整体类图如下

image.png

1.2 Logback配置说明

1.2.1 configuration

  • configuration是logback的父节点,它只有三个属性
    • scan:自动加载判断,当配置文件发生改变时,将会被重新加载,默认为true
    • scanPeriod:检测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认为毫秒,当scan=true时这个值生效,默认时间间隔为1分钟,可以配置单位
    • debug:当被设置为true时,将打印出logback内部日志信息,实时查看logback运行信息,默认为false

1.2.2 logger

  1. logger用来设置某一个包或者某一个具体类的日志打印级别以及方式
  2. 在logger内可以包含零个或者多个appender-ref,然后对应的appender将会被添加到这个logger
  3. 它的属性如下
    • name:指定对应的包路径或者类路径
    • level:用来设置日志打印的级别,五个常用打印级别从低至高依次为TRACE、DEBUG、INFO、WARN、ERROR,如果未设置此级别,那么当前logger会继承上级的级别
    • additivity:是否向上级log传递打印信息,默认为true

1.2.3 root

  • root也是logger元素,但它是根logger,所有logger的最上级,只有一个level属性,它的name固定为ROOT

1.2.4 appender

  • appender是configuration的子节点,它的作用是定义日志输出的方式,它有2个必要的属性
    • name:指定当前appender的名称
    • class:指定appender对应的类,控制日志输出方式

1.2.5 encoder

  1. encoder主要负责将日志信息转换成字节数组以及将字节数组写到输出流中
  2. encoder常用转换符有如下

%logger {length}

  • 输出日志的logger名,可有一个整形参数,功能是缩短logger名,设置为0表示只输入logger最右边点符号之后的字符串 | Conversion Pattern | Logger name | Result | | --- | --- | --- | | %logger | mainPackage.sub.sample.Bar | mainPackage.sub.sample.Bar | | %logger{0} | mainPackage.sub.sample.Bar | Bar | | %logger{5} | mainPackage.sub.sample.Bar | m.s.s.Bar | | %logger{10} | mainPackage.sub.sample.Bar | m.s.s.Bar | | %logger{15} | mainPackage.sub.sample.Bar | m.s.sample.Bar | | %logger{16} | mainPackage.sub.sample.Bar | m.sub.sample.Bar | | %logger{26} | mainPackage.sub.sample.Bar | mainPackage.sub.sample.Bar |

%d{pattern}

  • 输出日志的打印日志,模式语法与java.text.SimpleDateFormat 兼容 | Conversion Pattern | Result | | --- | --- | | %d | 2021-06-20 14:06:49,812 | | %date | 2021-06-20 14:06:49,812 | | %date{ISO8601} | 2021-0620 14:06:49,812 | | %date{HH:mm:ss.SSS} | 15:06:49.812 | | %date{dd MMM yyyy ;HH:mm:ss.SSS} | 20 oct. 2006;14:06:49. |

%msg

  • 输出应用程序提供的信息

%level

  • 输出日志级别

%thread

  • 输出产生日志的线程名

%n

  • 输出平台相关的分行符"\n"或者"\r\n"

%ex

  • 输出异常信息

1.2.6 filter

  1. 是appender的子节点,表示对当前给定的日志级别再进行一次过滤,配置方式
<!-- 只打印错误日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
</filter>

1.3 Logback框架配置

  1. 新建logback配置文件,增加下面的配置
  2. 配置文件中也可以针对包指定日志级别,但是通常使用配置文件
logging:
  level:
    com.codecoord.springboot.practice.log.info: error
  1. 可以在配置文件中指定日志配置文件位置,然后在日志配置文件中配置日志等策略,配置解释如下
<?xml version="1.0" encoding="UTF-8"?>
<!-- 是否开启debug和扫描周期 -->
<configuration debug="true" scan="true" scanPeriod="1 seconds">
    <contextName>logback</contextName>
    <!--定义参数,后面可以通过${name}使用-->
    <property name="log_pattern" value="%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n%ex"/>
    <!-- 定义日志路径 -->
    <property name="log_path" value="log"/>
    <!-- 最大历史天数为10天 -->
    <property name="log_file_max_history" value="10"/>
    <!-- 单个日志最大大小 -->
    <property name="log_file_max_size" value="1KB"/>
    <!-- 最大日志文件大小 -->
    <property name="log_file_max_total_size" value="1MB"/>

    <!-- ConsoleAppender 用于在屏幕上输出日志 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 定义了一个过滤器,在指定LEVEL之下的日志输出不会被打印出来 -->
        <!-- 这里定义了ERROR,也就是控制台不会输出比ERROR级别小的日志 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <!--定义控制台输出格式-->
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 基于文件appender,不支持滚动记录 -->
    <appender name="FILE_APPENDER" class="ch.qos.logback.core.FileAppender">
        <file>${log_path}/fileAppender.log</file>
        <append>true</append>
        <!-- 将immediateFlush设置为false以获得更高的日志吞吐量 -->
        <immediateFlush>true</immediateFlush>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 基于时间滚动,注意这个基于时间(日)滚动 -->
    <appender name="ROLLING_FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_path}/timeBasedRollingPolicy.log</file>
        <!-- true:事件追加到现有文件的末尾。false:任何现有文件都会被截断。默认设置为true -->
        <append>true</append>
        <!--定义日志滚动的策略,TimeBasedRollingPolicy:不支持%i自动滚动增加序号 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--定义文件滚动时的文件名的格式-->
            <fileNamePattern>${log_path}/timeBasedRollingPolicy.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 最大历记录 -->
            <maxHistory>60</maxHistory>s
            <!-- 该属性在 1.1.6版本后 才开始支持,总文件大小,需要设置maxHistory属性 -->
            <totalSizeCap>10GB</totalSizeCap>
            <!-- 是否启动时清除历史数据 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 基于时间滚动,根据文件大小滚动,适用于一天会产生多个日志情况,推荐使用 -->
    <appender name="ROLLING_FILE_TIME_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_path}/system/sizeAndTime.log</file>
        <append>true</append>
        <!--定义日志滚动的策略,SizeAndTimeBasedRollingPolicy:支持%i自动滚动增加序号 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--定义文件滚动时的文件名的格式-->
            <fileNamePattern>${log_path}/system/sizeAndTime.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 单个文件最大大小,超过自动轮转,保存60天但是不超过10G -->
            <maxFileSize>1KB</maxFileSize>
            <!-- 最大历记录,通常和totalSizeCap配合使用 -->
            <maxHistory>60</maxHistory>
            <!-- 总文件大小,需要设置maxHistory属性 -->
            <totalSizeCap>10GB</totalSizeCap>
            <!-- 是否启动时清除历史数据 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- root是默认的logger,当logger没有配置appender是使用此处配置的输出 -->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>

    <!-- 对于类路径以com.codecoord.springboot.practice.log.info开头的Logger,输出级别设置为info -->
    <!-- 这个logger没有指定appender,它会继承root节点中定义appender,由于root的appender配置debug级别,此处不输出 -->
    <!--<logger name="com.codecoord.springboot.practice.log.info" level="info"/>-->

    <!-- additivity为false表示不要使用rootLogger配置的appender进行输出 -->
    <!-- 由于设置了additivity="false",所以输出时不会使用rootLogger的appender -->
    <!-- 如果没有设置 additivity="false",可能导致一条日志在控制台输出两次的情况 -->
    <!--<logger name="com.codecoord.springboot.practice.log.info" level="INFO" additivity="false">
        <appender-ref ref="FILE_APPENDER"/>
    </logger>-->

    <!-- 基于时间滚动日志记录 -->
    <logger name="com.codecoord.springboot.practice.log.info" level="INFO" additivity="false">
        <appender-ref ref="ROLLING_FILE_TIME_APPENDER"/>
    </logger>

    <!-- 这个logger本身没有配置appender,配置了additivity="false",所以logger输出日志不会输出到任何地方 -->
    <!--<logger name="com.codecoord.springboot.practice.log.error" level="error" additivity="false"/>-->
</configuration>
  1. 新建几个定时任务,用于定时输出日志,其中一个类输出如下

import java.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class LogDebugSchedule {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogDebugSchedule.class);

    @Scheduled(cron = "0/1 * * * * ?")
    public void logDebug() {
        LOGGER.debug("logDebug任务执行,当前时间【{}】", LocalDateTime.now());
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true" scan="true" scanPeriod="1 seconds">

    <contextName>logback</contextName>
    <!--定义参数,后面可以通过${name}使用-->
    <property name="log_pattern" value="%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n%ex"/>
    <!-- 定义日志路径 -->
    <property name="log_path" value="log"/>
    <!-- 最大历史天数 -->
    <property name="log_file_max_history" value="1"/>
    <!-- 单个日志最大大小 -->
    <property name="log_file_max_size" value="10MB"/>
    <!-- 最大日志文件大小 -->
    <property name="log_file_max_total_size" value="1MB"/>

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 基于时间滚动,根据文件大小滚动,适用于一天会产生多个日志情况 -->
    <appender name="ROLLING_FILE_TIME_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_path}/sizeAndTime.log</file>
        <!-- 追加记录 -->
        <append>true</append>
        <!-- 定义日志滚动的策略,SizeAndTimeBasedRollingPolicy:支持%i自动滚动增加序号 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 定义文件滚动时的文件名的格式-->
            <fileNamePattern>${log_path}/system/sizeAndTime.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 单个文件最大大小,超过自动轮转,保存60天但是不超过10G -->
            <maxFileSize>$log_file_max_size}</maxFileSize>
            <!-- 最大历记录,通常和totalSizeCap配合使用 -->
            <maxHistory>${log_file_max_history}</maxHistory>
            <!-- 总文件大小,需要设置maxHistory属性 -->
            <totalSizeCap>${log_file_max_total_size}</totalSizeCap>
            <!-- 是否启动时清除历史数据 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 日志采集 -->
    <logger name="com.codecoord.springboot.elasticsearch.logstash" level="INFO" additivity="false">
        <appender-ref ref="ROLLING_FILE_TIME_APPENDER"/>
    </logger>

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

1.4 Logback高级配置

  1. logback除了可以使用核心内部变量外,可以通过MDC来实现自定义参数
  2. MDC是一个静态类,Logback实现的MDC用ThreadLocal实现,所以用完之后需要清除MDC数据
package org.slf4j;

public class MDC {
  //Put a context value as identified by key
  //into the current thread's context map.
  public static void put(String key, String val);

  //Get the context identified by the key parameter.
  public static String get(String key);

  //Remove the context identified by the key parameter.
  public static void remove(String key);

  //Clear all entries in the MDC.
  public static void clear();
}
  1. 在代码中通过**MDC.put("key", "value")放入数据,然后在配置文件中通过%X{key}**使用
  2. 代码示例
import java.time.LocalDateTime;
import java.util.Random;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class LogstashLogbackAggregation {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogstashLogbackAggregation.class);
    private static final Random RANDOM = new Random();

    @Scheduled(cron = "0/3 * * * * ?")
    public void logbackMdc() {
        MDC.put("code", UUID.randomUUID().toString());
        MDC.put("createTime", LocalDateTime.now().toString());
        MDC.put("content", "logstash日志采集错误" + LocalDateTime.now());
        LOGGER.info("logstash日志采集,当前时间【{}】", LocalDateTime.now());
        MDC.clear();
        System.out.println(String.format("logstash日志采集,当前时间【%s】", LocalDateTime.now()));
    }
}

  1. logback配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">

    <contextName>logback</contextName>
    <!--定义参数,后面可以通过${name}使用-->
    <property name="log_pattern" value="%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n%ex"/>
    <!-- 定义日志路径 -->
    <property name="log_path" value="log"/>
    <!-- 最大历史天数 -->
    <property name="log_file_max_history" value="1"/>
    <!-- 单个日志最大大小 -->
    <property name="log_file_max_size" value="10MB"/>
    <!-- 最大日志文件大小 -->
    <property name="log_file_max_total_size" value="1GB"/>

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- 基于时间滚动,根据文件大小滚动,适用于一天会产生多个日志情况 -->
    <appender name="ROLLING_FILE_TIME_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_path}/sizeAndTime.log</file>
        <!-- 追加记录 -->
        <append>true</append>
        <!-- 定义日志滚动的策略,SizeAndTimeBasedRollingPolicy:支持%i自动滚动增加序号 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 定义文件滚动时的文件名的格式-->
            <fileNamePattern>${log_path}/system/sizeAndTime.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 单个文件最大大小,超过自动轮转 -->
            <maxFileSize>${log_file_max_size}</maxFileSize>
            <!-- 最大历记录,通常和totalSizeCap配合使用 -->
            <maxHistory>${log_file_max_history}</maxHistory>
            <!-- 总文件大小,需要设置maxHistory属性 -->
            <totalSizeCap>${log_file_max_total_size}</totalSizeCap>
            <!-- 是否启动时清除历史数据 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${log_pattern}</pattern>
        </encoder>
    </appender>

    <!-- MDC -->
    <appender name="ROLLING_FILE_TIME_APPENDER_MDC" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log_path}/mdc.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log_path}/system/mdc.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>${log_file_max_size}</maxFileSize>
            <maxHistory>${log_file_max_history}</maxHistory>
            <totalSizeCap>${log_file_max_total_size}</totalSizeCap>
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} 单号【%X{code}】上传时间【%X{createTime}】内容【%X{content}】 %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志记录器 -->
    <logger name="com.codecoord.springboot.elasticsearch.logstash" level="INFO" additivity="false">
        <appender-ref ref="ROLLING_FILE_TIME_APPENDER_MDC"/>
    </logger>

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

1.5 日志错误处理

Empty watch file list. Disabling

  • 由于配置中开启了扫描,但是无法扫描jar包中的信息,所以保存,把configuration的debug移除即可
    • 问题配置
<configuration debug="true" scan="true" scanPeriod="1 seconds">
  • 调整配置
<configuration scan="true" scanPeriod="30 seconds">