mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 04:59:21 +08:00
feat(core): 新增请求响应可重复读流处理并优化日志模块
增加访问日志打印处理:包括参数打印、过滤敏感参数和超长参数配置
This commit is contained in:
@@ -23,6 +23,12 @@
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- servlet包 -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hibernate Validator -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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));
|
||||
}
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -16,8 +16,6 @@
|
||||
|
||||
package top.continew.starter.log.handler;
|
||||
|
||||
import top.continew.starter.log.AbstractLogHandler;
|
||||
|
||||
/**
|
||||
* 日志处理器-AOP 版实现
|
||||
*
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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<AccessLogContext> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* 处理访问日志开始请求
|
||||
*
|
||||
* @param accessLogContext 访问日志上下文
|
||||
*/
|
||||
void processAccessLogStartReq(AccessLogContext accessLogContext);
|
||||
|
||||
/**
|
||||
* 处理访问日志 结束请求
|
||||
*
|
||||
* @param accessLogContext 访问日志上下文
|
||||
*/
|
||||
void processAccessLogEndReq(AccessLogContext accessLogContext);
|
||||
}
|
@@ -71,4 +71,11 @@ public interface RecordableHttpRequest {
|
||||
* @return 请求参数
|
||||
*/
|
||||
Map<String, Object> getParam();
|
||||
|
||||
/**
|
||||
* 获取路径 - 格式 /system/dept
|
||||
*
|
||||
* @return {@link String }
|
||||
*/
|
||||
String getPath();
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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;
|
||||
|
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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)
|
||||
* <p>
|
||||
* 不记录日志也支持开启打印访问日志
|
||||
* </p>
|
||||
*/
|
||||
private boolean isPrint = false;
|
||||
|
||||
/**
|
||||
* 放行路由
|
||||
*/
|
||||
private List<String> excludePatterns = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 是否记录请求参数(body/query/form)
|
||||
* <p>开启后会在日志中输出请求参数</p>
|
||||
*/
|
||||
private boolean isReqParams = false;
|
||||
|
||||
/**
|
||||
* 是否自动截断超长参数值(如 base64、大文本)
|
||||
* <p>开启后会对超过指定长度的参数值进行截断处理</p>
|
||||
*/
|
||||
private boolean truncateLongParams = false;
|
||||
|
||||
/**
|
||||
* 超长参数检测阈值(单位:字符)
|
||||
* <p>当参数值长度超过此值时,触发截断规则</p>
|
||||
* <p>默认:2000</p>
|
||||
*/
|
||||
private int ultraLongParamThreshold = 2000;
|
||||
|
||||
/**
|
||||
* 超长参数最大展示长度(单位:字符)
|
||||
* <p>当参数超过ultraLongParamThreshold时,强制截断到此长度</p>
|
||||
* <p>默认:50</p>
|
||||
*/
|
||||
private int ultraLongParamMaxLength = 50;
|
||||
|
||||
/**
|
||||
* 截断后追加的后缀符号(如配置 "..." 会让截断内容更直观)
|
||||
* <p>建议配置 3-5 个非占宽字符,默认为空不追加</p>
|
||||
*/
|
||||
private String truncateSuffix = "...";
|
||||
|
||||
/**
|
||||
* 是否过滤敏感参数
|
||||
* <p>开启后会对敏感参数进行过滤,默认不过滤</p>
|
||||
*/
|
||||
private boolean isSensitiveParams = false;
|
||||
|
||||
/**
|
||||
* 敏感参数字段列表(如:password,token,idCard)
|
||||
* <p>支持精确匹配(区分大小写)</p>
|
||||
* <p>示例值:password,oldPassword</p>
|
||||
*/
|
||||
private List<String> sensitiveParamList = new ArrayList<>();
|
||||
|
||||
public boolean isPrint() {
|
||||
return isPrint;
|
||||
}
|
||||
|
||||
public void setPrint(boolean print) {
|
||||
isPrint = print;
|
||||
}
|
||||
|
||||
public List<String> getExcludePatterns() {
|
||||
return excludePatterns;
|
||||
}
|
||||
|
||||
public void setExcludePatterns(List<String> 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<String> getSensitiveParamList() {
|
||||
return sensitiveParamList;
|
||||
}
|
||||
|
||||
public void setSensitiveParamList(List<String> sensitiveParamList) {
|
||||
this.sensitiveParamList = sensitiveParamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配放行路由
|
||||
*
|
||||
* @param uri 请求 URI
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean isMatch(String uri) {
|
||||
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(uri, pattern));
|
||||
}
|
||||
}
|
@@ -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)
|
||||
* <p>
|
||||
* 不记录日志也支持开启打印访问日志
|
||||
* </p>
|
||||
* 访问日志配置
|
||||
*/
|
||||
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<Include> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配放行路由
|
||||
*
|
||||
|
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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<String, Object> 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<String, Object> filterSensitiveParams(Map<String, Object> params, List<String> sensitiveParams) {
|
||||
if (params == null || params.isEmpty() || sensitiveParams == null || sensitiveParams.isEmpty()) {
|
||||
return params;
|
||||
}
|
||||
|
||||
Map<String, Object> 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<String, Object> truncateLongParams(Map<String, Object> params,
|
||||
int threshold,
|
||||
int maxLength,
|
||||
String suffix) {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return params;
|
||||
}
|
||||
|
||||
Map<String, Object> truncatedParams = new HashMap<>(params);
|
||||
for (Map.Entry<String, Object> 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
|
@@ -16,8 +16,6 @@
|
||||
|
||||
package top.continew.starter.log.handler;
|
||||
|
||||
import top.continew.starter.log.AbstractLogHandler;
|
||||
|
||||
/**
|
||||
* 日志处理器-拦截器版实现
|
||||
*
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user