From da5e162a2ab5c4a428bcdda4c8ea94d52722b7ad Mon Sep 17 00:00:00 2001 From: liquor <958142070@qq.com> Date: Tue, 25 Mar 2025 13:09:06 +0000 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=93=8D=E5=BA=94=E5=8F=AF=E9=87=8D=E5=A4=8D=E8=AF=BB?= =?UTF-8?q?=E6=B5=81=E5=A4=84=E7=90=86=E5=B9=B6=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=A8=A1=E5=9D=97=20=E5=A2=9E=E5=8A=A0=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=9A=E5=8C=85=E6=8B=AC=E5=8F=82=E6=95=B0=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E3=80=81=E8=BF=87=E6=BB=A4=E6=95=8F=E6=84=9F=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=92=8C=E8=B6=85=E9=95=BF=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continew-starter-core/pom.xml | 6 + .../wrapper/RepeatReadRequestWrapper.java | 101 +++++++++++ .../wrapper/RepeatReadResponseWrapper.java | 128 +++++++++++++ .../starter/log/aspect/AccessLogAspect.java | 28 +-- .../starter/log/aspect/LogAspect.java | 2 +- .../autoconfigure/LogAutoConfiguration.java | 15 +- .../starter/log/handler/AopLogHandler.java | 2 - .../starter/log/{ => filter}/LogFilter.java | 54 +++--- .../log/{ => handler}/AbstractLogHandler.java | 48 ++++- .../starter/log/{ => handler}/LogHandler.java | 17 +- .../log/http/RecordableHttpRequest.java | 7 + .../servlet/RecordableServletHttpRequest.java | 18 +- .../RecordableServletHttpResponse.java | 10 +- .../starter/log/model/AccessLogContext.java | 132 ++++++++++++++ .../log/model/AccessLogProperties.java | 171 ++++++++++++++++++ .../starter/log/model/LogProperties.java | 25 ++- .../starter/log/util/AccessLogUtils.java | 118 ++++++++++++ .../autoconfigure/LogAutoConfiguration.java | 4 +- .../log/handler/InterceptorLogHandler.java | 2 - .../log/interceptor/LogInterceptor.java | 24 +-- 20 files changed, 811 insertions(+), 101 deletions(-) create mode 100644 continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java create mode 100644 continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java rename continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/{ => filter}/LogFilter.java (71%) rename continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/{ => handler}/AbstractLogHandler.java (73%) rename continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/{ => handler}/LogHandler.java (86%) create mode 100644 continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogContext.java create mode 100644 continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java create mode 100644 continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/util/AccessLogUtils.java diff --git a/continew-starter-core/pom.xml b/continew-starter-core/pom.xml index 632a5eae..2b58950c 100644 --- a/continew-starter-core/pom.xml +++ b/continew-starter-core/pom.xml @@ -23,6 +23,12 @@ spring-boot-configuration-processor + + + jakarta.servlet + jakarta.servlet-api + + org.hibernate.validator diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java new file mode 100644 index 00000000..937847c1 --- /dev/null +++ b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.core.wrapper; + +import cn.hutool.core.io.IoUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 可重复读取请求体的包装器 + * 支持文件流直接透传,非文件流可重复读取 + * + * @author echo + * @since 2025/03/25 11:11 + **/ +public class RepeatReadRequestWrapper extends HttpServletRequestWrapper { + + private byte[] cachedBody; + private final HttpServletRequest originalRequest; + + public RepeatReadRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + this.originalRequest = request; + + // 判断是否为文件上传请求 + if (!isMultipartContent(request)) { + this.cachedBody = IoUtil.readBytes(request.getInputStream(), false); + } + } + + /** + * 检查是否为文件上传请求 + */ + private boolean isMultipartContent(HttpServletRequest request) { + return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/"); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 如果是文件上传,直接返回原始输入流 + if (isMultipartContent(originalRequest)) { + return originalRequest.getInputStream(); + } + + // 非文件上传,返回可重复读取的输入流 + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + + return new ServletInputStream() { + @Override + public boolean isFinished() { + return byteArrayInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + // 非阻塞I/O,这里可以根据需要实现 + } + + @Override + public int read() { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() throws IOException { + // 如果是文件上传,直接返回原始Reader + if (isMultipartContent(originalRequest)) { + return originalRequest.getReader(); + } + return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody), StandardCharsets.UTF_8)); + } +} diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java new file mode 100644 index 00000000..786f9462 --- /dev/null +++ b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.core.wrapper; + +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; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +/** + * 可重复读取响应内容的包装器 + * 支持缓存响应内容,便于日志记录和后续处理 (不缓存SSE) + * + * @author echo + * @since 2025/03/25 11:11 + **/ +public class RepeatReadResponseWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream cachedOutputStream = new ByteArrayOutputStream(); + private final PrintWriter writer = new PrintWriter(cachedOutputStream, true); + + // 是否为流式响应 + private boolean isStreamingResponse = false; + + public RepeatReadResponseWrapper(HttpServletResponse response) { + super(response); + checkStreamingResponse(); + } + + @Override + public void setContentType(String type) { + super.setContentType(type); + // 根据 Content-Type 判断是否为流式响应 + if (type != null) { + String lowerType = type.toLowerCase(); + isStreamingResponse = lowerType.contains("text/event-stream"); + } + } + + private void checkStreamingResponse() { + String contentType = getContentType(); + if (contentType != null) { + String lowerType = contentType.toLowerCase(); + isStreamingResponse = lowerType.contains("text/event-stream"); + } + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + checkStreamingResponse(); + if (isStreamingResponse) { + // 对于 SSE 流式响应,直接返回原始响应流,不做额外处理 + return super.getOutputStream(); + } + return new ServletOutputStream() { + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + } + + @Override + public void write(int b) throws IOException { + cachedOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + cachedOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + cachedOutputStream.write(b, off, len); + } + }; + } + + @Override + public PrintWriter getWriter() throws IOException { + checkStreamingResponse(); + if (isStreamingResponse) { + // 对于 SSE 流式响应,直接返回原始响应写入器,不做额外处理 + return super.getWriter(); + } + return writer; + } + + public String getResponseContent() { + if (!isStreamingResponse) { + writer.flush(); + return cachedOutputStream.toString(StandardCharsets.UTF_8); + } + return null; + } + + public void copyBodyToResponse() throws IOException { + if (!isStreamingResponse && cachedOutputStream.size() > 0) { + getResponse().getOutputStream().write(cachedOutputStream.toByteArray()); + } + } + + public boolean isStreamingResponse() { + return isStreamingResponse; + } +} diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java index 86f7f1a0..34ab4728 100644 --- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java +++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java @@ -26,9 +26,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import top.continew.starter.log.handler.LogHandler; +import top.continew.starter.log.http.servlet.RecordableServletHttpRequest; +import top.continew.starter.log.http.servlet.RecordableServletHttpResponse; +import top.continew.starter.log.model.AccessLogContext; import top.continew.starter.log.model.LogProperties; -import java.time.Duration; import java.time.Instant; /** @@ -43,9 +46,11 @@ public class AccessLogAspect { private static final Logger log = LoggerFactory.getLogger(AccessLogAspect.class); private final LogProperties logProperties; + private final LogHandler logHandler; - public AccessLogAspect(LogProperties logProperties) { + public AccessLogAspect(LogProperties logProperties, LogHandler logHandler) { this.logProperties = logProperties; + this.logHandler = logHandler; } /** @@ -108,19 +113,18 @@ public class AccessLogAspect { HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); try { - // 打印请求日志 - if (Boolean.TRUE.equals(logProperties.getIsPrint())) { - log.info("[{}] {}", request.getMethod(), request.getRequestURI()); - } + logHandler.processAccessLogStartReq(AccessLogContext.builder() + .startTime(startTime) + .request(new RecordableServletHttpRequest(request)) + .properties(logProperties) + .build()); return joinPoint.proceed(); } finally { Instant endTime = Instant.now(); - if (Boolean.TRUE.equals(logProperties.getIsPrint())) { - Duration timeTaken = Duration.between(startTime, endTime); - log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response != null - ? response.getStatus() - : "N/A", timeTaken.toMillis()); - } + logHandler.processAccessLogEndReq(AccessLogContext.builder() + .endTime(endTime) + .response(new RecordableServletHttpResponse(response, response.getStatus())) + .build()); } } } diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java index d9c84299..149ea807 100644 --- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java +++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java @@ -32,7 +32,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import top.continew.starter.log.annotation.Log; import top.continew.starter.log.dao.LogDao; -import top.continew.starter.log.LogHandler; +import top.continew.starter.log.handler.LogHandler; import top.continew.starter.log.model.LogProperties; import top.continew.starter.log.model.LogRecord; import top.continew.starter.web.util.SpringWebUtils; diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java index dc71abec..00498a90 100644 --- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java +++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java @@ -29,9 +29,9 @@ import top.continew.starter.log.aspect.AccessLogAspect; import top.continew.starter.log.aspect.LogAspect; import top.continew.starter.log.dao.LogDao; import top.continew.starter.log.dao.impl.DefaultLogDaoImpl; +import top.continew.starter.log.filter.LogFilter; import top.continew.starter.log.handler.AopLogHandler; -import top.continew.starter.log.LogFilter; -import top.continew.starter.log.LogHandler; +import top.continew.starter.log.handler.LogHandler; import top.continew.starter.log.model.LogProperties; /** @@ -49,9 +49,11 @@ public class LogAutoConfiguration { private static final Logger log = LoggerFactory.getLogger(LogAutoConfiguration.class); private final LogProperties logProperties; + private final LogHandler logHandler; - public LogAutoConfiguration(LogProperties logProperties) { + public LogAutoConfiguration(LogProperties logProperties, LogHandler logHandler) { this.logProperties = logProperties; + this.logHandler = logHandler; } /** @@ -66,13 +68,12 @@ public class LogAutoConfiguration { /** * 日志切面 * - * @param logHandler 日志处理器 - * @param logDao 日志持久层接口 + * @param logDao 日志持久层接口 * @return {@link LogAspect } */ @Bean @ConditionalOnMissingBean - public LogAspect logAspect(LogHandler logHandler, LogDao logDao) { + public LogAspect logAspect(LogDao logDao) { return new LogAspect(logProperties, logHandler, logDao); } @@ -84,7 +85,7 @@ public class LogAutoConfiguration { @Bean @ConditionalOnMissingBean public AccessLogAspect accessLogAspect() { - return new AccessLogAspect(logProperties); + return new AccessLogAspect(logProperties, logHandler); } /** diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java index 203b3926..a01810ba 100644 --- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java +++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java @@ -16,8 +16,6 @@ package top.continew.starter.log.handler; -import top.continew.starter.log.AbstractLogHandler; - /** * 日志处理器-AOP 版实现 * diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java similarity index 71% rename from continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java rename to continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java index b585a1b3..acd74244 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package top.continew.starter.log; +package top.continew.starter.log.filter; import cn.hutool.extra.spring.SpringUtil; import jakarta.servlet.FilterChain; @@ -25,15 +25,13 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.core.Ordered; import org.springframework.lang.NonNull; 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 top.continew.starter.core.wrapper.RepeatReadRequestWrapper; +import top.continew.starter.core.wrapper.RepeatReadResponseWrapper; import top.continew.starter.log.model.LogProperties; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Objects; /** * 日志过滤器 @@ -67,20 +65,24 @@ public class LogFilter extends OncePerRequestFilter implements Ordered { filterChain.doFilter(request, response); return; } + boolean isMatch = logProperties.isMatch(request.getRequestURI()); - // 包装输入流,可重复读取 - if (!isMatch && this.isRequestWrapper(request)) { - request = new ContentCachingRequestWrapper(request); - } - // 包装输出流,可重复读取 - boolean isResponseWrapper = !isMatch && this.isResponseWrapper(response); - if (isResponseWrapper) { - response = new ContentCachingResponseWrapper(response); - } - filterChain.doFilter(request, response); - // 更新响应(不操作这一步,会导致接口响应空白) - if (isResponseWrapper) { - this.updateResponse(response); + + // 处理可重复读取的请求 + HttpServletRequest requestWrapper = (isMatch || !this.isRequestWrapper(request)) + ? request + : new RepeatReadRequestWrapper(request); + + // 处理可重复读取的响应 + HttpServletResponse responseWrapper = (isMatch || !this.isResponseWrapper(response)) + ? response + : new RepeatReadResponseWrapper(response); + + filterChain.doFilter(requestWrapper, responseWrapper); + + // 如果响应被包装了,复制缓存数据到原始响应 + if (responseWrapper instanceof RepeatReadResponseWrapper wrappedResponse) { + wrappedResponse.copyBodyToResponse(); } } @@ -121,7 +123,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered { * @return true:是;false:否 */ private boolean isRequestWrapper(HttpServletRequest request) { - return !(request instanceof ContentCachingRequestWrapper); + return !(request instanceof RepeatReadRequestWrapper); } /** @@ -131,18 +133,6 @@ public class LogFilter extends OncePerRequestFilter implements Ordered { * @return true:是;false:否 */ private boolean isResponseWrapper(HttpServletResponse response) { - return !(response instanceof ContentCachingResponseWrapper); - } - - /** - * 更新响应 - * - * @param response 响应对象 - * @throws IOException / - */ - private void updateResponse(HttpServletResponse response) throws IOException { - ContentCachingResponseWrapper responseWrapper = WebUtils - .getNativeResponse(response, ContentCachingResponseWrapper.class); - Objects.requireNonNull(responseWrapper).copyBodyToResponse(); + return !(response instanceof RepeatReadResponseWrapper); } } diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java similarity index 73% rename from continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java rename to continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java index b385719e..9c44571a 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java @@ -14,21 +14,31 @@ * limitations under the License. */ -package top.continew.starter.log; +package top.continew.starter.log.handler; import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.ttl.TransmittableThreadLocal; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import top.continew.starter.log.annotation.Log; import top.continew.starter.log.enums.Include; +import top.continew.starter.log.http.RecordableHttpRequest; +import top.continew.starter.log.http.RecordableHttpResponse; import top.continew.starter.log.http.servlet.RecordableServletHttpRequest; import top.continew.starter.log.http.servlet.RecordableServletHttpResponse; +import top.continew.starter.log.model.AccessLogContext; +import top.continew.starter.log.model.AccessLogProperties; import top.continew.starter.log.model.LogRecord; +import top.continew.starter.log.util.AccessLogUtils; import java.lang.reflect.Method; +import java.time.Duration; import java.time.Instant; import java.util.HashSet; import java.util.Set; @@ -41,6 +51,9 @@ import java.util.Set; */ public abstract class AbstractLogHandler implements LogHandler { + private static final Logger log = LoggerFactory.getLogger(AbstractLogHandler.class); + private final TransmittableThreadLocal logContextThread = new TransmittableThreadLocal<>(); + @Override public LogRecord.Started start(Instant startTime, HttpServletRequest request) { return LogRecord.start(startTime, new RecordableServletHttpRequest(request)); @@ -156,4 +169,37 @@ public abstract class AbstractLogHandler implements LogHandler { includes.removeAll(Set.of(excludeArr)); } } + + @Override + public void processAccessLogStartReq(AccessLogContext accessLogContext) { + AccessLogProperties properties = accessLogContext.getProperties().getAccessLog(); + // 是否需要打印 规则: 是否打印开关 或 放行路径 + if (!properties.isPrint() || accessLogContext.getProperties() + .getAccessLog() + .isMatch(accessLogContext.getRequest().getPath())) { + return; + } + // 构建上下文 + logContextThread.set(accessLogContext); + RecordableHttpRequest request = accessLogContext.getRequest(); + String path = request.getPath(); + String param = AccessLogUtils.getParam(request, properties); + log.info(param != null ? "[Start] [{}] {} {}" : "[Start] [{}] {}", request.getMethod(), path, param); + } + + @Override + public void processAccessLogEndReq(AccessLogContext accessLogContext) { + AccessLogContext logContext = logContextThread.get(); + if (ObjectUtil.isNotEmpty(logContext)) { + try { + RecordableHttpRequest request = logContext.getRequest(); + RecordableHttpResponse response = accessLogContext.getResponse(); + Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime()); + log.info("[End] [{}] {} {} {}ms", request.getMethod(), request.getPath(), response + .getStatus(), timeTaken.toMillis()); + } finally { + logContextThread.remove(); + } + } + } } diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogHandler.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java similarity index 86% rename from continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogHandler.java rename to continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java index 15e0a9f4..8f8d7a23 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogHandler.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package top.continew.starter.log; +package top.continew.starter.log.handler; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import top.continew.starter.log.enums.Include; +import top.continew.starter.log.model.AccessLogContext; import top.continew.starter.log.model.LogRecord; import java.lang.reflect.Method; @@ -97,4 +98,18 @@ public interface LogHandler { * @return 日志包含信息 */ Set getIncludes(Set includes, Method targetMethod, Class targetClass); + + /** + * 处理访问日志开始请求 + * + * @param accessLogContext 访问日志上下文 + */ + void processAccessLogStartReq(AccessLogContext accessLogContext); + + /** + * 处理访问日志 结束请求 + * + * @param accessLogContext 访问日志上下文 + */ + void processAccessLogEndReq(AccessLogContext accessLogContext); } diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpRequest.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpRequest.java index 07ce377d..7e2ab4da 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpRequest.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpRequest.java @@ -71,4 +71,11 @@ public interface RecordableHttpRequest { * @return 请求参数 */ Map getParam(); + + /** + * 获取路径 - 格式 /system/dept + * + * @return {@link String } + */ + String getPath(); } diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpRequest.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpRequest.java index 86c87d06..f2f0e3f7 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpRequest.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpRequest.java @@ -17,13 +17,10 @@ package top.continew.starter.log.http.servlet; import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.extra.servlet.JakartaServletUtil; import cn.hutool.json.JSONUtil; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.UriUtils; -import org.springframework.web.util.WebUtils; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.log.http.RecordableHttpRequest; @@ -80,12 +77,8 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest @Override public String getBody() { - ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); - if (null != wrapper) { - String body = StrUtil.utf8Str(wrapper.getContentAsByteArray()); - return JSONUtil.isTypeJSON(body) ? body : null; - } - return null; + String body = JakartaServletUtil.getBody(request); + return JSONUtil.isTypeJSON(body) ? body : null; } @Override @@ -93,7 +86,12 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest String body = this.getBody(); return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) - : Collections.unmodifiableMap(request.getParameterMap()); + : Collections.unmodifiableMap(JakartaServletUtil.getParamMap(request)); + } + + @Override + public String getPath() { + return request.getRequestURI(); } private StringBuilder appendQueryString(String queryString) { diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpResponse.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpResponse.java index 17a5c931..3c92a39a 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpResponse.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpResponse.java @@ -17,11 +17,9 @@ package top.continew.starter.log.http.servlet; import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.web.util.ContentCachingResponseWrapper; -import org.springframework.web.util.WebUtils; +import top.continew.starter.core.wrapper.RepeatReadResponseWrapper; import top.continew.starter.log.http.RecordableHttpResponse; import top.continew.starter.web.util.ServletUtils; @@ -56,10 +54,8 @@ public final class RecordableServletHttpResponse implements RecordableHttpRespon @Override public String getBody() { - ContentCachingResponseWrapper wrapper = WebUtils - .getNativeResponse(response, ContentCachingResponseWrapper.class); - if (null != wrapper) { - String body = StrUtil.utf8Str(wrapper.getContentAsByteArray()); + if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) { + String body = wrapper.getResponseContent(); return JSONUtil.isTypeJSON(body) ? body : null; } return null; diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogContext.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogContext.java new file mode 100644 index 00000000..57eb963f --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogContext.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.log.model; + +import top.continew.starter.log.http.RecordableHttpRequest; +import top.continew.starter.log.http.RecordableHttpResponse; + +import java.time.Instant; + +/** + * 访问日志上下文 + * + * @author echo + * @since 2.8.3 + */ +public class AccessLogContext { + + /** + * 开始时间 + */ + private final Instant startTime; + + /** + * 结束时间 + */ + private Instant endTime; + + /** + * 请求信息 + */ + private final RecordableHttpRequest request; + + /** + * 响应信息 + */ + private final RecordableHttpResponse response; + + /** + * 配置信息 + */ + private final LogProperties properties; + + private AccessLogContext(Builder builder) { + this.startTime = builder.startTime; + this.endTime = builder.endTime; + this.request = builder.request; + this.response = builder.response; + this.properties = builder.properties; + } + + public Instant getStartTime() { + return startTime; + } + + public Instant getEndTime() { + return endTime; + } + + public RecordableHttpRequest getRequest() { + return request; + } + + public RecordableHttpResponse getResponse() { + return response; + } + + public LogProperties getProperties() { + return properties; + } + + public void setEndTime(Instant endTime) { + this.endTime = endTime; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Instant startTime; + private Instant endTime; + private RecordableHttpRequest request; + private RecordableHttpResponse response; + private LogProperties properties; + + private Builder() { + } + + public Builder startTime(Instant startTime) { + this.startTime = startTime; + return this; + } + + public Builder endTime(Instant endTime) { + this.endTime = endTime; + return this; + } + + public Builder request(RecordableHttpRequest request) { + this.request = request; + return this; + } + + public Builder response(RecordableHttpResponse response) { + this.response = response; + return this; + } + + public Builder properties(LogProperties properties) { + this.properties = properties; + return this; + } + + public AccessLogContext build() { + return new AccessLogContext(this); + } + } +} diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java new file mode 100644 index 00000000..9ee2b697 --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.log.model; + +import top.continew.starter.web.util.SpringWebUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 访问日志输出配置 + * + * @author echo + * @since 2.8.3 + */ +public class AccessLogProperties { + + /** + * 是否打印日志,开启后可打印访问日志(类似于 Nginx access log) + *

+ * 不记录日志也支持开启打印访问日志 + *

+ */ + private boolean isPrint = false; + + /** + * 放行路由 + */ + private List excludePatterns = new ArrayList<>(); + + /** + * 是否记录请求参数(body/query/form) + *

开启后会在日志中输出请求参数

+ */ + private boolean isReqParams = false; + + /** + * 是否自动截断超长参数值(如 base64、大文本) + *

开启后会对超过指定长度的参数值进行截断处理

+ */ + private boolean truncateLongParams = false; + + /** + * 超长参数检测阈值(单位:字符) + *

当参数值长度超过此值时,触发截断规则

+ *

默认:2000

+ */ + private int ultraLongParamThreshold = 2000; + + /** + * 超长参数最大展示长度(单位:字符) + *

当参数超过ultraLongParamThreshold时,强制截断到此长度

+ *

默认:50

+ */ + private int ultraLongParamMaxLength = 50; + + /** + * 截断后追加的后缀符号(如配置 "..." 会让截断内容更直观) + *

建议配置 3-5 个非占宽字符,默认为空不追加

+ */ + private String truncateSuffix = "..."; + + /** + * 是否过滤敏感参数 + *

开启后会对敏感参数进行过滤,默认不过滤

+ */ + private boolean isSensitiveParams = false; + + /** + * 敏感参数字段列表(如:password,token,idCard) + *

支持精确匹配(区分大小写)

+ *

示例值:password,oldPassword

+ */ + private List sensitiveParamList = new ArrayList<>(); + + public boolean isPrint() { + return isPrint; + } + + public void setPrint(boolean print) { + isPrint = print; + } + + public List getExcludePatterns() { + return excludePatterns; + } + + public void setExcludePatterns(List excludePatterns) { + this.excludePatterns = excludePatterns; + } + + public boolean isReqParams() { + return isReqParams; + } + + public void setReqParams(boolean reqParams) { + isReqParams = reqParams; + } + + public boolean isTruncateLongParams() { + return truncateLongParams; + } + + public void setTruncateLongParams(boolean truncateLongParams) { + this.truncateLongParams = truncateLongParams; + } + + public int getUltraLongParamThreshold() { + return ultraLongParamThreshold; + } + + public void setUltraLongParamThreshold(int ultraLongParamThreshold) { + this.ultraLongParamThreshold = ultraLongParamThreshold; + } + + public int getUltraLongParamMaxLength() { + return ultraLongParamMaxLength; + } + + public void setUltraLongParamMaxLength(int ultraLongParamMaxLength) { + this.ultraLongParamMaxLength = ultraLongParamMaxLength; + } + + public String getTruncateSuffix() { + return truncateSuffix; + } + + public void setTruncateSuffix(String truncateSuffix) { + this.truncateSuffix = truncateSuffix; + } + + public boolean isSensitiveParams() { + return isSensitiveParams; + } + + public void setSensitiveParams(boolean sensitiveParams) { + isSensitiveParams = sensitiveParams; + } + + public List getSensitiveParamList() { + return sensitiveParamList; + } + + public void setSensitiveParamList(List sensitiveParamList) { + this.sensitiveParamList = sensitiveParamList; + } + + /** + * 是否匹配放行路由 + * + * @param uri 请求 URI + * @return 是否匹配 + */ + public boolean isMatch(String uri) { + return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(uri, pattern)); + } +} diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogProperties.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogProperties.java index 977e2caf..75e3c1ee 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogProperties.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogProperties.java @@ -17,6 +17,7 @@ package top.continew.starter.log.model; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.log.enums.Include; import top.continew.starter.web.util.SpringWebUtils; @@ -40,12 +41,10 @@ public class LogProperties { private boolean enabled = true; /** - * 是否打印日志,开启后可打印访问日志(类似于 Nginx access log) - *

- * 不记录日志也支持开启打印访问日志 - *

+ * 访问日志配置 */ - private Boolean isPrint = false; + @NestedConfigurationProperty + private AccessLogProperties accessLog = new AccessLogProperties(); /** * 包含信息 @@ -65,14 +64,6 @@ public class LogProperties { this.enabled = enabled; } - public Boolean getIsPrint() { - return isPrint; - } - - public void setIsPrint(Boolean print) { - isPrint = print; - } - public Set getIncludes() { return includes; } @@ -89,6 +80,14 @@ public class LogProperties { this.excludePatterns = excludePatterns; } + public AccessLogProperties getAccessLog() { + return accessLog; + } + + public void setAccessLog(AccessLogProperties accessLog) { + this.accessLog = accessLog; + } + /** * 是否匹配放行路由 * diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/util/AccessLogUtils.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/util/AccessLogUtils.java new file mode 100644 index 00000000..911091ea --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/util/AccessLogUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.log.util; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONUtil; +import top.continew.starter.log.http.RecordableHttpRequest; +import top.continew.starter.log.model.AccessLogProperties; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author echo + * @since 2025/03/25 19:16 + **/ +public class AccessLogUtils { + + public AccessLogUtils() { + } + + /** + * 获取参数信息 + * + * @param request 请求 + * @param properties 属性 + * @return {@link String } + */ + public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) { + // 是否需要输出参数 + if (!properties.isReqParams()) { + return null; + } + + // 参数为空返回空 + Map params = request.getParam(); + if (ObjectUtil.isEmpty(params) || params.isEmpty()) { + return null; + } + + // 是否需要对特定入参脱敏 + if (properties.isSensitiveParams()) { + params = filterSensitiveParams(params, properties.getSensitiveParamList()); + } + + // 是否自动截断超长参数值 + if (properties.isTruncateLongParams()) { + params = truncateLongParams(params, properties.getUltraLongParamThreshold(), properties + .getUltraLongParamMaxLength(), properties.getTruncateSuffix()); + } + return "param:" + JSONUtil.toJsonStr(params); + } + + /** + * 过滤敏感参数 + * + * @param params 参数 Map + * @param sensitiveParams 敏感参数列表 + * @return 处理后的参数 Map + */ + private static Map filterSensitiveParams(Map params, List sensitiveParams) { + if (params == null || params.isEmpty() || sensitiveParams == null || sensitiveParams.isEmpty()) { + return params; + } + + Map filteredParams = new HashMap<>(params); + for (String sensitiveKey : sensitiveParams) { + if (filteredParams.containsKey(sensitiveKey)) { + filteredParams.put(sensitiveKey, "***"); + } + } + return filteredParams; + } + + /** + * 截断超长参数 + * + * @param params 参数 Map + * @param threshold 截断阈值(值长度超过该值才截断) + * @param maxLength 最大长度 + * @param suffix 后缀(如 "...") + * @return 处理后的参数 Map + */ + private static Map truncateLongParams(Map params, + int threshold, + int maxLength, + String suffix) { + if (params == null || params.isEmpty()) { + return params; + } + + Map truncatedParams = new HashMap<>(params); + for (Map.Entry entry : truncatedParams.entrySet()) { + Object value = entry.getValue(); + if (value instanceof String strValue) { + if (strValue.length() > threshold) { + entry.setValue(strValue.substring(0, Math.min(strValue.length(), maxLength)) + suffix); + } + } + } + return truncatedParams; + } +} diff --git a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java index 51992572..d8ad0326 100644 --- a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java +++ b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java @@ -30,8 +30,8 @@ import top.continew.starter.log.annotation.ConditionalOnEnabledLog; import top.continew.starter.log.dao.LogDao; import top.continew.starter.log.dao.impl.DefaultLogDaoImpl; import top.continew.starter.log.handler.InterceptorLogHandler; -import top.continew.starter.log.LogFilter; -import top.continew.starter.log.LogHandler; +import top.continew.starter.log.filter.LogFilter; +import top.continew.starter.log.handler.LogHandler; import top.continew.starter.log.interceptor.LogInterceptor; import top.continew.starter.log.model.LogProperties; diff --git a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/handler/InterceptorLogHandler.java b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/handler/InterceptorLogHandler.java index 1f3a4bdd..954d5fe9 100644 --- a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/handler/InterceptorLogHandler.java +++ b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/handler/InterceptorLogHandler.java @@ -16,8 +16,6 @@ package top.continew.starter.log.handler; -import top.continew.starter.log.AbstractLogHandler; - /** * 日志处理器-拦截器版实现 * diff --git a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/interceptor/LogInterceptor.java b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/interceptor/LogInterceptor.java index 452a7f09..4dcf2fc0 100644 --- a/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/interceptor/LogInterceptor.java +++ b/continew-starter-log/continew-starter-log-interceptor/src/main/java/top/continew/starter/log/interceptor/LogInterceptor.java @@ -27,13 +27,15 @@ import org.springframework.lang.NonNull; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import top.continew.starter.log.annotation.Log; +import top.continew.starter.log.http.servlet.RecordableServletHttpRequest; +import top.continew.starter.log.http.servlet.RecordableServletHttpResponse; +import top.continew.starter.log.model.AccessLogContext; import top.continew.starter.log.model.LogProperties; import top.continew.starter.log.dao.LogDao; -import top.continew.starter.log.LogHandler; +import top.continew.starter.log.handler.LogHandler; import top.continew.starter.log.model.LogRecord; import java.lang.reflect.Method; -import java.time.Duration; import java.time.Instant; /** @@ -62,10 +64,11 @@ public class LogInterceptor implements HandlerInterceptor { @NonNull HttpServletResponse response, @NonNull Object handler) { Instant startTime = Instant.now(); - if (Boolean.TRUE.equals(logProperties.getIsPrint())) { - log.info("[{}] {}", request.getMethod(), request.getRequestURI()); - timeTtl.set(startTime); - } + logHandler.processAccessLogStartReq(AccessLogContext.builder() + .startTime(startTime) + .request(new RecordableServletHttpRequest(request)) + .properties(logProperties) + .build()); // 开始日志记录 if (this.isRequestRecord(handler, request)) { LogRecord.Started startedLogRecord = logHandler.start(startTime, request); @@ -81,11 +84,10 @@ public class LogInterceptor implements HandlerInterceptor { Exception e) { try { Instant endTime = Instant.now(); - if (Boolean.TRUE.equals(logProperties.getIsPrint())) { - Duration timeTaken = Duration.between(timeTtl.get(), endTime); - log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response - .getStatus(), timeTaken.toMillis()); - } + logHandler.processAccessLogEndReq(AccessLogContext.builder() + .endTime(endTime) + .response(new RecordableServletHttpResponse(response, response.getStatus())) + .build()); LogRecord.Started startedLogRecord = logTtl.get(); if (null == startedLogRecord) { return;