最近接触一个新的传统项目,在联调过程中,查看日志特别不方便,既无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;}
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<>();apiResult.setData(user);return apiResult;}@PostMapping("/queryUser")public CommonResult<User> queryAlert(@RequestBody User user) {CommonResult<User> apiResult = new CommonResult<>();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);}@Overridepublic ServletOutputStream getOutputStream() {return out;}@Overridepublic 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;}@Overridepublic void write(int b) {bos.write(b);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {}}
}
@Getter
public class MyServletRequestWrapper extends HttpServletRequestWrapper {private final String body;public MyServletRequestWrapper(HttpServletRequest request) {super(request);if ("POST".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();}}@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());return new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() {return byteArrayInputStream.read();}};}@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) {log.info("=====================ContextFilter init =====================>");}@Overridepublic 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);//消耗时间long start = System.currentTimeMillis();log.info("请求入参{} ", request.getBody());// 执行主体方法start==================================================================chain.doFilter(request, response);// 执行主体方法 end==================================================================long end = System.currentTimeMillis();byte[] content = response.getContent();String resultParams = "";if (content.length > 0) {resultParams = new String(content, StandardCharsets.UTF_8);}try {CommonResult<?> apiResult = JSONObject.parseObject(resultParams, CommonResult.class);apiResult.setCostTime(end - start);apiResult.setTraceId(traceId);//返回消息 否则前台收不到消息log.info("返回值:{}", JSONObject.toJSONString(apiResult));servletResponse.getOutputStream().write(JSONObject.toJSONString(apiResult).getBytes());} catch (Exception e) {log.info("ContextFilter error ", e);servletResponse.getOutputStream().write(resultParams.getBytes());}}@Overridepublic 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}][%X{extra_biz_info}] %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>