diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/util/ServletUtils.java b/continew-starter-core/src/main/java/top/continew/starter/core/util/ServletUtils.java index 0d8dcdd0..32089585 100644 --- a/continew-starter-core/src/main/java/top/continew/starter/core/util/ServletUtils.java +++ b/continew-starter-core/src/main/java/top/continew/starter/core/util/ServletUtils.java @@ -21,7 +21,6 @@ import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.extra.servlet.JakartaServletUtil; import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgentUtil; -import cn.hutool.json.JSONUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @@ -29,24 +28,15 @@ import org.springframework.http.MediaType; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.UriUtils; import top.continew.starter.core.constant.StringConstants; -import top.continew.starter.core.wrapper.RepeatReadRequestWrapper; -import top.continew.starter.core.wrapper.RepeatReadResponseWrapper; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.Collection; -import java.util.Collections; import java.util.Map; -import java.util.Objects; /** * Servlet 工具类 * * @author Charles7c - * @author echo * @since 1.0.0 */ public class ServletUtils extends JakartaServletUtil { @@ -118,138 +108,13 @@ public class ServletUtils extends JakartaServletUtil { } } - /** - * 获取请求方法 - * - * @return {@link String } - * @since 2.11.0 - */ - public static String getRequestMethod() { - HttpServletRequest request = getRequest(); - return request != null ? request.getMethod() : null; - } - - /** - * 获取请求参数 - * - * @param name 参数名 - * @return {@link String } - * @since 2.11.0 - */ - public static String getRequestParameter(String name) { - HttpServletRequest request = getRequest(); - return request != null ? request.getParameter(name) : null; - } - - /** - * 获取请求 Ip - * - * @return {@link String } - * @since 2.11.0 - */ - public static String getRequestIp() { - HttpServletRequest request = getRequest(); - return request != null ? getClientIP(request) : null; - } - - /** - * 获取请求头信息 - * - * @return {@link Map }<{@link String }, {@link String }> - * @since 2.11.0 - */ - public static Map getRequestHeaders() { - HttpServletRequest request = getRequest(); - return request != null ? getHeaderMap(request) : Collections.emptyMap(); - } - - /** - * 获取请求 URL(包含 query 参数) - *

{@code http://localhost:8000/system/user?page=1&size=10}

- * - * @return {@link URI } - * @since 2.11.0 - */ - public static URI getRequestUrl() { - HttpServletRequest request = getRequest(); - if (request == null) { - return null; - } - String queryString = request.getQueryString(); - if (CharSequenceUtil.isBlank(queryString)) { - return URI.create(request.getRequestURL().toString()); - } - try { - StringBuilder urlBuilder = appendQueryString(queryString); - return new URI(urlBuilder.toString()); - } catch (URISyntaxException e) { - String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8); - StringBuilder urlBuilder = appendQueryString(encoded); - return URI.create(urlBuilder.toString()); - } - } - - /** - * 获取请求路径 - * - * @return {@link URI } - * @since 2.11.0 - */ - public static String getRequestPath() { - HttpServletRequest request = getRequest(); - return request != null ? request.getRequestURI() : null; - } - - /** - * 获取请求 body 参数 - * - * @return {@link String } - * @since 2.11.0 - */ - public static String getRequestBody() { - HttpServletRequest request = getRequest(); - if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) { - String body = JakartaServletUtil.getBody(request); - return JSONUtil.isTypeJSON(body) ? body : null; - } - return null; - } - - /** - * 获取请求参数 - * - * @return {@link Map }<{@link String }, {@link Object }> - * @since 2.11.0 - */ - public static Map getRequestParams() { - String body = getRequestBody(); - return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) - ? JSONUtil.toBean(body, Map.class) - : Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest()))); - } - - /** - * 获取响应状态 - * - * @return int - * @since 2.11.0 - */ - public static int getResponseStatus() { - HttpServletResponse response = getResponse(); - return response != null ? response.getStatus() : -1; - } - /** * 获取响应所有的头(header)信息 * + * @param response 响应对象{@link HttpServletResponse} * @return header值 - * @since 2.11.0 */ - public static Map getResponseHeaders() { - HttpServletResponse response = getResponse(); - if (response == null) { - return Collections.emptyMap(); - } + public static Map getHeaderMap(HttpServletResponse response) { final Collection headerNames = response.getHeaderNames(); final Map headerMap = MapUtil.newHashMap(headerNames.size(), true); for (String name : headerNames) { @@ -258,32 +123,6 @@ public class ServletUtils extends JakartaServletUtil { return headerMap; } - /** - * 获取响应 body 参数 - * - * @return {@link String } - * @since 2.11.0 - */ - public static String getResponseBody() { - HttpServletResponse response = getResponse(); - if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) { - String body = wrapper.getResponseContent(); - return JSONUtil.isTypeJSON(body) ? body : null; - } - return null; - } - - /** - * 获取响应参数 - * - * @return {@link Map }<{@link String }, {@link Object }> - * @since 2.11.0 - */ - public static Map getResponseParams() { - String body = getResponseBody(); - return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null; - } - /** * 获取 HTTP Session * @@ -349,20 +188,4 @@ public class ServletUtils extends JakartaServletUtil { public static void writeJSON(HttpServletResponse response, String data) { write(response, data, MediaType.APPLICATION_JSON_VALUE); } - - /** - * 追加查询字符串 - * - * @param queryString 查询字符串 - * @return {@link StringBuilder } - */ - private static StringBuilder appendQueryString(String queryString) { - HttpServletRequest request = getRequest(); - if (request == null) { - return new StringBuilder(); - } - return new StringBuilder().append(request.getRequestURL()) - .append(StringConstants.QUESTION_MARK) - .append(queryString); - } } 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 a349a678..4b8d9684 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 @@ -16,6 +16,8 @@ package top.continew.starter.log.aspect; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -23,6 +25,8 @@ import org.aspectj.lang.annotation.Pointcut; 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; @@ -103,16 +107,22 @@ public class AccessLogAspect { if (attributes == null) { return joinPoint.proceed(); } + HttpServletRequest request = attributes.getRequest(); + HttpServletResponse response = attributes.getResponse(); try { // 开始访问日志记录 logHandler.accessLogStart(AccessLogContext.builder() .startTime(startTime) + .request(new RecordableServletHttpRequest(request)) .properties(logProperties) .build()); return joinPoint.proceed(); } finally { Instant endTime = Instant.now(); - logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build()); + logHandler.accessLogFinish(AccessLogContext.builder() + .endTime(endTime) + .response(new RecordableServletHttpResponse(response)) + .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 674affa3..1cbb2eb1 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 @@ -17,6 +17,8 @@ package top.continew.starter.log.aspect; import cn.hutool.core.text.CharSequenceUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -27,6 +29,7 @@ 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.core.util.ServletUtils; import top.continew.starter.log.annotation.Log; import top.continew.starter.log.dao.LogDao; import top.continew.starter.log.handler.LogHandler; @@ -75,6 +78,7 @@ public class LogAspect { public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Instant startTime = Instant.now(); // 指定规则不记录 + HttpServletRequest request = ServletUtils.getRequest(); Method targetMethod = this.getMethod(joinPoint); Class targetClass = joinPoint.getTarget().getClass(); if (!isRecord(targetMethod, targetClass)) { @@ -82,7 +86,7 @@ public class LogAspect { } String errorMsg = null; // 开始记录 - LogRecord.Started startedLogRecord = logHandler.start(startTime); + LogRecord.Started startedLogRecord = logHandler.start(startTime, request); try { // 执行目标方法 return joinPoint.proceed(); @@ -92,7 +96,8 @@ public class LogAspect { } finally { try { Instant endTime = Instant.now(); - LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties + HttpServletResponse response = ServletUtils.getResponse(); + LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties .getIncludes(), targetMethod, targetClass); // 记录异常信息 if (errorMsg != null) { diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java index 8ad9ee72..f14e812f 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java @@ -56,18 +56,14 @@ public class LogFilter extends OncePerRequestFilter { return; } + // 包装可重复读取请求及响应 boolean isExcludeUri = logProperties.isMatch(request.getRequestURI()); - - // 处理可重复读取的请求 HttpServletRequest requestWrapper = (isExcludeUri || !this.isRequestWrapper(request)) ? request : new RepeatReadRequestWrapper(request); - - // 处理可重复读取的响应 HttpServletResponse responseWrapper = (isExcludeUri || !this.isResponseWrapper(response)) ? response : new RepeatReadResponseWrapper(response); - filterChain.doFilter(requestWrapper, responseWrapper); // 如果响应被包装了,复制缓存数据到原始响应 diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java index e6fe4b16..9077e0ea 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java @@ -23,15 +23,20 @@ import com.alibaba.ttl.TransmittableThreadLocal; import io.swagger.v3.oas.annotations.Hidden; 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 top.continew.starter.core.util.ServletUtils; import java.lang.reflect.Method; import java.time.Duration; @@ -74,18 +79,19 @@ public abstract class AbstractLogHandler implements LogHandler { } @Override - public LogRecord.Started start(Instant startTime) { - return LogRecord.start(startTime); + public LogRecord.Started start(Instant startTime, HttpServletRequest request) { + return LogRecord.start(startTime, new RecordableServletHttpRequest(request)); } @Override public LogRecord finish(LogRecord.Started started, Instant endTime, + HttpServletResponse response, Set includes, Method targetMethod, Class targetClass) { Set includeSet = this.getIncludes(includes, targetMethod, targetClass); - LogRecord logRecord = this.finish(started, endTime, includeSet); + LogRecord logRecord = this.finish(started, endTime, response, includeSet); // 记录日志描述 if (includeSet.contains(Include.DESCRIPTION)) { this.logDescription(logRecord, targetMethod); @@ -98,8 +104,11 @@ public abstract class AbstractLogHandler implements LogHandler { } @Override - public LogRecord finish(LogRecord.Started started, Instant endTime, Set includes) { - return started.finish(endTime, includes); + public LogRecord finish(LogRecord.Started started, + Instant endTime, + HttpServletResponse response, + Set includes) { + return started.finish(endTime, new RecordableServletHttpResponse(response), includes); } /** @@ -186,30 +195,33 @@ public abstract class AbstractLogHandler implements LogHandler { } @Override - public void accessLogStart(AccessLogContext accessLogContext) { - AccessLogProperties properties = accessLogContext.getProperties().getAccessLog(); - // 是否需要打印 规则: 是否打印开关 或 放行路径 - if (!properties.isEnabled() || AccessLogUtils.exclusionPath(accessLogContext.getProperties(), ServletUtils - .getRequestPath())) { + public void accessLogStart(AccessLogContext context) { + AccessLogProperties properties = context.getProperties().getAccessLog(); + // 是否需要打印 + if (!properties.isEnabled() || AccessLogUtils.exclusionPath(context.getProperties(), context.getRequest() + .getPath())) { return; } // 构建上下文 - logContextThread.set(accessLogContext); - String param = AccessLogUtils.getParam(properties); - log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", ServletUtils - .getRequestMethod(), ServletUtils.getRequestPath(), param); + logContextThread.set(context); + RecordableHttpRequest request = context.getRequest(); + String param = AccessLogUtils.getParam(request, properties); + log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", request.getMethod(), request + .getPath(), param); } @Override - public void accessLogFinish(AccessLogContext accessLogContext) { + public void accessLogFinish(AccessLogContext context) { AccessLogContext logContext = logContextThread.get(); if (ObjectUtil.isEmpty(logContext)) { return; } try { - Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime()); - log.info("[End] [{}] {} {} {}ms", ServletUtils.getRequestMethod(), ServletUtils - .getRequestPath(), ServletUtils.getResponseStatus(), timeTaken.toMillis()); + RecordableHttpRequest request = logContext.getRequest(); + RecordableHttpResponse response = context.getResponse(); + Duration timeTaken = Duration.between(logContext.getStartTime(), context.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/handler/LogHandler.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java index 37e0a053..7cdd6cf5 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/LogHandler.java @@ -16,6 +16,8 @@ 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; @@ -46,25 +48,28 @@ public interface LogHandler { * 开始日志记录 * * @param startTime 开始时间 + * @param request 请求对象 * @return 日志记录器 */ - LogRecord.Started start(Instant startTime); + LogRecord.Started start(Instant startTime, HttpServletRequest request); /** * 结束日志记录 * * @param started 开始日志记录器 * @param endTime 结束时间 + * @param response 响应对象 * @param includes 包含信息 * @return 日志记录 */ - LogRecord finish(LogRecord.Started started, Instant endTime, Set includes); + LogRecord finish(LogRecord.Started started, Instant endTime, HttpServletResponse response, Set includes); /** * 结束日志记录 * * @param started 开始日志记录器- * @param endTime 结束时间 + * @param response 响应对象 * @param includes 包含信息 * @param targetMethod 目标方法 * @param targetClass 目标类 @@ -72,6 +77,7 @@ public interface LogHandler { */ LogRecord finish(LogRecord.Started started, Instant endTime, + HttpServletResponse response, Set includes, Method targetMethod, Class targetClass); 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 new file mode 100644 index 00000000..8d03c909 --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpRequest.java @@ -0,0 +1,84 @@ +/* + * 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.http; + +import java.net.URI; +import java.util.Map; + +/** + * 可记录的 HTTP 请求信息 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Phillip Webb(Spring Boot Actuator) + * @author Charles7c + * @author echo + * @see RecordableHttpResponse + * @since 1.1.0 + */ +public interface RecordableHttpRequest { + + /** + * 获取请求方式 + * + * @return 请求方式 + */ + String getMethod(); + + /** + * 获取 URL + * + * @return URL + */ + URI getUrl(); + + /** + * 获取路径 + *

/foo/bar

+ * + * @return 路径 + * @since 2.10.0 + */ + String getPath(); + + /** + * 获取请求头 + * + * @return 请求头 + */ + Map getHeaders(); + + /** + * 获取请求体 + * + * @return 请求体 + */ + String getBody(); + + /** + * 获取请求参数 + * + * @return 请求参数 + */ + String getParams(); + + /** + * 获取 IP + * + * @return IP + */ + String getIp(); +} diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpResponse.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpResponse.java new file mode 100644 index 00000000..b472b631 --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/RecordableHttpResponse.java @@ -0,0 +1,58 @@ +/* + * 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.http; + +import java.util.Map; + +/** + * 可记录的 HTTP 响应信息 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Charles7c + * @see RecordableHttpRequest + * @since 1.1.0 + */ +public interface RecordableHttpResponse { + + /** + * 获取状态码 + * + * @return 状态码 + */ + int getStatus(); + + /** + * 获取响应头 + * + * @return 响应头 + */ + Map getHeaders(); + + /** + * 获取响应体 + * + * @return 响应体 + */ + String getBody(); + + /** + * 获取响应参数 + * + * @return 响应参数 + */ + String getParams(); +} 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 new file mode 100644 index 00000000..2b4b1ec5 --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpRequest.java @@ -0,0 +1,111 @@ +/* + * 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.http.servlet; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; +import cn.hutool.json.JSONUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.util.UriUtils; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.wrapper.RepeatReadRequestWrapper; +import top.continew.starter.log.http.RecordableHttpRequest; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * 可记录的 HTTP 请求信息适配器 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Charles7c + * @author echo + * @since 1.1.0 + */ +public final class RecordableServletHttpRequest implements RecordableHttpRequest { + + private final HttpServletRequest request; + + public RecordableServletHttpRequest(HttpServletRequest request) { + this.request = request; + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public URI getUrl() { + String queryString = request.getQueryString(); + if (CharSequenceUtil.isBlank(queryString)) { + return URI.create(request.getRequestURL().toString()); + } + try { + StringBuilder urlBuilder = this.appendQueryString(queryString); + return new URI(urlBuilder.toString()); + } catch (URISyntaxException e) { + String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8); + StringBuilder urlBuilder = this.appendQueryString(encoded); + return URI.create(urlBuilder.toString()); + } + } + + @Override + public String getPath() { + return request.getRequestURI(); + } + + @Override + public Map getHeaders() { + return JakartaServletUtil.getHeaderMap(request); + } + + @Override + public String getBody() { + if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) { + String body = JakartaServletUtil.getBody(request); + return JSONUtil.isTypeJSON(body) ? body : null; + } + return null; + } + + @Override + public String getParams() { + String body = this.getBody(); + return CharSequenceUtil.isNotBlank(body) ? body : JSONUtil.toJsonStr(JakartaServletUtil.getParamMap(request)); + } + + @Override + public String getIp() { + return JakartaServletUtil.getClientIP(request); + } + + /** + * URL 追加查询字符串 + * + * @param queryString 查询字符串 + * @return StringBuilder + */ + private StringBuilder appendQueryString(String queryString) { + return new StringBuilder().append(request.getRequestURL()) + .append(StringConstants.QUESTION_MARK) + .append(queryString); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..66ad8b85 --- /dev/null +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/http/servlet/RecordableServletHttpResponse.java @@ -0,0 +1,68 @@ +/* + * 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.http.servlet; + +import cn.hutool.json.JSONUtil; +import jakarta.servlet.http.HttpServletResponse; +import top.continew.starter.core.util.ServletUtils; +import top.continew.starter.core.wrapper.RepeatReadResponseWrapper; +import top.continew.starter.log.http.RecordableHttpResponse; + +import java.util.Map; + +/** + * 可记录的 HTTP 响应信息适配器 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Charles7c + * @author echo + * @since 1.1.0 + */ +public final class RecordableServletHttpResponse implements RecordableHttpResponse { + + private final HttpServletResponse response; + private final int status; + + public RecordableServletHttpResponse(HttpServletResponse response) { + this.response = response; + this.status = response.getStatus(); + } + + @Override + public int getStatus() { + return this.status; + } + + @Override + public Map getHeaders() { + return ServletUtils.getHeaderMap(response); + } + + @Override + public String getBody() { + if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) { + String body = wrapper.getResponseContent(); + return JSONUtil.isTypeJSON(body) ? body : null; + } + return null; + } + + @Override + public String getParams() { + return this.getBody(); + } +} 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 index f4f9e0d4..8c7be0f0 100644 --- 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 @@ -16,6 +16,9 @@ package top.continew.starter.log.model; +import top.continew.starter.log.http.RecordableHttpRequest; +import top.continew.starter.log.http.RecordableHttpResponse; + import java.time.Instant; /** @@ -36,6 +39,16 @@ public class AccessLogContext { */ private Instant endTime; + /** + * 请求信息 + */ + private final RecordableHttpRequest request; + + /** + * 响应信息 + */ + private final RecordableHttpResponse response; + /** * 配置信息 */ @@ -44,6 +57,8 @@ public class AccessLogContext { private AccessLogContext(Builder builder) { this.startTime = builder.startTime; this.endTime = builder.endTime; + this.request = builder.request; + this.response = builder.response; this.properties = builder.properties; } @@ -55,6 +70,14 @@ public class AccessLogContext { return endTime; } + public RecordableHttpRequest getRequest() { + return request; + } + + public RecordableHttpResponse getResponse() { + return response; + } + public LogProperties getProperties() { return properties; } @@ -74,6 +97,8 @@ public class AccessLogContext { private Instant startTime; private Instant endTime; + private RecordableHttpRequest request; + private RecordableHttpResponse response; private LogProperties properties; private Builder() { @@ -89,6 +114,16 @@ public class AccessLogContext { 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; diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRecord.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRecord.java index 9c0ba05d..c652bcae 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRecord.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRecord.java @@ -17,6 +17,8 @@ package top.continew.starter.log.model; import top.continew.starter.log.enums.Include; +import top.continew.starter.log.http.RecordableHttpRequest; +import top.continew.starter.log.http.RecordableHttpResponse; import java.time.Duration; import java.time.Instant; @@ -78,20 +80,22 @@ public class LogRecord { /** * 开始记录日志 * + * @param request 请求信息 * @return 日志记录器 */ - public static Started start() { - return start(Instant.now()); + public static Started start(RecordableHttpRequest request) { + return start(Instant.now(), request); } /** * 开始记录日志 * * @param timestamp 开始时间 + * @param request 请求信息 * @return 日志记录器 */ - public static Started start(Instant timestamp) { - return new Started(timestamp); + public static Started start(Instant timestamp, RecordableHttpRequest request) { + return new Started(timestamp, request); } /** @@ -101,20 +105,24 @@ public class LogRecord { private final Instant timestamp; - private Started(Instant timestamp) { + private final RecordableHttpRequest request; + + private Started(Instant timestamp, RecordableHttpRequest request) { this.timestamp = timestamp; + this.request = request; } /** * 结束日志记录 * * @param timestamp 结束时间 + * @param response 响应信息 * @param includes 包含信息 * @return 日志记录 */ - public LogRecord finish(Instant timestamp, Set includes) { - LogRequest logRequest = new LogRequest(includes); - LogResponse logResponse = new LogResponse(includes); + public LogRecord finish(Instant timestamp, RecordableHttpResponse response, Set includes) { + LogRequest logRequest = new LogRequest(this.request, includes); + LogResponse logResponse = new LogResponse(response, includes); Duration duration = Duration.between(this.timestamp, timestamp); return new LogRecord(this.timestamp, logRequest, logResponse, duration); } diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRequest.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRequest.java index edecff7b..66e0d753 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRequest.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogRequest.java @@ -22,6 +22,7 @@ import top.continew.starter.core.util.ExceptionUtils; import top.continew.starter.core.util.IpUtils; import top.continew.starter.core.util.ServletUtils; import top.continew.starter.log.enums.Include; +import top.continew.starter.log.http.RecordableHttpRequest; import java.net.URI; import java.util.Map; @@ -63,7 +64,7 @@ public class LogRequest { /** * 请求参数 */ - private Map param; + private String params; /** * IP 归属地 @@ -80,15 +81,15 @@ public class LogRequest { */ private String os; - public LogRequest(Set includes) { - this.method = ServletUtils.getRequestMethod(); - this.url = ServletUtils.getRequestUrl(); - this.ip = ServletUtils.getRequestIp(); - this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? ServletUtils.getRequestHeaders() : null; + public LogRequest(RecordableHttpRequest request, Set includes) { + this.method = request.getMethod(); + this.url = request.getUrl(); + this.ip = request.getIp(); + this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? request.getHeaders() : null; if (includes.contains(Include.REQUEST_BODY)) { - this.body = ServletUtils.getRequestBody(); + this.body = request.getBody(); } else if (includes.contains(Include.REQUEST_PARAM)) { - this.param = ServletUtils.getRequestParams(); + this.params = request.getParams(); } this.address = (includes.contains(Include.IP_ADDRESS)) ? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip)) @@ -148,12 +149,12 @@ public class LogRequest { this.body = body; } - public Map getParam() { - return param; + public String getParams() { + return params; } - public void setParam(Map param) { - this.param = param; + public void setParams(String params) { + this.params = params; } public String getAddress() { diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogResponse.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogResponse.java index 59c73734..d047695d 100644 --- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogResponse.java +++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/LogResponse.java @@ -17,7 +17,7 @@ package top.continew.starter.log.model; import top.continew.starter.log.enums.Include; -import top.continew.starter.core.util.ServletUtils; +import top.continew.starter.log.http.RecordableHttpResponse; import java.util.Map; import java.util.Set; @@ -48,15 +48,15 @@ public class LogResponse { /** * 响应参数 */ - private Map param; + private String params; - public LogResponse(Set includes) { - this.status = ServletUtils.getResponseStatus(); - this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? ServletUtils.getResponseHeaders() : null; + public LogResponse(RecordableHttpResponse response, Set includes) { + this.status = response.getStatus(); + this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? response.getHeaders() : null; if (includes.contains(Include.RESPONSE_BODY)) { - this.body = ServletUtils.getResponseBody(); + this.body = response.getBody(); } else if (includes.contains(Include.RESPONSE_PARAM)) { - this.param = ServletUtils.getResponseParams(); + this.params = response.getParams(); } } @@ -84,11 +84,11 @@ public class LogResponse { this.body = body; } - public Map getParam() { - return param; + public String getParams() { + return params; } - public void setParam(Map param) { - this.param = param; + public void setParams(String params) { + this.params = params; } } \ No newline at end of file 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 index c856d0b9..5686933b 100644 --- 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 @@ -17,14 +17,15 @@ package top.continew.starter.log.util; import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONUtil; +import top.continew.starter.core.util.SpringWebUtils; +import top.continew.starter.log.http.RecordableHttpRequest; import top.continew.starter.log.model.AccessLogProperties; import top.continew.starter.log.model.LogProperties; -import top.continew.starter.core.util.ServletUtils; -import top.continew.starter.core.util.SpringWebUtils; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -48,37 +49,42 @@ public class AccessLogUtils { /** * 获取参数信息 * + * @param request 请求对象 * @param properties 属性 * @return {@link String } */ - public static String getParam(AccessLogProperties properties) { + public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) { // 是否需要打印请求参数 if (!properties.isPrintRequestParam()) { return null; } // 参数为空返回空 - Object params; - try { - params = getAccessLogReqParam(); - } catch (Exception e) { + String params = request.getParams(); + if (CharSequenceUtil.isBlank(params)) { return null; } - if (ObjectUtil.isEmpty(params)) { - return null; + + Object paramObj; + if (JSONUtil.isTypeJSONArray(params)) { + paramObj = JSONUtil.toBean(params, List.class); + } else if (JSONUtil.isTypeJSONObject(params)) { + paramObj = JSONUtil.toBean(params, Map.class); + } else { + paramObj = params; } // 是否需要对特定入参脱敏 if (properties.isParamSensitive()) { - params = processSensitiveParams(params, properties.getSensitiveParams()); + paramObj = processSensitiveParams(paramObj, properties.getSensitiveParams()); } // 是否自动截断超长参数值 if (properties.isLongParamTruncate()) { - params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties + paramObj = processTruncateLongParams(paramObj, properties.getLongParamThreshold(), properties .getLongParamMaxLength(), properties.getLongParamSuffix()); } - return JSONUtil.toJsonStr(params); + return JSONUtil.toJsonStr(paramObj); } /** @@ -106,7 +112,7 @@ public class AccessLogUtils { return filterSensitiveParams((Map)params, sensitiveParams); } else if (params instanceof List) { return ((List)params).stream() - .filter(item -> item instanceof Map) + .filter(Map.class::isInstance) .map(item -> filterSensitiveParams((Map)item, sensitiveParams)) .collect(Collectors.toList()); } @@ -183,25 +189,4 @@ public class AccessLogUtils { } return truncatedParams; } - - /** - * 获取访问日志请求参数 - * - * @return {@link Object } - */ - private static Object getAccessLogReqParam() { - String body = ServletUtils.getRequestBody(); - if (CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)) { - try { - if (JSONUtil.isTypeJSONArray(body)) { - return JSONUtil.toBean(body, List.class); - } else { - return JSONUtil.toBean(body, Map.class); - } - } catch (Exception e) { - return null; - } - } - return Collections.unmodifiableMap(ServletUtils.getRequestParams()); - } } 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 10d9db4e..1a27bad7 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 @@ -24,10 +24,12 @@ import org.slf4j.LoggerFactory; import org.springframework.lang.NonNull; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; -import top.continew.starter.log.dao.LogDao; -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 top.continew.starter.log.dao.LogDao; +import top.continew.starter.log.handler.LogHandler; import top.continew.starter.log.model.LogRecord; import java.lang.reflect.Method; @@ -58,10 +60,15 @@ public class LogInterceptor implements HandlerInterceptor { @NonNull HttpServletResponse response, @NonNull Object handler) { Instant startTime = Instant.now(); - logHandler.accessLogStart(AccessLogContext.builder().startTime(startTime).properties(logProperties).build()); + // 访问日志 + logHandler.accessLogStart(AccessLogContext.builder() + .startTime(startTime) + .request(new RecordableServletHttpRequest(request)) + .properties(logProperties) + .build()); // 开始日志记录 if (this.isRecord(handler)) { - LogRecord.Started startedLogRecord = logHandler.start(startTime); + LogRecord.Started startedLogRecord = logHandler.start(startTime, request); logTtl.set(startedLogRecord); } return true; @@ -74,7 +81,11 @@ public class LogInterceptor implements HandlerInterceptor { Exception e) { try { Instant endTime = Instant.now(); - logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build()); + // 访问日志 + logHandler.accessLogFinish(AccessLogContext.builder() + .endTime(endTime) + .response(new RecordableServletHttpResponse(response)) + .build()); LogRecord.Started startedLogRecord = logTtl.get(); if (startedLogRecord == null) { return; @@ -83,7 +94,7 @@ public class LogInterceptor implements HandlerInterceptor { HandlerMethod handlerMethod = (HandlerMethod)handler; Method targetMethod = handlerMethod.getMethod(); Class targetClass = handlerMethod.getBeanType(); - LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties + LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties .getIncludes(), targetMethod, targetClass); logDao.add(logRecord); } catch (Exception ex) {