最近接触一个新的传统项目,在联调过程中,查看日志特别不方便,既无trackId,也无接口耗时,如果用户量上来后,完全无法过滤出当前请求的日志,所以写了该博客。话不多说,直接上代码
1、实体类user
package com.yk.domain;
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String sex;
}
2、接口统计返回实体封装类
/**
* @author : yk
* @date : 2024/03/11
* @description : 封装的基础 result
*/
@Data
public class CommonResult<T> implements Serializable {
private Integer code;
private String message;
private String traceId;
private Long costTime;
private T data;
public CommonResult(Integer code) {
this.code = code;
if(code.equals(HttpStatus.OK.value())){
this.message = HttpStatus.OK.name();
}
}
}
2、controller层
@Slf4j
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("/query")
public CommonResult<User> query(@RequestParam(name = "id", required = false, defaultValue = "1") Long id) {
User user = new User();
user.setId(id);
user.setUsername("yk");
user.setSex("男");
CommonResult<User> apiResult = new CommonResult<>(HttpStatus.OK.value());
apiResult.setData(user);
return apiResult;
}
@PostMapping("/queryUser")
public CommonResult<User> queryAlert(@RequestBody User user) {
CommonResult<User> apiResult = new CommonResult<>(HttpStatus.OK.value());
apiResult.setData(user);
return apiResult;
}
}
3、aspect包
public class TraceIdUtil {
public static final String REGEX = "-";
public static final String TRACE_ID = "trace_id";
/**
* 从header和参数中获取traceId
* 从网关传入数据
*
* @param request HttpServletRequest
* @return traceId
*/
public static String getTraceIdByRequest(HttpServletRequest request) {
String traceId = request.getParameter(TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = request.getHeader(TRACE_ID);
}
return traceId;
}
/**
* 传递traceId至MDC
*
* @param traceId 链路id
*/
public static void setTraceId(String traceId) {
if (StringUtils.isNotBlank(traceId)) {
MDC.put(TRACE_ID, traceId);
}
}
/**
* 构建traceId
*
* @return
*/
public static String getTraceId() {
if (StringUtils.isBlank(MDC.get(TRACE_ID))) {
String traceId = UUID.randomUUID().toString().replaceAll(REGEX, StringUtils.EMPTY);
setTraceId(traceId);
return traceId;
}
return MDC.get(TRACE_ID);
}
/**
* 清理traceId
*/
public static void removeTraceId() {
MDC.remove(TRACE_ID);
}
}
package com.yk.aspect;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class MyServletResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream buffer;
private final ServletOutputStream out;
public MyServletResponseWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() {
return out;
}
@Override
public void flushBuffer()
throws IOException {
if (out != null) {
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
static class WrapperOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) {
bos.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
@Getter
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyServletRequestWrapper(HttpServletRequest request) {
super(request);
if (HttpMethod.POST.name().equals(request.getMethod().toUpperCase(Locale.ROOT))) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
} else {
body = request.getQueryString();
}
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
import com.alibaba.fastjson.JSONObject;
import com.yk.domain.CommonResult;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class ContextFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
log.info("=====================ContextFilter init =====================>");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
String traceId = TraceIdUtil.getTraceId();
//为了在post的时候能继续传递
MyServletRequestWrapper request = new MyServletRequestWrapper((HttpServletRequest) servletRequest);
MyServletResponseWrapper response = new MyServletResponseWrapper((HttpServletResponse) servletResponse);
String requestURI = request.getRequestURI();
//消耗时间
long start = System.currentTimeMillis();
log.info("requestUrl={},入参:{} ", requestURI, request.getBody());
String resultParams = "";
try {
// 执行主体方法start==================================================================
chain.doFilter(request, response);
// 执行主体方法 end==================================================================
long end = System.currentTimeMillis();
byte[] content = response.getContent();
if (content.length > 0) {
resultParams = new String(content, StandardCharsets.UTF_8);
}
CommonResult<?> apiResult = JSONObject.parseObject(resultParams, CommonResult.class);
apiResult.setCostTime(end - start);
apiResult.setTraceId(traceId);
//返回消息 否则前台收不到消息
log.info("requestUrl={},出参:{}", requestURI, JSONObject.toJSONString(apiResult));
servletResponse.getOutputStream().write(JSONObject.toJSONString(apiResult).getBytes());
} catch (Exception e) {
CommonResult<?> apiResult = new CommonResult<>(HttpStatus.BAD_REQUEST.value());
apiResult.setCostTime(System.currentTimeMillis() - start);
apiResult.setTraceId(traceId);
if (e.getCause() instanceof IllegalArgumentException) {
log.error("参数化异常 error ", e);
apiResult.setMessage(e.getCause().getLocalizedMessage());
} else {
log.error("请求异常 error ", e);
apiResult.setMessage("系统开小差,请稍后再试");
}
servletResponse.getOutputStream().write(JSONObject.toJSONString(apiResult).getBytes());
} finally {
TraceIdUtil.removeTraceId();
}
}
@Override
public void destroy() {
TraceIdUtil.removeTraceId();
}
}
4、application.properties
server.port=8080
#配置日志全链路跟踪 logId
logging.pattern.console=[%p]%d{yyyy-MM-dd HH:mm:ss.SSS}[%X{trace_id}][%t] %m [%c#%M:%L]%n
5、pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yk</groupId>
<artifactId>springboot-hellword</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-hellword</name>
<description>springboot-hellword</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
postmain请求调用看出参结果
后台日志
[INFO]2024-03-14 11:28:21.389[1e5822679b704344bb6401f64c088eed][] 请求入参id=1 [com.yk.aspect.ContextFilter#doFilter:33]
[INFO]2024-03-14 11:28:21.392[1e5822679b704344bb6401f64c088eed][] 返回值:{"costTime":3,"data":{"sex":"男","id":1,"username":"yk"},"traceId":"1e5822679b704344bb6401f64c088eed"} [com.yk.aspect.ContextFilter#doFilter:50]