坊网站建设,陵水网站建设介绍,乐视网站建设目标,邯郸装修网站建设使用过滤器自动打印接口入参、出参首先要了解一个过滤器OncePerRequestFilter#xff0c;一般使用这个过滤器进行日志打印。 一、OncePerRequestFilter 1)、什么是OncePerRequestFilter 回顾一下 Filter 的工作原理。Filter 可以在 Servlet 执行之前或之后调用。当请求被调度… 使用过滤器自动打印接口入参、出参首先要了解一个过滤器OncePerRequestFilter一般使用这个过滤器进行日志打印。 一、OncePerRequestFilter 1)、什么是OncePerRequestFilter 回顾一下 Filter 的工作原理。Filter 可以在 Servlet 执行之前或之后调用。当请求被调度给一个 Servlet 时RequestDispatcher 可能会将其转发给另一个 Servlet。另一个 Servlet 也有可能使用相同的 Filter。在这种情况下同一个 Filter 会被调用多次。
但是有时需要确保每个请求只调用一次特定的 Filter。一个常见的用例是在使用 Spring Security 时。当请求通过过滤器链Filter Chain时对请求的身份证认证应该只执行一次。
在这种情况下可以继承 OncePerRequestFilter。Spring 保证 OncePerRequestFilter 只对指定请求执行一次。 2)、 OncePerRequestFilter 用法
Component
public class AuthenticationFilter extends OncePerRequestFilter {Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {filterChain.doFilter(request, response);}Overrideprotected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {return true;}
} 定义一个继承了 OncePerRequestFilter 的 AuthenticationFilter Filter 类一般重写的方法是以上两个。 shouldNotFilter
这个方法用于指示是否应该跳过过滤器的执行。默认情况下它返回false表示应该执行过滤器。你可以根据需要重写这个方法根据请求的条件来决定是否执行过滤器。例如可以根据请求的路径或者参数来决定是否执行过滤器。 doFilterInternal
这个方法就是具体的过滤器的逻辑 二、logFilter 具体实现 举两个示例都大同小异 示例一 import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;/*** Title: LogFilter* Package com.hoau.hbdp.framework.server.web.filter* 描述: Controller请求日志过滤器不能使用拦截器实现拦截器不能读取requestBody中参数* author* date* version V1.0*/
Slf4j
Component
public class LogFilter extends OncePerRequestFilter {Logger logger LoggerFactory.getLogger(getClass());/*** 是否记录请求日志*/private boolean needLogRequest true;/*** 是否记录响应日志*/private boolean needLogResponse true;/*** 是否记录header*/private boolean needLogHeader true;/*** 是否记录参数*/private boolean needLogPayload true;/*** 记录的最大payload大小*/private int maxPayloadLength 2*1024*1024;AntPathMatcher antPathMatcher new AntPathMatcher();/*** 不进行过滤的请求pattern*/private ListString excludeUrlPatterns new ArrayListString(Arrays.asList(/health));Overrideprotected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {String url request.getServletPath();boolean matched false;for (String pattern : excludeUrlPatterns) {matched antPathMatcher.match(pattern, url);if (matched) {break;}}return matched;}/*** Same contract as for {code doFilter}, but guaranteed to be* just invoked once per request within a single request thread.* See {link #shouldNotFilterAsyncDispatch()} for details.* pProvides HttpServletRequest and HttpServletResponse arguments instead of the* default ServletRequest and ServletResponse ones.** param request* param response* param filterChain*/Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {Date requestDate new Date();boolean isFirstRequest !isAsyncDispatch(request);//包装缓存requestBody信息HttpServletRequest requestToUse request;if (isNeedLogPayload() isFirstRequest !(request instanceof ContentCachingRequestWrapper)) {requestToUse new ContentCachingRequestWrapper(request, getMaxPayloadLength());}//包装缓存responseBody信息HttpServletResponse responseToUse response;if (isNeedLogPayload() !(response instanceof ContentCachingResponseWrapper)) {responseToUse new ContentCachingResponseWrapper(response);}try {filterChain.doFilter(requestToUse, responseToUse);} finally {//记录请求日志if (isNeedLogRequest()) {logRequest(requestToUse,requestDate);}//记录响应日志if (isNeedLogResponse()) {logResponse(responseToUse);//把从response中读取过的内容重新放回response否则客户端获取不到返回的数据resetResponse(responseToUse);}}}/*** 记录请求日志* param request* param requestDate* author* date*/protected void logRequest(HttpServletRequest request, Date requestDate) throws IOException {String payload isNeedLogPayload() ? getRequestPayload(request) : ;logger.info(createRequestMessage(request, payload,requestDate));}/*** 记录响应日志* param response*/protected void logResponse(HttpServletResponse response) {String payload isNeedLogPayload() ? getResponsePayload(response) : ;logger.info(createResponseMessage(response, payload, new Date()));}/*** 重新将响应参数设置到response中* param response* throws IOException*/protected void resetResponse(HttpServletResponse response) throws IOException {ContentCachingResponseWrapper wrapper WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);if (wrapper ! null) {wrapper.copyBodyToResponse();}}/*** 获取请求体中参数* param request* return*/protected String getRequestPayload(HttpServletRequest request) throws IOException {String payload ;ContentCachingRequestWrapper wrapper WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);if (wrapper ! null) {byte[] buf wrapper.getContentAsByteArray();payload getPayloadFromBuf(buf, wrapper.getCharacterEncoding());}return payload;}/*** 获取响应体中参数* param response* return*/protected String getResponsePayload(HttpServletResponse response) {String payload ;ContentCachingResponseWrapper wrapper WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);if (wrapper ! null) {byte[] buf wrapper.getContentAsByteArray();payload getPayloadFromBuf(buf, wrapper.getCharacterEncoding());}return payload;}/*** 创建请求日志实际需要打印的内容* param request* param payload* param requestDate* return*/protected String createRequestMessage(HttpServletRequest request, String payload, Date requestDate) {StringBuilder msg new StringBuilder();msg.append(Inbound Message\n----------------------------\n);msg.append(Address: ).append(request.getRequestURL()).append(\n);msg.append(HttpMethod: ).append(request.getMethod()).append(\n);
// msg.append(QueryString: ).append(request.getQueryString()).append(\n);
// msg.append(RequestId: ).append(RequestContext.getRequestId()).append(\n);
// msg.append(RequestDate: ).append(DateUtils.convert(requestDate)).append(\n);msg.append(Encoding: ).append(request.getCharacterEncoding()).append(\n);msg.append(Content-Type: ).append(request.getContentType()).append(\n);if (isNeedLogHeader()) {msg.append(Headers: ).append(new ServletServerHttpRequest(request).getHeaders()).append(\n);}if (isNeedLogPayload()) {int length Math.min(payload.length(), getMaxPayloadLength());msg.append(Payload: ).append(payload.substring(0, length)).append(\n);}msg.append(----------------------------------------------);return msg.toString();}/*** 创建响应日志实际需要打印的内容* param response* param payload* param responseDate* return*/protected String createResponseMessage(HttpServletResponse response, String payload, Date responseDate) {StringBuilder msg new StringBuilder();msg.append(Outbound Message\n----------------------------\n);
// msg.append(RequestId: ).append(RequestContext.getRequestId()).append(\n);
// msg.append(ResponseDate: ).append(DateUtils.convert(responseDate)).append(\n);msg.append(Encoding: ).append(response.getCharacterEncoding()).append(\n);msg.append(Content-Type: ).append(response.getContentType()).append(\n);if (isNeedLogHeader()) {msg.append(Headers: ).append(new ServletServerHttpResponse(response).getHeaders()).append(\n);}boolean needLogContentType true;String contentType response.getContentType();
// //excel文件导出的不需要记录
// if (application/octet-stream;charsetUTF-8.equals(contentType)) {
// needLogContentType false;
// }//是JSON格式的才输出needLogContentType StringUtils.isEmpty(contentType) || contentType.toUpperCase().contains(JSON) || contentType.contains(text);if (isNeedLogPayload() needLogContentType) {int length Math.min(payload.length(), getMaxPayloadLength());msg.append(Payload: ).append(payload.substring(0, length)).append(\n);}msg.append(----------------------------------------------);return msg.toString();}/*** 将bytep[]参数转换为字符串用于输出* param buf* param characterEncoding* return*/protected String getPayloadFromBuf(byte[] buf, String characterEncoding) {String payload ;if (buf.length 0) {int length Math.min(buf.length, getMaxPayloadLength());try {payload new String(buf, 0, length, characterEncoding);} catch (UnsupportedEncodingException ex) {logger.error(ex.getMessage(), ex);}}return payload;}public boolean isNeedLogRequest() {return needLogRequest;}public void setNeedLogRequest(boolean needLogRequest) {this.needLogRequest needLogRequest;}public boolean isNeedLogResponse() {return needLogResponse;}public void setNeedLogResponse(boolean needLogResponse) {this.needLogResponse needLogResponse;}public boolean isNeedLogHeader() {return needLogHeader;}public void setNeedLogHeader(boolean needLogHeader) {this.needLogHeader needLogHeader;}public boolean isNeedLogPayload() {return needLogPayload;}public void setNeedLogPayload(boolean needLogPayload) {this.needLogPayload needLogPayload;}public int getMaxPayloadLength() {return maxPayloadLength;}public void setMaxPayloadLength(int maxPayloadLength) {this.maxPayloadLength maxPayloadLength;}public ListString getExcludeUrlPatterns() {return excludeUrlPatterns;}public void setExcludeUrlPatterns(ListString excludeUrlPatterns) {this.excludeUrlPatterns excludeUrlPatterns;}
}
上述代码解释 1、首先请求进入过滤器之后先进入shouldNotFilter方法通过 getServletPath() 获取访问路径然后再根据AntPathMatche类专门用来进行路劲匹配的可以单独了解一下来进行路径匹配看是否是需要进行过滤如果是就返回false 代表执行这个过滤器。 2、然后请求进入doFilterInternal方法先进行一系列判断然后如果需要记录日志就将HttpServletRequest 对象转换为 ContentCachingRequestWrapper 对象转换成ContentCachingRequestWrapper对象是为了缓存请求体内容并允许多次读取和修改。因为HttpServletRequest 对象只能被读取一次读取后的数据就无法再次获取。所以一般都会先转换为ContentCachingRequestWrapper对象类型。 然后经过判断再将HttpServletResponse 转换为ContentCachingResponseWrapper原因同理。 3、最后在finally 中打印 请求体数据和响应体数据 首先 logRequest 方法记录请求日志在logRequest方法中经过判断进 getRequestPayload 方法目的是为了获取请求体中参数在这个方法中用到了一个方法 WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); 这个方法我的理解是为了将HttpServletRequest对象 转换成 ContentCachingRequestWrapper对象 目的在上面也讲了拿到ContentCachingRequestWrapper对象之后就可以根据 wrapper.getContentAsByteArray() 方法获取请求体的字节数据了。再根据 getPayloadFromBuf 方法将字节数据转换成字符串。 获取到请求数据之后再根据 createRequestMessage 方法拼接需要打印的数据即可。 响应数据同理但需要注意的是我们需要使用 wrapper.copyBodyToResponse() 方法重新将响应参数设置到response中不然客户端获取不到响应数据 示例二
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONValidator;
import jdk.nashorn.internal.ir.annotations.Ignore;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;Slf4j
Component
//Order()
public class LogFilter extends OncePerRequestFilter implements Ordered {/*** 配置要记录请求的路径前缀*/private static final String NEED_TRACE_PATH_PREFIX /;/*** 忽略为multipart/form-data的ContentType的请求*/private static final SetString IGNORE_CONTENT_TYPE new HashSet(Arrays.asList(multipart/form-data,application/octet-stream));Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE1 ;}OverrideSuppressWarnings(NullableProblems)protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if (!isRequestValid(request)) {filterChain.doFilter(request, response);return;}RequestWrapper request1new RequestWrapper(request);ResponseWrapper response1new ResponseWrapper(response);int status HttpStatus.INTERNAL_SERVER_ERROR.value();long startTime System.currentTimeMillis();String path request.getRequestURI();try {if (path.startsWith(NEED_TRACE_PATH_PREFIX)) {// 1. 记录日志consoleRequestLog(request1);}} catch (Exception ignore){log.error(请求日志打印异常,ignore);}filterChain.doFilter(request1, response1);status response1.getStatus();try {if (path.startsWith(NEED_TRACE_PATH_PREFIX)) {// 1. 记录日志consoleResponseLog(path, startTime, status, response1);}updateResponse(response1, response);} catch (Exception ignore) {log.error(响应日志输出异常,ignore);}}private Boolean ignoreCheck(String contentType){if(StrUtil.isNotBlank(contentType)) {for (String s : IGNORE_CONTENT_TYPE) {if (contentType.contains(s)){return true;}}}return false;}/*** 输出请求日志* param request*/private synchronized void consoleRequestLog(RequestWrapper request){log.info(请求 | 请求路径:[{}] | 请求方法:[{}] | 请求IP:[{}] | 请求参数:{} | 请求Body:{} ,request.getRequestURI(),request.getMethod(),request.getRemoteAddr(),JSON.toJSONString(request.getParameterMap()),getRequestBody(request));}/*** 获取请求body* param request* return 请求body*/private String getRequestBody(RequestWrapper request) {String requestBody{};
// ContentCachingRequestWrapper wrapper WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
// if (wrapper ! null) {try {
// requestBody new String(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());requestBody request.getBody();if (StrUtil.isNotBlank(requestBody) (JSONValidator.from(requestBody).validate() || (requestBody.startsWith() requestBody.endsWith()))) {return requestBody;} else if (StrUtil.isNotBlank(requestBody)) {return IOStream;} else {return ;}} catch (Exception ignore) {log.error(请求体转换异常,ignore);}
// }return requestBody;}/*** Description: 打印日志* Param: [path - 请求路径, request - Http请求, startTime - 开始毫秒, status - 响应状态码, response - Http响应]*/private synchronized void consoleResponseLog(String path, long startTime, int status, ResponseWrapper response) {log.info(返回 | 处理耗时:[{}ms] | 响应时间:[{}] | 响应状态:[{}] | 响应Body:{} ,System.currentTimeMillis() - startTime,LocalDateTime.now(),status,getResponseBody(response));}/*** Description: 判断请求是否合法* Param: [request]* return: {link boolean}*/private boolean isRequestValid(HttpServletRequest request) {try {new URI(request.getRequestURL().toString());return true;} catch (URISyntaxException ex) {return false;}}/*** Description: 获取响应Body* Param: [response]* return: {link String}*/private String getResponseBody(ResponseWrapper response) {if (Objects.isNull(response.getDataStream())){return ;}String responseBody new String(response.getDataStream());;if(JSONValidator.from(responseBody).validate()||responseBody.startsWith()responseBody.endsWith()) {return responseBody;}else{return FileStream;}}/*** Description: 更新响应* Param: [response]*/private void updateResponse(ResponseWrapper response1,HttpServletResponse response) throws IOException {response.getOutputStream().write(response1.getDataStream());response.getOutputStream().flush();
// ContentCachingResponseWrapper responseWrapper WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
// Objects.requireNonNull(responseWrapper).copyBodyToResponse();}}import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ByteUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
import java.io.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;Slf4j
public class RequestWrapper extends ContentCachingRequestWrapper {private final String MULTIPARTHEADERmultipart/form-data;private byte[] body;private CollectionPart parts;private BufferedReader reader;private ServletInputStream inputStream;public RequestWrapper(HttpServletRequest request) throws IOException, ServletException {super(request);if (StrUtil.isNotBlank(request.getContentType())request.getContentType().contains(MULTIPARTHEADER)){partsrequest.getParts();}else {//读一次 然后缓存起来body IoUtil.readBytes(request.getInputStream());inputStream new RequestCachingInputStream(body);}}Overridepublic CollectionPart getParts() throws IOException, ServletException {return parts;}public String getBody() {try {if (Objects.nonNull(body)body.length0) {return new String(body, getCharacterEncoding());}else{return ;}} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}}Overridepublic ServletInputStream getInputStream() throws IOException {if (inputStream ! null) {return inputStream;}return new RequestCachingInputStream(body);}Overridepublic BufferedReader getReader() throws IOException {if (reader null) {reader new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));}return reader;}private static class RequestCachingInputStream extends ServletInputStream {private final ByteArrayInputStream inputStream;public RequestCachingInputStream(byte[] bytes) {inputStream new ByteArrayInputStream(bytes);}Overridepublic int read() throws IOException {return inputStream.read();}Overridepublic boolean isFinished() {return inputStream.available() 0;}Overridepublic boolean isReady() {return true;}Overridepublic void setReadListener(ReadListener readlistener) {}}
}import org.springframework.web.util.ContentCachingResponseWrapper;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;public class ResponseWrapper extends ContentCachingResponseWrapper {public ResponseWrapper(HttpServletResponse response) {super(response);}public byte[] getDataStream() {return getContentAsByteArray();}
}代码解释 1、首先实现了 Ordered 接口需要实现 getOrder() 方法来指定 Bean 的加载顺序。 2、请求进入之后的逻辑类似先进入 doFilterInternal() 方法先判断请求是否合法如果不合法就不进行日志打印。 3、然后将 HttpServletRequest 转换为 RequestWrapper 对象其实RequestWrapper对象是自定义对象继承了 ContentCachingRequestWrapper 对象。所以实际上还是转换成了 ContentCachingRequestWrapper 对象主要是为了缓存请求数据。 RequestWrapper 的类这个类继承自 ContentCachingRequestWrapper并且用于包装 HttpServletRequest 对象。 主要功能 处理请求体内容 当请求的 Content-Type 是 multipart/form-data 时通过 request.getParts() 获取多部分请求的所有 Part 对象。否则将请求体内容读取一次并缓存起来以便后续的读取。 提供方法获取请求体内容 getBody()返回请求体内容的字符串表示。getInputStream()返回输入流可以用于读取请求体内容。getReader()返回字符流的 BufferedReader 对象可以用于读取请求体内容。 代码解析 构造方法 RequestWrapper(HttpServletRequest request) 根据请求的 Content-Type 来判断是否是 multipart/form-data 类型的请求。如果是 multipart/form-data则调用 request.getParts() 获取所有的 Part 对象。否则读取一次请求体内容并将其缓存在 body 数组中。 getParts() 方法 如果是 multipart/form-data 请求则直接返回之前获取的 parts 集合。否则返回 null。 getBody() 方法 返回请求体内容的字符串表示首先判断 body 数组是否为空然后将其转换为字符串返回。 getInputStream() 方法 如果 inputStream 不为空则直接返回该输入流。否则创建一个新的 RequestCachingInputStream 对象并将之前缓存的 body 数组传入其中。 getReader() 方法 如果 reader 为空则创建一个新的 BufferedReader 对象并使用 inputStream 创建。否则直接返回之前创建的 reader 对象。 RequestCachingInputStream 内部类 继承自 ServletInputStream用于提供一个包装 body 数组的输入流。实现了 read() 方法用于读取字节数据。实现了 isFinished() 方法用于判断输入流是否已经读取完毕。实现了 isReady() 方法始终返回 true。实现了 setReadListener() 方法空实现。 4、缓存完请求对象的内容之后 使用consoleRequestLog方法打印日志在这个方法中它记录了 请求路径:[{}] 、 请求方法:[{}]、请求IP:[{}]、请求参数:{} 、 请求Body:{}request.getParameterMap() 就是专门获取get请求 请求参数的。 其中 请求Body 需要使用 getRequestBody 方法获取这个方法中主要是这个判断需要说一下
if (StrUtil.isNotBlank(requestBody) (JSONValidator.from(requestBody).validate() || (requestBody.startsWith() requestBody.endsWith()))) 这个判断主要是为了 requestBody 不为空时且当请求体是 json数据时或xml数据时才进行打印。
JSONValidator.from(requestBody).validate()使用 JSON 格式验证器验证请求体内容是否是有效的 JSON 格式。(requestBody.startsWith() requestBody.endsWith())检查请求体内容是否以 开头且以 结尾。 响应数据同理 三、MDC MDC即 Mapped Diagnostic Context是 logback 日志框架提供的一种上下文信息存储的机制可以在日志输出中方便地添加和显示额外的上下文信息。 MDC 的作用 MDC 允许你在一个线程中存储一些额外的信息并在该线程执行的任何代码中访问这些信息。这些信息可以是任何与日志记录相关的数据比如用户 ID、请求 ID、会话 ID 等。通过 MDC你可以将这些信息附加到日志消息中使日志更加丰富和有用。 简单说一下使用 MDC 添加日志链路追踪id 和 ip 以及 userAgent
import cn.hutool.core.lang.UUID;import cn.hutool.http.Header;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;Component
WebFilter(filterName logRequestIdFilter, urlPatterns /*)
Order(Integer.MIN_VALUE)
public class LogRequestIdFilter implements Filter {public static final String TRACE_ID traceId;public static final String IP ip;public static final String CLIENT_INFO client_info;Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpRequest (HttpServletRequest) servletRequest;HttpServletResponse httpResponse (HttpServletResponse) servletResponse;String requestId httpRequest.getHeader(TRACE_ID);String userAgent httpRequest.getHeader(Header.USER_AGENT.getValue());String realIp RemortIPUtil.getRealIp(httpRequest);if (requestId null) {requestId UUID.randomUUID().toString();}MDC.put(TRACE_ID, requestId);MDC.put(IP,realIp);MDC.put(CLIENT_INFO,userAgent);httpResponse.setHeader(TRACE_ID, requestId);try {filterChain.doFilter(servletRequest, servletResponse);} finally {MDC.clear();}}
}import lombok.extern.slf4j.Slf4j;import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;Slf4j
public class RemortIPUtil {// 将长Ip截取public static String getRemortIP(HttpServletRequest request) {if (request.getHeader(x-forwarded-for) null) {return request.getRemoteAddr();}// 获得反向代理IPString ip request.getHeader(x-forwarded-for);if (null!ip!ip) {if (ip.indexOf(,) -1) {ip ip.substring(0, ip.indexOf(,));}}return ip;}public static String getRemortIPLong(HttpServletRequest request) {String ip request.getHeader(x-forwarded-for);if(ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(Proxy-Client-IP);}if(ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(WL-Proxy-Client-IP);}if(ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getRemoteAddr();}return ip;}public static String getRealIp(HttpServletRequest req) {String regex ([0-9]{1,3}.){3}[0-9]{1,3};String ip req.getRemoteAddr();if(null ! req.getHeader(HTTP_CDN_SRC_IP) Pattern.matches(regex, req.getHeader(HTTP_CDN_SRC_IP))) {ip req.getHeader(HTTP_CDN_SRC_IP);}else if(null ! req.getHeader(X-FORWARDED-FOR) Pattern.matches(regex, req.getHeader(X-FORWARDED-FOR))) {ip req.getHeader(X-FORWARDED-FOR);}else if(null ! req.getHeader(X-REAL-IP) Pattern.matches(regex, req.getHeader(X-REAL-IP))) {ip req.getHeader(X-REAL-IP);}else if(null ! req.getHeader(HTTP_X_REAL_FORWARDED_FOR) Pattern.matches(regex, req.getHeader(HTTP_X_REAL_FORWARDED_FOR))) {ip req.getHeader(HTTP_X_REAL_FORWARDED_FOR);}else if(null ! req.getHeader(HTTP_X_FORWARDED_FOR) Pattern.matches(regex, req.getHeader(HTTP_X_FORWARDED_FOR))) {ip req.getHeader(HTTP_X_FORWARDED_FOR);}else if(null ! req.getHeader(HTTP_X_REAL_IP) Pattern.matches(regex, req.getHeader(HTTP_X_REAL_IP))) {ip req.getHeader(HTTP_X_REAL_IP);}else if(null ! req.getHeader(HTTP_CLIENT_IP) Pattern.matches(regex, req.getHeader(HTTP_CLIENT_IP))) {ip req.getHeader(HTTP_CLIENT_IP);}return ip;}public static Long ip2Long(String ip) {String regex ([0-9]{1,3}.){3}[0-9]{1,3};if(Pattern.matches(regex, ip)) {String[] ips ip.split(\\.);Long v Long.valueOf(ips[0]);Long v1 Long.valueOf(ips[1]);Long v2 Long.valueOf(ips[2]);Long v3 Long.valueOf(ips[3]);return (v 24) (v1 16) (v2 8) v3;}return null;}}将获取到的 TRACE_ID、User-Agent 和 realIp 放入 MDC 中TRACE_ID用于标识请求的唯一标识符。从请求头中获取如果请求头中没有则生成一个随机的 UUID。User-Agent客户端的用户代理信息通常是浏览器的相关信息。realIp真实的客户端 IP 地址可能会通过代理等方式隐藏。 在MDC中设置了之后 我们在logback日志的配置文件中可以是使用下面的方式直接引用
%X{ip}%X{traceId} 在配置logback日志配置文件时在需要的地方引入即可。这样每个日志打印时都会有 traceId 和 ip 显示了。