mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 08:57:17 +08:00
refactor(log): 优化访问日志相关配置属性名称
This commit is contained in:
@@ -23,12 +23,6 @@
|
|||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- servlet包 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Hibernate Validator -->
|
<!-- Hibernate Validator -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate.validator</groupId>
|
<groupId>org.hibernate.validator</groupId>
|
||||||
@@ -65,5 +59,12 @@
|
|||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-http</artifactId>
|
<artifactId>hutool-http</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jakarta(原 Javax Servlet) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
@@ -33,8 +33,8 @@ import java.nio.charset.StandardCharsets;
|
|||||||
* 支持文件流直接透传,非文件流可重复读取
|
* 支持文件流直接透传,非文件流可重复读取
|
||||||
*
|
*
|
||||||
* @author echo
|
* @author echo
|
||||||
* @since 2025/03/25 11:11
|
* @since 2.10.0
|
||||||
**/
|
*/
|
||||||
public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
private byte[] cachedBody;
|
private byte[] cachedBody;
|
||||||
@@ -50,13 +50,6 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为文件上传请求
|
|
||||||
*/
|
|
||||||
private boolean isMultipartContent(HttpServletRequest request) {
|
|
||||||
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
public ServletInputStream getInputStream() throws IOException {
|
||||||
// 如果是文件上传,直接返回原始输入流
|
// 如果是文件上传,直接返回原始输入流
|
||||||
@@ -98,4 +91,14 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
}
|
}
|
||||||
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody), StandardCharsets.UTF_8));
|
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody), StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为文件上传请求
|
||||||
|
*
|
||||||
|
* @param request 请求对象
|
||||||
|
* @return 是否为文件上传请求
|
||||||
|
*/
|
||||||
|
private boolean isMultipartContent(HttpServletRequest request) {
|
||||||
|
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,14 +31,16 @@ import java.nio.charset.StandardCharsets;
|
|||||||
* 支持缓存响应内容,便于日志记录和后续处理 (不缓存SSE)
|
* 支持缓存响应内容,便于日志记录和后续处理 (不缓存SSE)
|
||||||
*
|
*
|
||||||
* @author echo
|
* @author echo
|
||||||
* @since 2025/03/25 11:11
|
* @author Charles7c
|
||||||
**/
|
* @since 2.10.0
|
||||||
|
*/
|
||||||
public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
||||||
|
|
||||||
private final ByteArrayOutputStream cachedOutputStream = new ByteArrayOutputStream();
|
private final ByteArrayOutputStream cachedOutputStream = new ByteArrayOutputStream();
|
||||||
private final PrintWriter writer = new PrintWriter(cachedOutputStream, true);
|
private final PrintWriter writer = new PrintWriter(cachedOutputStream, true);
|
||||||
|
/**
|
||||||
// 是否为流式响应
|
* 是否为流式响应
|
||||||
|
*/
|
||||||
private boolean isStreamingResponse = false;
|
private boolean isStreamingResponse = false;
|
||||||
|
|
||||||
public RepeatReadResponseWrapper(HttpServletResponse response) {
|
public RepeatReadResponseWrapper(HttpServletResponse response) {
|
||||||
@@ -67,8 +69,8 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
|||||||
@Override
|
@Override
|
||||||
public ServletOutputStream getOutputStream() throws IOException {
|
public ServletOutputStream getOutputStream() throws IOException {
|
||||||
checkStreamingResponse();
|
checkStreamingResponse();
|
||||||
|
// 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
|
||||||
if (isStreamingResponse) {
|
if (isStreamingResponse) {
|
||||||
// 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
|
|
||||||
return super.getOutputStream();
|
return super.getOutputStream();
|
||||||
}
|
}
|
||||||
return new ServletOutputStream() {
|
return new ServletOutputStream() {
|
||||||
@@ -108,6 +110,11 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
|||||||
return writer;
|
return writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存的响应内容
|
||||||
|
*
|
||||||
|
* @return 缓存的响应内容
|
||||||
|
*/
|
||||||
public String getResponseContent() {
|
public String getResponseContent() {
|
||||||
if (!isStreamingResponse) {
|
if (!isStreamingResponse) {
|
||||||
writer.flush();
|
writer.flush();
|
||||||
@@ -116,12 +123,22 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将缓存的响应内容复制到原始响应中
|
||||||
|
*
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
public void copyBodyToResponse() throws IOException {
|
public void copyBodyToResponse() throws IOException {
|
||||||
if (!isStreamingResponse && cachedOutputStream.size() > 0) {
|
if (!isStreamingResponse && cachedOutputStream.size() > 0) {
|
||||||
getResponse().getOutputStream().write(cachedOutputStream.toByteArray());
|
getResponse().getOutputStream().write(cachedOutputStream.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为流式响应
|
||||||
|
*
|
||||||
|
* @return 是否为流式响应
|
||||||
|
*/
|
||||||
public boolean isStreamingResponse() {
|
public boolean isStreamingResponse() {
|
||||||
return isStreamingResponse;
|
return isStreamingResponse;
|
||||||
}
|
}
|
||||||
|
@@ -22,8 +22,6 @@ import org.aspectj.lang.ProceedingJoinPoint;
|
|||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import top.continew.starter.log.handler.LogHandler;
|
import top.continew.starter.log.handler.LogHandler;
|
||||||
@@ -44,7 +42,6 @@ import java.time.Instant;
|
|||||||
@Aspect
|
@Aspect
|
||||||
public class AccessLogAspect {
|
public class AccessLogAspect {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AccessLogAspect.class);
|
|
||||||
private final LogProperties logProperties;
|
private final LogProperties logProperties;
|
||||||
private final LogHandler logHandler;
|
private final LogHandler logHandler;
|
||||||
|
|
||||||
@@ -113,7 +110,8 @@ public class AccessLogAspect {
|
|||||||
HttpServletRequest request = attributes.getRequest();
|
HttpServletRequest request = attributes.getRequest();
|
||||||
HttpServletResponse response = attributes.getResponse();
|
HttpServletResponse response = attributes.getResponse();
|
||||||
try {
|
try {
|
||||||
logHandler.processAccessLogStartReq(AccessLogContext.builder()
|
// 开始访问日志记录
|
||||||
|
logHandler.accessLogStart(AccessLogContext.builder()
|
||||||
.startTime(startTime)
|
.startTime(startTime)
|
||||||
.request(new RecordableServletHttpRequest(request))
|
.request(new RecordableServletHttpRequest(request))
|
||||||
.properties(logProperties)
|
.properties(logProperties)
|
||||||
@@ -121,7 +119,7 @@ public class AccessLogAspect {
|
|||||||
return joinPoint.proceed();
|
return joinPoint.proceed();
|
||||||
} finally {
|
} finally {
|
||||||
Instant endTime = Instant.now();
|
Instant endTime = Instant.now();
|
||||||
logHandler.processAccessLogEndReq(AccessLogContext.builder()
|
logHandler.accessLogFinish(AccessLogContext.builder()
|
||||||
.endTime(endTime)
|
.endTime(endTime)
|
||||||
.response(new RecordableServletHttpResponse(response, response.getStatus()))
|
.response(new RecordableServletHttpResponse(response, response.getStatus()))
|
||||||
.build());
|
.build());
|
||||||
|
@@ -42,6 +42,7 @@ import java.net.URISyntaxException;
|
|||||||
* @author Venil Noronha(Spring Boot Actuator)
|
* @author Venil Noronha(Spring Boot Actuator)
|
||||||
* @author Madhura Bhave(Spring Boot Actuator)
|
* @author Madhura Bhave(Spring Boot Actuator)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
|
* @author echo
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public class LogFilter extends OncePerRequestFilter implements Ordered {
|
public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||||
@@ -66,15 +67,15 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMatch = logProperties.isMatch(request.getRequestURI());
|
boolean isExcludeUri = logProperties.isMatch(request.getRequestURI());
|
||||||
|
|
||||||
// 处理可重复读取的请求
|
// 处理可重复读取的请求
|
||||||
HttpServletRequest requestWrapper = (isMatch || !this.isRequestWrapper(request))
|
HttpServletRequest requestWrapper = (isExcludeUri || !this.isRequestWrapper(request))
|
||||||
? request
|
? request
|
||||||
: new RepeatReadRequestWrapper(request);
|
: new RepeatReadRequestWrapper(request);
|
||||||
|
|
||||||
// 处理可重复读取的响应
|
// 处理可重复读取的响应
|
||||||
HttpServletResponse responseWrapper = (isMatch || !this.isResponseWrapper(response))
|
HttpServletResponse responseWrapper = (isExcludeUri || !this.isResponseWrapper(response))
|
||||||
? response
|
? response
|
||||||
: new RepeatReadResponseWrapper(response);
|
: new RepeatReadResponseWrapper(response);
|
||||||
|
|
||||||
|
@@ -171,11 +171,10 @@ public abstract class AbstractLogHandler implements LogHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processAccessLogStartReq(AccessLogContext accessLogContext) {
|
public void accessLogStart(AccessLogContext accessLogContext) {
|
||||||
AccessLogProperties properties = accessLogContext.getProperties().getAccessLog();
|
AccessLogProperties properties = accessLogContext.getProperties().getAccessLog();
|
||||||
// 是否需要打印 规则: 是否打印开关 或 放行路径
|
// 是否需要打印 规则: 是否打印开关 或 放行路径
|
||||||
if (!properties.isPrint() || accessLogContext.getProperties()
|
if (!properties.isPrint() || accessLogContext.getProperties()
|
||||||
.getAccessLog()
|
|
||||||
.isMatch(accessLogContext.getRequest().getPath())) {
|
.isMatch(accessLogContext.getRequest().getPath())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -184,22 +183,23 @@ public abstract class AbstractLogHandler implements LogHandler {
|
|||||||
RecordableHttpRequest request = accessLogContext.getRequest();
|
RecordableHttpRequest request = accessLogContext.getRequest();
|
||||||
String path = request.getPath();
|
String path = request.getPath();
|
||||||
String param = AccessLogUtils.getParam(request, properties);
|
String param = AccessLogUtils.getParam(request, properties);
|
||||||
log.info(param != null ? "[Start] [{}] {} {}" : "[Start] [{}] {}", request.getMethod(), path, param);
|
log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", request.getMethod(), path, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processAccessLogEndReq(AccessLogContext accessLogContext) {
|
public void accessLogFinish(AccessLogContext accessLogContext) {
|
||||||
AccessLogContext logContext = logContextThread.get();
|
AccessLogContext logContext = logContextThread.get();
|
||||||
if (ObjectUtil.isNotEmpty(logContext)) {
|
if (ObjectUtil.isEmpty(logContext)) {
|
||||||
try {
|
return;
|
||||||
RecordableHttpRequest request = logContext.getRequest();
|
}
|
||||||
RecordableHttpResponse response = accessLogContext.getResponse();
|
try {
|
||||||
Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime());
|
RecordableHttpRequest request = logContext.getRequest();
|
||||||
log.info("[End] [{}] {} {} {}ms", request.getMethod(), request.getPath(), response
|
RecordableHttpResponse response = accessLogContext.getResponse();
|
||||||
.getStatus(), timeTaken.toMillis());
|
Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime());
|
||||||
} finally {
|
log.info("[End] [{}] {} {} {}ms", request.getMethod(), request.getPath(), response.getStatus(), timeTaken
|
||||||
logContextThread.remove();
|
.toMillis());
|
||||||
}
|
} finally {
|
||||||
|
logContextThread.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ import java.util.Set;
|
|||||||
* 日志处理器
|
* 日志处理器
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
|
* @author echo
|
||||||
* @since 2.8.0
|
* @since 2.8.0
|
||||||
*/
|
*/
|
||||||
public interface LogHandler {
|
public interface LogHandler {
|
||||||
@@ -100,16 +101,18 @@ public interface LogHandler {
|
|||||||
Set<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass);
|
Set<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理访问日志开始请求
|
* 开始访问日志记录
|
||||||
*
|
*
|
||||||
* @param accessLogContext 访问日志上下文
|
* @param accessLogContext 访问日志上下文
|
||||||
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
void processAccessLogStartReq(AccessLogContext accessLogContext);
|
void accessLogStart(AccessLogContext accessLogContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理访问日志 结束请求
|
* 结束访问日志记录
|
||||||
*
|
*
|
||||||
* @param accessLogContext 访问日志上下文
|
* @param accessLogContext 访问日志上下文
|
||||||
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
void processAccessLogEndReq(AccessLogContext accessLogContext);
|
void accessLogFinish(AccessLogContext accessLogContext);
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@ import java.util.Map;
|
|||||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||||
* @author Phillip Webb(Spring Boot Actuator)
|
* @author Phillip Webb(Spring Boot Actuator)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
|
* @author echo
|
||||||
* @see RecordableHttpResponse
|
* @see RecordableHttpResponse
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
@@ -45,11 +46,13 @@ public interface RecordableHttpRequest {
|
|||||||
URI getUrl();
|
URI getUrl();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 IP
|
* 获取路径
|
||||||
|
* <p>/foo/bar</p>
|
||||||
*
|
*
|
||||||
* @return IP
|
* @return 路径
|
||||||
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
String getIp();
|
String getPath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取请求头
|
* 获取请求头
|
||||||
@@ -73,9 +76,9 @@ public interface RecordableHttpRequest {
|
|||||||
Map<String, Object> getParam();
|
Map<String, Object> getParam();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取路径 - 格式 /system/dept
|
* 获取 IP
|
||||||
*
|
*
|
||||||
* @return {@link String }
|
* @return IP
|
||||||
*/
|
*/
|
||||||
String getPath();
|
String getIp();
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,8 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
|
* @author echo
|
||||||
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public final class RecordableServletHttpRequest implements RecordableHttpRequest {
|
public final class RecordableServletHttpRequest implements RecordableHttpRequest {
|
||||||
|
|
||||||
@@ -66,8 +68,8 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIp() {
|
public String getPath() {
|
||||||
return JakartaServletUtil.getClientIP(request);
|
return request.getRequestURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,8 +92,8 @@ public final class RecordableServletHttpRequest implements RecordableHttpRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPath() {
|
public String getIp() {
|
||||||
return request.getRequestURI();
|
return JakartaServletUtil.getClientIP(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder appendQueryString(String queryString) {
|
private StringBuilder appendQueryString(String queryString) {
|
||||||
|
@@ -30,6 +30,8 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
|
* @author echo
|
||||||
|
* @since 1.1.0
|
||||||
*/
|
*/
|
||||||
public final class RecordableServletHttpResponse implements RecordableHttpResponse {
|
public final class RecordableServletHttpResponse implements RecordableHttpResponse {
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@ import java.time.Instant;
|
|||||||
* 访问日志上下文
|
* 访问日志上下文
|
||||||
*
|
*
|
||||||
* @author echo
|
* @author echo
|
||||||
* @since 2.8.3
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
public class AccessLogContext {
|
public class AccessLogContext {
|
||||||
|
|
||||||
@@ -90,7 +90,11 @@ public class AccessLogContext {
|
|||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问日志上下文构建者
|
||||||
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private Instant startTime;
|
private Instant startTime;
|
||||||
private Instant endTime;
|
private Instant endTime;
|
||||||
private RecordableHttpRequest request;
|
private RecordableHttpRequest request;
|
||||||
|
@@ -16,76 +16,71 @@
|
|||||||
|
|
||||||
package top.continew.starter.log.model;
|
package top.continew.starter.log.model;
|
||||||
|
|
||||||
import top.continew.starter.web.util.SpringWebUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 访问日志输出配置
|
* 访问日志配置属性
|
||||||
*
|
*
|
||||||
* @author echo
|
* @author echo
|
||||||
* @since 2.8.3
|
* @author Charles7c
|
||||||
|
* @since 2.10.0
|
||||||
*/
|
*/
|
||||||
public class AccessLogProperties {
|
public class AccessLogProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否打印日志,开启后可打印访问日志(类似于 Nginx access log)
|
* 是否打印访问日志(类似于 Nginx access log)
|
||||||
* <p>
|
* <p>
|
||||||
* 不记录日志也支持开启打印访问日志
|
* 不记录请求日志也支持开启打印访问日志
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
private boolean isPrint = false;
|
private boolean isPrint = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 放行路由
|
* 是否打印请求参数(body/query/form)
|
||||||
|
* <p>开启后,访问日志会打印请求参数</p>
|
||||||
*/
|
*/
|
||||||
private List<String> excludePatterns = new ArrayList<>();
|
private boolean isPrintRequestParam = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否记录请求参数(body/query/form)
|
|
||||||
* <p>开启后会在日志中输出请求参数</p>
|
|
||||||
*/
|
|
||||||
private boolean isReqParams = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否自动截断超长参数值(如 base64、大文本)
|
* 是否自动截断超长参数值(如 base64、大文本)
|
||||||
* <p>开启后会对超过指定长度的参数值进行截断处理</p>
|
* <p>开启后,超过指定长度的参数值将会自动截断处理</p>
|
||||||
*/
|
*/
|
||||||
private boolean truncateLongParams = false;
|
private boolean longParamTruncate = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 超长参数检测阈值(单位:字符)
|
* 超长参数检测阈值(单位:字符)
|
||||||
* <p>当参数值长度超过此值时,触发截断规则</p>
|
* <p>当参数值长度超过此值时,触发截断规则</p>
|
||||||
* <p>默认:2000</p>
|
* <p>默认:2000,仅在 {@link #longParamTruncate} 启用时生效</p>
|
||||||
*/
|
*/
|
||||||
private int ultraLongParamThreshold = 2000;
|
private int longParamThreshold = 2000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 超长参数最大展示长度(单位:字符)
|
* 超长参数最大保留长度(单位:字符)
|
||||||
* <p>当参数超过ultraLongParamThreshold时,强制截断到此长度</p>
|
* <p>当参数超过 {@link #longParamThreshold} 时,强制截断到此长度</p>
|
||||||
* <p>默认:50</p>
|
* <p>默认:50,仅在 {@link #longParamTruncate} 启用时生效</p>
|
||||||
*/
|
*/
|
||||||
private int ultraLongParamMaxLength = 50;
|
private int longParamMaxLength = 50;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 截断后追加的后缀符号(如配置 "..." 会让截断内容更直观)
|
* 截断后追加的后缀符号(如配置 "..." 会让截断内容更直观)
|
||||||
* <p>建议配置 3-5 个非占宽字符,默认为空不追加</p>
|
* <p>建议配置 3-5 个非占宽字符,默认为 ...</p>
|
||||||
|
* <p>仅在 {@link #longParamTruncate} 启用时生效</p>
|
||||||
*/
|
*/
|
||||||
private String truncateSuffix = "...";
|
private String longParamSuffix = "...";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否过滤敏感参数
|
* 是否过滤敏感参数
|
||||||
* <p>开启后会对敏感参数进行过滤,默认不过滤</p>
|
* <p>开启后会对敏感参数进行过滤,默认不过滤</p>
|
||||||
*/
|
*/
|
||||||
private boolean isSensitiveParams = false;
|
private boolean isParamSensitive = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 敏感参数字段列表(如:password,token,idCard)
|
* 敏感参数字段列表(如:password,token,idCard)
|
||||||
* <p>支持精确匹配(区分大小写)</p>
|
* <p>支持精确匹配(区分大小写)</p>
|
||||||
* <p>示例值:password,oldPassword</p>
|
* <p>示例值:password,oldPassword</p>
|
||||||
*/
|
*/
|
||||||
private List<String> sensitiveParamList = new ArrayList<>();
|
private List<String> sensitiveParams = new ArrayList<>();
|
||||||
|
|
||||||
public boolean isPrint() {
|
public boolean isPrint() {
|
||||||
return isPrint;
|
return isPrint;
|
||||||
@@ -95,77 +90,59 @@ public class AccessLogProperties {
|
|||||||
isPrint = print;
|
isPrint = print;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getExcludePatterns() {
|
public boolean isPrintRequestParam() {
|
||||||
return excludePatterns;
|
return isPrintRequestParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExcludePatterns(List<String> excludePatterns) {
|
public void setPrintRequestParam(boolean printRequestParam) {
|
||||||
this.excludePatterns = excludePatterns;
|
isPrintRequestParam = printRequestParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReqParams() {
|
public boolean isLongParamTruncate() {
|
||||||
return isReqParams;
|
return longParamTruncate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReqParams(boolean reqParams) {
|
public void setLongParamTruncate(boolean longParamTruncate) {
|
||||||
isReqParams = reqParams;
|
this.longParamTruncate = longParamTruncate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTruncateLongParams() {
|
public int getLongParamThreshold() {
|
||||||
return truncateLongParams;
|
return longParamThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTruncateLongParams(boolean truncateLongParams) {
|
public void setLongParamThreshold(int longParamThreshold) {
|
||||||
this.truncateLongParams = truncateLongParams;
|
this.longParamThreshold = longParamThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUltraLongParamThreshold() {
|
public int getLongParamMaxLength() {
|
||||||
return ultraLongParamThreshold;
|
return longParamMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUltraLongParamThreshold(int ultraLongParamThreshold) {
|
public void setLongParamMaxLength(int longParamMaxLength) {
|
||||||
this.ultraLongParamThreshold = ultraLongParamThreshold;
|
this.longParamMaxLength = longParamMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUltraLongParamMaxLength() {
|
public String getLongParamSuffix() {
|
||||||
return ultraLongParamMaxLength;
|
return longParamSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUltraLongParamMaxLength(int ultraLongParamMaxLength) {
|
public void setLongParamSuffix(String longParamSuffix) {
|
||||||
this.ultraLongParamMaxLength = ultraLongParamMaxLength;
|
this.longParamSuffix = longParamSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTruncateSuffix() {
|
public boolean isParamSensitive() {
|
||||||
return truncateSuffix;
|
return isParamSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTruncateSuffix(String truncateSuffix) {
|
public void setParamSensitive(boolean paramSensitive) {
|
||||||
this.truncateSuffix = truncateSuffix;
|
isParamSensitive = paramSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSensitiveParams() {
|
public List<String> getSensitiveParams() {
|
||||||
return isSensitiveParams;
|
return sensitiveParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSensitiveParams(boolean sensitiveParams) {
|
public void setSensitiveParams(List<String> sensitiveParams) {
|
||||||
isSensitiveParams = sensitiveParams;
|
this.sensitiveParams = 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,14 +26,14 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 访问日志工具类
|
||||||
|
*
|
||||||
* @author echo
|
* @author echo
|
||||||
* @since 2025/03/25 19:16
|
* @author Charles7c
|
||||||
**/
|
* @since 2.10.0
|
||||||
|
*/
|
||||||
public class AccessLogUtils {
|
public class AccessLogUtils {
|
||||||
|
|
||||||
public AccessLogUtils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取参数信息
|
* 获取参数信息
|
||||||
*
|
*
|
||||||
@@ -42,8 +42,8 @@ public class AccessLogUtils {
|
|||||||
* @return {@link String }
|
* @return {@link String }
|
||||||
*/
|
*/
|
||||||
public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) {
|
public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) {
|
||||||
// 是否需要输出参数
|
// 是否需要打印请求参数
|
||||||
if (!properties.isReqParams()) {
|
if (!properties.isPrintRequestParam()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,16 +54,16 @@ public class AccessLogUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否需要对特定入参脱敏
|
// 是否需要对特定入参脱敏
|
||||||
if (properties.isSensitiveParams()) {
|
if (properties.isParamSensitive()) {
|
||||||
params = filterSensitiveParams(params, properties.getSensitiveParamList());
|
params = filterSensitiveParams(params, properties.getSensitiveParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否自动截断超长参数值
|
// 是否自动截断超长参数值
|
||||||
if (properties.isTruncateLongParams()) {
|
if (properties.isLongParamTruncate()) {
|
||||||
params = truncateLongParams(params, properties.getUltraLongParamThreshold(), properties
|
params = truncateLongParams(params, properties.getLongParamThreshold(), properties
|
||||||
.getUltraLongParamMaxLength(), properties.getTruncateSuffix());
|
.getLongParamMaxLength(), properties.getLongParamSuffix());
|
||||||
}
|
}
|
||||||
return "param:" + JSONUtil.toJsonStr(params);
|
return JSONUtil.toJsonStr(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,9 +80,7 @@ public class AccessLogUtils {
|
|||||||
|
|
||||||
Map<String, Object> filteredParams = new HashMap<>(params);
|
Map<String, Object> filteredParams = new HashMap<>(params);
|
||||||
for (String sensitiveKey : sensitiveParams) {
|
for (String sensitiveKey : sensitiveParams) {
|
||||||
if (filteredParams.containsKey(sensitiveKey)) {
|
filteredParams.computeIfPresent(sensitiveKey, (key, value) -> "***");
|
||||||
filteredParams.put(sensitiveKey, "***");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return filteredParams;
|
return filteredParams;
|
||||||
}
|
}
|
||||||
@@ -115,4 +113,7 @@ public class AccessLogUtils {
|
|||||||
}
|
}
|
||||||
return truncatedParams;
|
return truncatedParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AccessLogUtils() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -64,7 +64,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
|||||||
@NonNull HttpServletResponse response,
|
@NonNull HttpServletResponse response,
|
||||||
@NonNull Object handler) {
|
@NonNull Object handler) {
|
||||||
Instant startTime = Instant.now();
|
Instant startTime = Instant.now();
|
||||||
logHandler.processAccessLogStartReq(AccessLogContext.builder()
|
logHandler.accessLogStart(AccessLogContext.builder()
|
||||||
.startTime(startTime)
|
.startTime(startTime)
|
||||||
.request(new RecordableServletHttpRequest(request))
|
.request(new RecordableServletHttpRequest(request))
|
||||||
.properties(logProperties)
|
.properties(logProperties)
|
||||||
@@ -84,7 +84,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
|||||||
Exception e) {
|
Exception e) {
|
||||||
try {
|
try {
|
||||||
Instant endTime = Instant.now();
|
Instant endTime = Instant.now();
|
||||||
logHandler.processAccessLogEndReq(AccessLogContext.builder()
|
logHandler.accessLogFinish(AccessLogContext.builder()
|
||||||
.endTime(endTime)
|
.endTime(endTime)
|
||||||
.response(new RecordableServletHttpResponse(response, response.getStatus()))
|
.response(new RecordableServletHttpResponse(response, response.getStatus()))
|
||||||
.build());
|
.build());
|
||||||
|
Reference in New Issue
Block a user