Springboot集成Log4j2+MDC串联单次请求的日志
技术选型
Springboot项目中有很多日志框架,这里针对最常见的做一下简单的对比。
对比维度 | logback | log4j2 |
---|---|---|
架构 | 同步阻塞IO,异步依赖 AsyncAppender队列 | 原生异步 |
性能表现 | 中,异步吞吐量 ≈8万条/秒 | 高,异步吞吐量 ≈20万条/秒 |
springboot集成 | 开箱即用,无需额外配置 | 需排除 spring-boot-starter-logging并显式引入 log4j2依赖 |
适用场景 | 中小项目、快速开发、日志量 <10万条/秒 | 高并发系统、金融级应用、物联网日志采集 |
稍微有点规模的公司都用log4j2,所以这里以log4j2为例子。
引入依赖
因为spring-web自带logback框架,因此需要移除。
<dependency><!--移除自带的logback--><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency>
使用
引入Slf4j注解
import lombok.extern.slf4j.Slf4j;import org.slf4j.MDC;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;@Slf4j
@Controller
public class BasicController {// http://127.0.0.1:8080/hello?name=lisi@RequestMapping("/hello")@ResponseBodypublic String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {log.info("hello, {}", name);return "Hello " + name;}
}
配置log4j2-spring.xml文件
如果需要自定义日志的格式、输出位置等,可以通过该文件来配置。
该文件需要放在项目的resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN"><Properties><Property name="SYSTEM_NAME">my-test</Property><Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss.SSS}] - [%-5level] - [${SYSTEM_NAME}] - [%X{TRACE_ID}] - [%X{USER_NAME}] - [%c{1}]: %msg%n</Property></Properties><Appenders><Console name="Console" target="SYSTEM_OUT" follow="true"><PatternLayout pattern="${LOG_PATTERN}"/></Console></Appenders><Loggers><Logger name="org.springframework" level="INFO"/><logger name="org.mybatis" level="DEBUG"/><Root level="DEBUG"><AppenderRef ref="Console"/></Root></Loggers>
</Configuration>
如上代码:
Appenders里面配置了日志输出到控制台,
内部PatternLayout 配置了输出的格式。
通过${}从property获取参数,
通过%X{}从MDC获取参数。
配合下方:
import lombok.extern.slf4j.Slf4j;import org.slf4j.MDC;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.UUID;@Slf4j
@Controller
public class BasicController {// http://127.0.0.1:8080/hello?name=lisi@RequestMapping("/hello")@ResponseBodypublic String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {MDC.put("USER_NAME",name);MDC.put("TRACE_ID", String.valueOf(UUID.randomUUID()));log.info("hello, {}", name);log.warn("第二条日志");return "Hello " + name;}
}
如上,项目名,用户名称,traceid等都打印出来了。
MDC底层本质上就是一个ThreadLocal,因此只要是同一个线程,都能从里面获取到所需参数。
最好是在前端请求头传traceid,然后过滤器将该参数放入MDC。
这样打印的日志都会携带该traceid,通过traceid即可查出该请求的全部日志。
思考:
异步或发MQ该如何携带该traceid?