日志增强版
- 一、需求
- 二、引入依赖
- 三、配置日志处理切面
- 四、配置RequestWrapper
- 五、效果展示
一、需求
需要打印请求参数和返回参数
二、引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
三、配置日志处理切面
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Order(Integer.MAX_VALUE)
@Aspect
@Component
@Slf4j
public class WebLogAspect {/*** 拦截所有Controller*/@Pointcut("execution(* org.example..controller..*.*(..))")public void cutController() {}@Around("cutController()")public Object traceMethod(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();Date requestStartTime = new Date();String requyestUrl = request.getRequestURL().toString();String requestParam = JSONUtil.toJsonStr(request.getParameterMap());String traceId = MDC.get(GlobalConstants.TRACE_ID);String requestBody;String requestHeader;if (request instanceof RequestWrapper) {RequestWrapper wrapper = (RequestWrapper) request;requestBody = wrapper.getRequestBodyStr();requestHeader = JSONUtil.toJsonStr(wrapper.getHeaders());} else {requestBody = "";requestHeader = "";}String contentType = request.getHeader("content-type");Object returnVal = null;Exception nowException = null;try {returnVal = proceedingJoinPoint.proceed();} catch (Exception e) {nowException = e;throw nowException;} finally {Exception finalNowException = nowException;Object finalReturnVal = returnVal;CompletableFuture.runAsync(() -> {Date requestEndTime = new Date();Map<String, Object> param = new LinkedHashMap<>();param.put("requestTime", DateUtil.formatDateTime(requestStartTime));param.put("requestUrl", requyestUrl);param.put("requestTimeUse", requestEndTime.getTime() - requestStartTime.getTime());param.put("requestParam", requestParam);param.put("requestHeader", requestHeader);param.put("traceId", traceId);if (StringUtils.isBlank(contentType) || !contentType.contains("multipart")) {// 文件上传相关不记录param.put("requestBody", requestBody);}param.put("isException", false);param.put("isBusinessException", false);if (finalNowException == null) {String requestResult = finalReturnVal != null ? JSONUtil.toJsonStr(finalReturnVal) : "null";if (requestResult.contains("异常") && !requestResult.contains("0000")) {param.put("isException", true);param.put("exceptionInfo", "请求出现异常但被捕获,请到对应服务器查看异常日志!");} else {param.put("requestResult", requestResult);}} else {param.put("isException", true);param.put("exceptionInfo", ExceptionUtils.getStackTrace(finalNowException));}log.debug("==========报文参数[{}]",JSONUtil.toJsonStr(param));});}return returnVal;}}
四、配置RequestWrapper
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {private final String charset = "UTF-8";//参数字节数组@Getterprivate byte[] requestBody;//Http请求对象private final HttpServletRequest request;//请求参数private final Map<String, String[]> params = new HashMap<>();//请求headerprivate final Map<String, String> header = new HashMap<>();private final String contentType;public RequestWrapper(HttpServletRequest request) {super(request);this.request = request;this.contentType = this.getHeader("content-type");this.initParams();}private void initParams() {Map<String, String[]> parameterMap = request.getParameterMap(); //接受客户端的数据if (MapUtils.isNotEmpty(parameterMap)) {for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {String name = entry.getKey();String[] values = entry.getValue();if (values == null) {params.put(name, new String[]{});continue;}String[] newvalues = new String[values.length];for (int i = 0; i < values.length; i++) {String value = values[i];newvalues[i] = convert(value);}params.put(name, newvalues);}return;} else {parseFormUrlencodedParameters();}}/*** @return* @throws IOException*/@Overridepublic ServletInputStream getInputStream() throws IOException {/*** 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中* 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题*/if (null == this.requestBody) {ByteArrayOutputStream baos = new ByteArrayOutputStream();IOUtils.copy(request.getInputStream(), baos);this.requestBody = baos.toByteArray();}final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);return new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {}@Overridepublic int read() {return bais.read();}};}@SneakyThrowspublic String getRequestBodyStr() {if (requestBody != null && requestBody.length > 0) {return new String(this.getRequestBody(), charset);} else {try {this.getInputStream();} catch (IOException e) {log.error("", e);}if (requestBody != null && requestBody.length > 0) {return new String(this.getRequestBody(), charset);}}return "";}@Overridepublic Map<String, String[]> getParameterMap() {return this.params;}@Overridepublic String getParameter(String name) {String[] values = params.get(name);return values == null || values.length == 0 ? null : values[0];}@Overridepublic String[] getParameterValues(String key) {return params.get(key);}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}public Map<String, String> getHeaders() {if (MapUtils.isNotEmpty(header)) {return header;}Enumeration<String> headers = this.getHeaderNames();while (headers.hasMoreElements()) {String key = (String) headers.nextElement();String value = this.getHeader(key);header.put(key, value);}return header;}private boolean isContentTypeFormUrlencoded() {if (StringUtils.isNotBlank(contentType) && contentType.contains("x-www-form-urlencoded")) {return true;}return false;}/*** 如果请求类型为[x-www-form-urlencoded],如果body有值,则需要将body的值转到request param中, 由于该类型的特性【如果先获取参数值,那么body则为空】,那么最后将body手动清空*/private void parseFormUrlencodedParameters() {if (!isContentTypeFormUrlencoded()) {return;}if (requestBody == null || requestBody.length == 0) {return;}String bodyStr = this.getRequestBodyStr();if (StringUtils.isBlank(bodyStr)) {return;}String[] paramsArray = bodyStr.split("&");for (String s : paramsArray) {String[] p = s.split("=");if (p.length == 2) {if (StringUtils.isBlank(p[0]) || StringUtils.isBlank(p[1]) || params.containsKey(p[0])) {continue;}params.put(p[0], new String[]{UrlUtil.getURLDecoderString(p[1])});}}//请空body数据requestBody = new byte[]{};}private String convert(String target) {if (StringUtils.isBlank(target)) {return target;}String requestCharset = "ISO-8859-1";//检查请求是否指定编码类型if (StringUtils.isNotBlank(contentType) && contentType.contains("charset") && !contentType.contains("charset ")) {String[] array = contentType.split(";");for (int i = 0; i < array.length; i++) {String[] p = array[i].split("=");if (p.length == 2) {if ("charset".equals(p[0].trim())) {requestCharset = p[1].trim();}}}}try {return new String(target.trim().getBytes(requestCharset), charset);} catch (UnsupportedEncodingException e) {return target;}}
}