logback + MDC 搭建 springboot 的日志系统

  • Post author:
  • Post category:其他




前言:

日志的作用是什么?调试,问题定位,数据分析。日志很重要,要保证统一的样式,分级别,请求可追溯。



1. springboot 自带的logback

如果你是 ieda 开发工具,并且是maven工程,

可以点开 pom 文件,右键 -> maven -> Show Dependencies ,可以查看整个项目的依赖关系,其中有 logback

logback



2.配置 logback.xml 文件

这个文件配置好放在 resources 目录下即可(不需要其他任何地方的配置),工程可自动识别,仔细阅读文件注解。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--每个logger都关联到logger上下文,默认上下文名称为“default”,用于区分不同应用程序的记录。一旦设置,不能修改 -->
    <contextName>${APP_NAME}</contextName>
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="./logs" />
    <property name="APP_NAME" value="log"/>
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{requestId}]  %-5level %logger{50} - %msg%n"/>

    <!-- 控制台输出 -->
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="fileInfoLog"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.info.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
            <!-- totalSizeCap:该类文件最大空间,超过后会删除而不会管是否在保存时间内 -->
            <totalSizeCap>30MB</totalSizeCap>
            <!--  是否启动时清理过期日志,默认false  -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="fileErrorLog"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.error.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>10</MaxHistory>
            <!-- totalSizeCap:该类文件最大空间,超过后会删除而不会管是否在保存时间内 -->
            <totalSizeCap>8MB</totalSizeCap>
            <!--  是否启动时清理过期日志,默认false  -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="fileWarnLog"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.warn.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>10</MaxHistory>
            <!-- totalSizeCap:该类文件最大空间,超过后会删除而不会管是否在保存时间内 -->
            <totalSizeCap>8MB</totalSizeCap>
            <!--  是否启动时清理过期日志,默认false  -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="fileDebugLog"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${APP_NAME}.debug.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>10</MaxHistory>
            <totalSizeCap>1MB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="consoleLog" />
        <appender-ref ref="fileErrorLog" />
        <appender-ref ref="fileInfoLog"/>
        <appender-ref ref="fileWarnLog" />
        <appender-ref ref="fileDebugLog" />
    </root>
</configuration>


3.写个controller 测试一下

日志记录用的是 lombok 里面的小插件,也可以使用其他的。

@RestController
@Slf4j
public class LogController {
    @GetMapping("/search")
    public Object getInfo(
            @RequestParam(value = "keyword", required = false) String keyword) {
        String error = "这是error级别log";
        log.error("this is error log,{}", error);

        String warn = "这是warn级别log";
        log.warn("this is warn log,{}", warn);

        String info = "这是info级别log";
        log.info("this is info log,{}", info);

        Map<String, String> response = new HashMap<>();
        response.put("requestId", RequestIdUtil.getRequestId());  // 这块下面讲
        response.put("body", "body");

        return response;

    }
}

在服务器上运行以后可以发现在当前目录下生成 log 文件夹,里面有4个级别的log文件,如下

日志级别文件



4.增加请求可追溯功能

在步骤 2 和 3 里面都有一个关键词 requestId,它的作用是给每次请求加一个唯一标识,这样请求在系统流转就会带着这个唯一id打印日志,定位问题和分析很有用。

  1. 主流方式是MDC,有兴趣的可以查一下,这里直接贴代码
  2. 先写一个类用于操作MDC
import org.slf4j.MDC;
import java.util.UUID;


public class RequestIdUtil {
    public static final String REQUEST_ID = "requestId";

    public static void setRequestId() {
        MDC.put(REQUEST_ID, UUID.randomUUID().toString());
    }


    public static String getRequestId() {
        return MDC.get(REQUEST_ID);
    }

    public static void clear() {
        MDC.clear();
    }
}
  1. 再写一个过滤器,用于记录每次请求
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class RequestIdFilter implements Filter {

    private static final String REQUEST_ID_HEADER = "RequestId";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        RequestIdUtil.setRequestId();   // 这行是核心
		HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        String requestId = RequestIdUtil.getRequestId();
        httpResponse.setHeader(REQUEST_ID_HEADER, requestId);  // 把请求id写到响应头里面
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            RequestIdUtil.clear();
        }
    }
}
  1. 测试

看一下步骤 3 的返回结果

{"requestId":"651a0b9b-1691-4fb2-a95f-e37275ee19e9","body":"body"}

查看一下日志内容:

2020-09-14 20:06:39.997 [http-nio-8088-exec-1] [651a0b9b-1691-4fb2-a95f-e37275ee19e9]  INFO  log.test.controller.LogController - this is info log,这是info级别log


5 参考文档

花了 20 分钟写的代码,比较粗糙,想了解更多的可以参考下面文档


  1. springboot超级详细的日志配置(基于logback)

  2. SpringBoot Logback日志配置

  3. 在SpringBoot项目中添加logback的MDC

  4. SpringBoot+MDC实现全链路调用日志跟踪,排查问题更方便



版权声明:本文为qingquanyingyue原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。