From 3e9a59df5a4b3952adc7e5491670cdefb582fdc5 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Sun, 17 Dec 2023 14:07:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(log):=20=E6=96=B0=E5=A2=9E=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=A8=A1=E5=9D=97=20-=20HttpTracePro=EF=BC=88Spring?= =?UTF-8?q?=20Boot=20Actuator=20HttpTrace=20=E5=AE=9A=E5=88=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E7=89=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../continew/starter/core/util/IpUtils.java | 22 +-- .../starter/core/util/ServletUtils.java | 39 ++++- continew-starter-dependencies/pom.xml | 22 +++ .../continew-starter-log-common/pom.xml | 17 ++ .../starter/log/common/annotation/Log.java | 53 ++++++ .../starter/log/common/dao/LogDao.java | 47 ++++++ .../common/dao/impl/LogDaoDefaultImpl.java | 92 +++++++++++ .../starter/log/common/enums/Include.java | 112 +++++++++++++ .../starter/log/common/model/LogRecord.java | 138 ++++++++++++++++ .../starter/log/common/model/LogRequest.java | 97 +++++++++++ .../starter/log/common/model/LogResponse.java | 59 +++++++ .../common/model/RecordableHttpRequest.java | 75 +++++++++ .../common/model/RecordableHttpResponse.java | 59 +++++++ .../pom.xml | 37 +++++ .../ConditionalOnEnabledLog.java | 33 ++++ .../autoconfigure/LogAutoConfiguration.java | 77 +++++++++ .../autoconfigure/LogProperties.java | 45 ++++++ .../log/httptracepro/handler/LogFilter.java | 88 ++++++++++ .../httptracepro/handler/LogInterceptor.java | 153 ++++++++++++++++++ .../handler/RecordableServletHttpRequest.java | 99 ++++++++++++ .../RecordableServletHttpResponse.java | 76 +++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + continew-starter-log/pom.xml | 30 ++++ pom.xml | 1 + 24 files changed, 1459 insertions(+), 13 deletions(-) create mode 100644 continew-starter-log/continew-starter-log-common/pom.xml create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/annotation/Log.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/LogDao.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/impl/LogDaoDefaultImpl.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/enums/Include.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRecord.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRequest.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogResponse.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpRequest.java create mode 100644 continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpResponse.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/pom.xml create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/ConditionalOnEnabledLog.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogAutoConfiguration.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogProperties.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogFilter.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogInterceptor.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpRequest.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpResponse.java create mode 100644 continew-starter-log/continew-starter-log-httptrace-pro/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 continew-starter-log/pom.xml diff --git a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/IpUtils.java b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/IpUtils.java index ecd96532..2b30a200 100644 --- a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/IpUtils.java +++ b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/IpUtils.java @@ -45,26 +45,26 @@ public class IpUtils { private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true"; /** - * 根据 IP 获取归属地信息 + * 查询 IP 归属地 * * @param ip IP 地址 - * @return 归属地信息 + * @return IP 归属地 */ - public static String getCityInfo(String ip) { + public static String getAddress(String ip) { if (ProjectProperties.IP_ADDR_LOCAL_PARSE_ENABLED) { - return getLocalCityInfo(ip); + return getAddressByLocal(ip); } else { - return getHttpCityInfo(ip); + return getAddressByHttp(ip); } } /** - * 根据 IP 获取归属地信息(网络解析) + * 查询 IP 归属地(网络解析) * * @param ip IP 地址 - * @return 归属地信息 + * @return IP 归属地 */ - public static String getHttpCityInfo(String ip) { + public static String getAddressByHttp(String ip) { if (isInnerIp(ip)) { return "内网IP"; } @@ -74,12 +74,12 @@ public class IpUtils { } /** - * 根据 IP 获取归属地信息(本地解析) + * 查询 IP 归属地(本地库解析) * * @param ip IP 地址 - * @return 归属地信息 + * @return IP 归属地 */ - public static String getLocalCityInfo(String ip) { + public static String getAddressByLocal(String ip) { if (isInnerIp(ip)) { return "内网IP"; } diff --git a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/ServletUtils.java b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/ServletUtils.java index 42ae3152..a70af10a 100644 --- a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/ServletUtils.java +++ b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/util/ServletUtils.java @@ -24,6 +24,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import top.charles7c.continew.starter.core.constant.StringConstants; import java.util.Objects; @@ -64,8 +65,42 @@ public class ServletUtils { if (null == request) { return null; } - UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); - return userAgent.getBrowser().getName() + " " + userAgent.getVersion(); + return getBrowser(request.getHeader("User-Agent")); + } + + /** + * 获取浏览器及其版本信息 + * + * @param userAgentString User-Agent 字符串 + * @return 浏览器及其版本信息 + */ + public static String getBrowser(String userAgentString) { + UserAgent userAgent = UserAgentUtil.parse(userAgentString); + return userAgent.getBrowser().getName() + StringConstants.SPACE + userAgent.getVersion(); + } + + /** + * 获取操作系统 + * + * @param request 请求对象 + * @return 操作系统 + */ + public static String getOs(HttpServletRequest request) { + if (null == request) { + return null; + } + return getOs(request.getHeader("User-Agent")); + } + + /** + * 获取操作系统 + * + * @param userAgentString User-Agent 字符串 + * @return 操作系统 + */ + public static String getOs(String userAgentString) { + UserAgent userAgent = UserAgentUtil.parse(userAgentString); + return userAgent.getOs().getName(); } private static ServletRequestAttributes getServletRequestAttributes() { diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index ce8ccc31..4fe1fa92 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -65,6 +65,7 @@ 1.6.2 3.3.2 4.3.0 + 2.14.4 3.1.5.1 5.8.23 @@ -171,6 +172,13 @@ import + + + com.alibaba + transmittable-thread-local + ${ttl.version} + + net.dreamlu @@ -249,6 +257,20 @@ ${revision} + + + top.charles7c.continew + continew-starter-log-httptrace-pro + ${revision} + + + + + top.charles7c.continew + continew-starter-log-common + ${revision} + + top.charles7c.continew diff --git a/continew-starter-log/continew-starter-log-common/pom.xml b/continew-starter-log/continew-starter-log-common/pom.xml new file mode 100644 index 00000000..c0cded1f --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + top.charles7c.continew + continew-starter-log + ${revision} + + + continew-starter-log-common + jar + + ${project.artifactId} + ContiNew Starter 日志模块 - 公共模块 + \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/annotation/Log.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/annotation/Log.java new file mode 100644 index 00000000..48b0794b --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/annotation/Log.java @@ -0,0 +1,53 @@ +/* + * 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.charles7c.continew.starter.log.common.annotation; + +import java.lang.annotation.*; + +/** + * 日志注解 + *

用于接口方法或类上,辅助 Spring Doc 使用效果最佳

+ * + * @author Charles7c + * @since 1.1.0 + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Log { + + /** + * 日志描述(仅用于接口方法上) + *

+ * 优先级:@Log("描述") > @Operation(summary="描述") + *

+ */ + String value() default ""; + + /** + * 所属模块(用于接口方法或类上) + *

+ * 优先级: 接口方法上的 @Log(module = "模块") > 接口类上的 @Log(module = "模块") > @Tag(name = "模块") 内容 + *

+ */ + String module() default ""; + + /** + * 是否忽略日志记录(用于接口方法或类上) + */ + boolean ignore() default false; +} diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/LogDao.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/LogDao.java new file mode 100644 index 00000000..1e047596 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/LogDao.java @@ -0,0 +1,47 @@ +/* + * 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.charles7c.continew.starter.log.common.dao; + +import top.charles7c.continew.starter.log.common.model.LogRecord; + +import java.util.Collections; +import java.util.List; + +/** + * 日志持久层接口 + * + * @author Charles7c + * @since 1.1.0 + */ +public interface LogDao { + + /** + * 查询日志列表 + * + * @return 日志列表 + */ + default List list() { + return Collections.emptyList(); + } + + /** + * 记录日志 + * + * @param logRecord 日志信息 + */ + void add(LogRecord logRecord); +} diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/impl/LogDaoDefaultImpl.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/impl/LogDaoDefaultImpl.java new file mode 100644 index 00000000..35a3fd91 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/dao/impl/LogDaoDefaultImpl.java @@ -0,0 +1,92 @@ +/* + * 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.charles7c.continew.starter.log.common.dao.impl; + +import top.charles7c.continew.starter.log.common.dao.LogDao; +import top.charles7c.continew.starter.log.common.model.LogRecord; + +import java.util.LinkedList; +import java.util.List; + +/** + * 日志持久层接口默认实现类(基于内存) + * + * @author Dave Syer(Spring Boot Actuator) + * @author Olivier Bourgain(Spring Boot Actuator) + * @author Charles7c + * @since 1.1.0 + */ +public class LogDaoDefaultImpl implements LogDao { + + /** + * 容量 + */ + private int capacity = 100; + + /** + * 是否降序 + */ + private boolean reverse = true; + + /** + * 日志列表 + */ + private final List logRecords = new LinkedList<>(); + + @Override + public List list() { + synchronized (this.logRecords) { + return List.copyOf(this.logRecords); + } + } + + @Override + public void add(LogRecord logRecord) { + synchronized (this.logRecords) { + while (this.logRecords.size() >= this.capacity) { + this.logRecords.remove(this.reverse ? this.capacity - 1 : 0); + } + if (this.reverse) { + this.logRecords.add(0, logRecord); + } else { + this.logRecords.add(logRecord); + } + } + } + + /** + * 设置内存中存储的最大日志容量 + * + * @param capacity 容量 + */ + public void setCapacity(int capacity) { + synchronized (this.logRecords) { + this.capacity = capacity; + } + } + + /** + * 设置是否降序 + * + * @param reverse 是否降序(默认:true) + */ + public void setReverse(boolean reverse) { + synchronized (this.logRecords) { + this.reverse = reverse; + } + } +} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/enums/Include.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/enums/Include.java new file mode 100644 index 00000000..cd64d8b6 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/enums/Include.java @@ -0,0 +1,112 @@ +/* + * 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.charles7c.continew.starter.log.common.enums; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * 日志包含信息 + * + * @author Wallace Wadge(Spring Boot Actuator) + * @author Emily Tsanova(Spring Boot Actuator) + * @author Joseph Beeton(Spring Boot Actuator) + * @author Charles7c + * @since 1.1.0 + */ +public enum Include { + + /** + * 描述 + */ + DESCRIPTION, + + /** + * 模块 + */ + MODULE, + + /** + * 请求头 + */ + REQUEST_HEADERS, + + /** + * 请求体 + */ + REQUEST_BODY, + + /** + * 请求参数 + */ + REQUEST_PARAM, + + /** + * IP 归属地 + */ + IP_ADDRESS, + + /** + * 浏览器 + */ + BROWSER, + + /** + * 操作系统 + */ + OS, + + /** + * 响应头 + */ + RESPONSE_HEADERS, + + /** + * 响应体 + */ + RESPONSE_BODY, + + /** + * 响应参数 + */ + RESPONSE_PARAM, + + /** + * 耗时 + */ + TIME_TAKEN; + + private static final Set DEFAULT_INCLUDES; + + static { + Set defaultIncludes = new LinkedHashSet<>(); + defaultIncludes.add(Include.TIME_TAKEN); + defaultIncludes.add(Include.REQUEST_HEADERS); + defaultIncludes.add(Include.RESPONSE_HEADERS); + DEFAULT_INCLUDES = Collections.unmodifiableSet(defaultIncludes); + } + + /** + * 获取默认包含信息 + * + * @return 默认包含信息 + */ + public static Set defaultIncludes() { + return DEFAULT_INCLUDES; + } +} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRecord.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRecord.java new file mode 100644 index 00000000..0434f4eb --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRecord.java @@ -0,0 +1,138 @@ +/* + * 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.charles7c.continew.starter.log.common.model; + + +import lombok.Data; +import top.charles7c.continew.starter.log.common.enums.Include; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Set; + +/** + * 日志信息 + * + * @author Dave Syer(Spring Boot Actuator) + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Phillip Webb(Spring Boot Actuator) + * @author Charles7c + * @since 1.1.0 + */ +@Data +public class LogRecord { + + /** + * 描述 + */ + private String description; + + /** + * 模块 + */ + private String module; + + /** + * 请求信息 + */ + private LogRequest request; + + /** + * 响应信息 + */ + private LogResponse response; + + /** + * 耗时 + */ + private Duration timeTaken; + + /** + * 时间戳 + */ + private final Instant timestamp; + + public LogRecord(Instant timestamp, LogRequest request, LogResponse response, Duration timeTaken) { + this.timestamp = timestamp; + this.request = request; + this.response = response; + this.timeTaken = timeTaken; + } + + /** + * 开始记录日志 + * + * @param request 请求信息 + * @return 日志记录器 + */ + public static Started start(RecordableHttpRequest request) { + return start(Clock.systemUTC(), request); + } + + /** + * 开始记录日志 + * + * @param timestamp 开始时间 + * @param request 请求信息 + * @return 日志记录器 + */ + public static Started start(Clock timestamp, RecordableHttpRequest request) { + return new Started(timestamp, request); + } + + /** + * 日志记录器 + */ + public static final class Started { + + private final Instant timestamp; + + private final RecordableHttpRequest request; + + private Started(Clock clock, RecordableHttpRequest request) { + this.timestamp = Instant.now(clock); + this.request = request; + } + + /** + * 结束日志记录 + * + * @param response 响应信息 + * @param includes 包含信息 + * @return 日志记录 + */ + public LogRecord finish(RecordableHttpResponse response, Set includes) { + return finish(Clock.systemUTC(), response, includes); + } + + /** + * 结束日志记录 + * + * @param clock 时间 + * @param response 响应信息 + * @param includes 包含信息 + * @return 日志记录 + */ + public LogRecord finish(Clock clock, RecordableHttpResponse response, Set includes) { + LogRequest logRequest = new LogRequest(this.request, includes); + LogResponse logResponse = new LogResponse(response, includes); + Duration duration = (includes.contains(Include.TIME_TAKEN)) ? Duration.between(this.timestamp, Instant.now(clock)) : null; + return new LogRecord(this.timestamp, logRequest, logResponse, duration); + } + } +} diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRequest.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRequest.java new file mode 100644 index 00000000..83a4dfda --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogRequest.java @@ -0,0 +1,97 @@ +/* + * 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.charles7c.continew.starter.log.common.model; + +import lombok.Data; +import org.springframework.http.HttpHeaders; +import top.charles7c.continew.starter.core.util.ExceptionUtils; +import top.charles7c.continew.starter.core.util.IpUtils; +import top.charles7c.continew.starter.core.util.ServletUtils; +import top.charles7c.continew.starter.log.common.enums.Include; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 请求信息 + * + * @author Charles7c + * @since 1.1.0 + */ +@Data +public class LogRequest { + + /** + * 请求方式 + */ + private String method; + + /** + * 请求 URI + */ + private URI uri; + + /** + * IP + */ + private String ip; + + /** + * 请求头 + */ + private Map> headers; + + /** + * 请求体(JSON 字符串) + */ + private String body; + + /** + * 请求参数 + */ + private Map param; + + /** + * IP 归属地 + */ + private String address; + + /** + * 浏览器 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + public LogRequest(RecordableHttpRequest request, Set includes) { + this.method = request.getMethod(); + this.uri = request.getUri(); + this.ip = request.getIp(); + this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? request.getHeaders() : null; + this.body = (includes.contains(Include.REQUEST_BODY)) ? request.getBody() : null; + this.param = (includes.contains(Include.RESPONSE_PARAM)) ? request.getParam() : null; + this.address = (includes.contains(Include.IP_ADDRESS)) ? IpUtils.getAddress(this.ip) : null; + String userAgentString = ExceptionUtils.exToNull(() -> this.headers.get(HttpHeaders.USER_AGENT).get(0)); + this.browser = (includes.contains(Include.BROWSER)) ? ServletUtils.getBrowser(userAgentString) : null; + this.os = (includes.contains(Include.OS)) ? ServletUtils.getOs(userAgentString) : null; + } +} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogResponse.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogResponse.java new file mode 100644 index 00000000..6b735910 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/LogResponse.java @@ -0,0 +1,59 @@ +/* + * 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.charles7c.continew.starter.log.common.model; + +import lombok.Data; +import top.charles7c.continew.starter.log.common.enums.Include; + +import java.util.*; + +/** + * 响应信息 + * + * @author Charles7c + * @since 1.1.0 + */ +@Data +public class LogResponse { + + /** + * 状态码 + */ + private Integer status; + + /** + * 响应头 + */ + private Map> headers; + + /** + * 响应体(JSON 字符串) + */ + private String body; + + /** + * 响应参数 + */ + private Map param; + + public LogResponse(RecordableHttpResponse response, Set includes) { + this.status = response.getStatus(); + this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? response.getHeaders() : null; + this.body = (includes.contains(Include.REQUEST_BODY)) ? response.getBody() : null; + this.param = (includes.contains(Include.RESPONSE_PARAM)) ? response.getParam() : null; + } +} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpRequest.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpRequest.java new file mode 100644 index 00000000..fca8ee47 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpRequest.java @@ -0,0 +1,75 @@ +/* + * 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.charles7c.continew.starter.log.common.model; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * 可记录的 HTTP 请求信息 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Phillip Webb(Spring Boot Actuator) + * @author Charles7c + * @see RecordableHttpResponse + * @since 1.1.0 + */ +public interface RecordableHttpRequest { + + /** + * 获取请求方式 + * + * @return 请求方式 + */ + String getMethod(); + + /** + * 获取 URI + * + * @return URI + */ + URI getUri(); + + /** + * 获取 IP + * + * @return IP + */ + String getIp(); + + /** + * 获取请求头 + * + * @return 请求头 + */ + Map> getHeaders(); + + /** + * 获取请求体 + * + * @return 请求体 + */ + String getBody(); + + /** + * 获取请求参数 + * + * @return 请求参数 + */ + Map getParam(); +} diff --git a/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpResponse.java b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpResponse.java new file mode 100644 index 00000000..74092779 --- /dev/null +++ b/continew-starter-log/continew-starter-log-common/src/main/java/top/charles7c/continew/starter/log/common/model/RecordableHttpResponse.java @@ -0,0 +1,59 @@ +/* + * 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.charles7c.continew.starter.log.common.model; + +import java.util.List; +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 响应参数 + */ + Map getParam(); +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/pom.xml b/continew-starter-log/continew-starter-log-httptrace-pro/pom.xml new file mode 100644 index 00000000..9b976692 --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + top.charles7c.continew + continew-starter-log + ${revision} + + + continew-starter-log-httptrace-pro + jar + + ${project.artifactId} + ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版) + + + + + io.swagger.core.v3 + swagger-annotations-jakarta + + + + + com.alibaba + transmittable-thread-local + + + + + top.charles7c.continew + continew-starter-log-common + + + \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/ConditionalOnEnabledLog.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/ConditionalOnEnabledLog.java new file mode 100644 index 00000000..8ce8887f --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/ConditionalOnEnabledLog.java @@ -0,0 +1,33 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.*; + +/** + * 是否启用日志记录注解 + * + * @author Charles7c + * @since 1.1.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@ConditionalOnProperty(prefix = "continew-starter.log", name = "enabled", havingValue = "true") +public @interface ConditionalOnEnabledLog {} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogAutoConfiguration.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogAutoConfiguration.java new file mode 100644 index 00000000..3f534f1b --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogAutoConfiguration.java @@ -0,0 +1,77 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.autoconfigure; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import top.charles7c.continew.starter.log.common.dao.LogDao; +import top.charles7c.continew.starter.log.common.dao.impl.LogDaoDefaultImpl; +import top.charles7c.continew.starter.log.httptracepro.handler.LogFilter; +import top.charles7c.continew.starter.log.httptracepro.handler.LogInterceptor; + +/** + * 日志自动配置 + * + * @author Charles7c + * @since 1.1.0 + */ +@Slf4j +@Configuration +@ConditionalOnEnabledLog +@RequiredArgsConstructor +@EnableConfigurationProperties(LogProperties.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +public class LogAutoConfiguration implements WebMvcConfigurer { + + private final LogProperties properties; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LogInterceptor(logDao(), properties)); + } + + /** + * 日志过滤器 + */ + @Bean + @ConditionalOnMissingBean + public LogFilter logFilter() { + return new LogFilter(); + } + + /** + * 日志持久层接口 + */ + @Bean + @ConditionalOnMissingBean + public LogDao logDao() { + return new LogDaoDefaultImpl(); + } + + @PostConstruct + public void postConstruct() { + log.info("[ContiNew Starter] - Auto Configuration 'Log-HttpTracePro' completed initialization."); + } +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogProperties.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogProperties.java new file mode 100644 index 00000000..45055fa0 --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/autoconfigure/LogProperties.java @@ -0,0 +1,45 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.autoconfigure; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import top.charles7c.continew.starter.log.common.enums.Include; + +import java.util.HashSet; +import java.util.Set; + +/** + * 日志配置属性 + * + * @author Charles7c + * @since 1.1.0 + */ +@Data +@ConfigurationProperties(prefix = "continew-starter.log") +public class LogProperties { + + /** + * 是否启用日志 + */ + private boolean enabled = false; + + /** + * 包含信息 + */ + private Set include = new HashSet<>(Include.defaultIncludes()); +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogFilter.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogFilter.java new file mode 100644 index 00000000..df019bda --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogFilter.java @@ -0,0 +1,88 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.handler; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +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 java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Objects; + +/** + * 日志过滤器 + * + * @author Dave Syer(Spring Boot Actuator) + * @author Wallace Wadge(Spring Boot Actuator) + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Venil Noronha(Spring Boot Actuator) + * @author Madhura Bhave(Spring Boot Actuator) + * @author Charles7c + * @since 1.1.0 + */ +@RequiredArgsConstructor +public class LogFilter extends OncePerRequestFilter implements Ordered { + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 10; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws ServletException, IOException { + if (!isRequestValid(request)) { + filterChain.doFilter(request, response); + return; + } + // 包装输入、输出流,可重复读取 + if (!(request instanceof ContentCachingRequestWrapper)) { + request = new ContentCachingRequestWrapper(request); + } + if (!(response instanceof ContentCachingResponseWrapper)) { + response = new ContentCachingResponseWrapper(response); + } + filterChain.doFilter(request, response); + // 更新响应(不操作这一步,会导致接口响应空白) + updateResponse(response); + } + + private boolean isRequestValid(HttpServletRequest request) { + try { + new URI(request.getRequestURL().toString()); + return true; + } catch (URISyntaxException e) { + return false; + } + } + + private void updateResponse(HttpServletResponse response) throws IOException { + ContentCachingResponseWrapper responseWrapper = + WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); + Objects.requireNonNull(responseWrapper).copyBodyToResponse(); + } +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogInterceptor.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogInterceptor.java new file mode 100644 index 00000000..a6f6e421 --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/LogInterceptor.java @@ -0,0 +1,153 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.handler; + +import cn.hutool.core.util.StrUtil; +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import top.charles7c.continew.starter.log.common.annotation.Log; +import top.charles7c.continew.starter.log.common.dao.LogDao; +import top.charles7c.continew.starter.log.common.enums.Include; +import top.charles7c.continew.starter.log.common.model.LogRecord; +import top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogProperties; + +import java.time.Clock; +import java.util.Set; + +/** + * 日志拦截器 + * + * @author Charles7c + * @since 1.1.0 + */ +@Slf4j +@RequiredArgsConstructor +public class LogInterceptor implements HandlerInterceptor { + + private final LogDao dao; + private final LogProperties properties; + private final TransmittableThreadLocal timestampTtl = new TransmittableThreadLocal<>(); + + @Override + public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull Object handler) { + if (this.isRequestRecord(handler)) { + timestampTtl.set(Clock.systemUTC()); + } + return true; + } + + @Override + public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull Object handler, Exception e) { + Clock timestamp = timestampTtl.get(); + if (null == timestamp) { + return; + } + timestampTtl.remove(); + Set includeSet = properties.getInclude(); + RecordableServletHttpRequest sourceRequest = new RecordableServletHttpRequest(request); + LogRecord.Started startedLogRecord = LogRecord.start(timestamp, sourceRequest); + RecordableServletHttpResponse sourceResponse = new RecordableServletHttpResponse(response, response.getStatus()); + LogRecord finishedLogRecord = startedLogRecord.finish(sourceResponse, includeSet); + HandlerMethod handlerMethod = (HandlerMethod) handler; + if (includeSet.contains(Include.DESCRIPTION)) { + // 记录日志描述 + this.logDescription(finishedLogRecord, handlerMethod); + } + if (includeSet.contains(Include.MODULE)) { + // 记录所属模块 + this.logModule(finishedLogRecord, handlerMethod); + } + dao.add(finishedLogRecord); + } + + /** + * 记录描述 + * + * @param logRecord 日志信息 + * @param handlerMethod 处理器方法 + */ + private void logDescription(LogRecord logRecord, HandlerMethod handlerMethod) { + // 例如:@Operation(summary="新增部门") -> 新增部门 + Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); + if (null != methodOperation) { + logRecord.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述")); + } + // 例如:@Log("新增部门") -> 新增部门 + Log methodLog = handlerMethod.getMethodAnnotation(Log.class); + if (null != methodLog && StrUtil.isNotBlank(methodLog.value())) { + logRecord.setDescription(methodLog.value()); + } + } + + /** + * 记录模块 + * + * @param logRecord 日志信息 + * @param handlerMethod 处理器方法 + */ + private void logModule(LogRecord logRecord, HandlerMethod handlerMethod) { + // 例如:@Tag(name = "部门管理") -> 部门管理 + Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class); + if (null != classTag) { + String name = classTag.name(); + logRecord.setModule(StrUtil.blankToDefault(name, "请在该接口类上指定所属模块")); + } + // 例如:@Log(module = "部门管理") -> 部门管理 + Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); + if (null != classLog && StrUtil.isNotBlank(classLog.module())) { + logRecord.setModule(classLog.module()); + } + Log methodLog = handlerMethod.getMethodAnnotation(Log.class); + if (null != methodLog && StrUtil.isNotBlank(methodLog.module())) { + logRecord.setModule(methodLog.module()); + } + } + + /** + * 是否要记录日志 + * + * @param handler 处理器 + * @return true:需要记录;false:不需要记录 + */ + private boolean isRequestRecord(Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return false; + } + // 如果接口被隐藏,不记录日志 + Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); + if (null != methodOperation && methodOperation.hidden()) { + return false; + } + // 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志 + Log methodLog = handlerMethod.getMethodAnnotation(Log.class); + if (null != methodLog && methodLog.ignore()) { + return false; + } + Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); + return null == classLog || !classLog.ignore(); + } +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpRequest.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpRequest.java new file mode 100644 index 00000000..400eb44c --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpRequest.java @@ -0,0 +1,99 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.handler; + +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.charles7c.continew.starter.core.constant.StringConstants; +import top.charles7c.continew.starter.log.common.model.RecordableHttpRequest; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * 可记录的 HTTP 请求信息适配器 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Charles7c + */ +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 getUri() { + String queryString = request.getQueryString(); + if (StrUtil.isBlank(queryString)) { + return URI.create(request.getRequestURL().toString()); + } + try { + StringBuffer urlBuffer = this.appendQueryString(queryString); + return new URI(urlBuffer.toString()); + } catch (URISyntaxException e) { + String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8); + StringBuffer urlBuffer = this.appendQueryString(encoded); + return URI.create(urlBuffer.toString()); + } + } + + @Override + public String getIp() { + return JakartaServletUtil.getClientIP(request); + } + + @Override + public Map> getHeaders() { + return JakartaServletUtil.getHeadersMap(request); + } + + @Override + public String getBody() { + ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); + if (null != wrapper) { + return StrUtil.utf8Str(wrapper.getContentAsByteArray()); + } + return StringConstants.EMPTY; + } + + @Override + public Map getParam() { + String body = this.getBody(); + return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) + ? JSONUtil.toBean(body, Map.class) + : Collections.unmodifiableMap(request.getParameterMap()); + } + + private StringBuffer appendQueryString(String queryString) { + return request.getRequestURL().append(StringConstants.QUESTION_MARK).append(queryString); + } +} \ No newline at end of file diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpResponse.java b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpResponse.java new file mode 100644 index 00000000..45dc6a99 --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/java/top/charles7c/continew/starter/log/httptracepro/handler/RecordableServletHttpResponse.java @@ -0,0 +1,76 @@ +/* + * 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.charles7c.continew.starter.log.httptracepro.handler; + +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.charles7c.continew.starter.core.constant.StringConstants; +import top.charles7c.continew.starter.log.common.model.RecordableHttpResponse; + +import java.util.*; + +/** + * 可记录的 HTTP 响应信息适配器 + * + * @author Andy Wilkinson(Spring Boot Actuator) + * @author Charles7c + */ +public final class RecordableServletHttpResponse implements RecordableHttpResponse { + + private final HttpServletResponse response; + + private final int status; + + public RecordableServletHttpResponse(HttpServletResponse response, int status) { + this.response = response; + this.status = status; + } + + @Override + public int getStatus() { + return this.status; + } + + @Override + public Map> getHeaders() { + Map> headers = new LinkedHashMap<>(); + for (String name : response.getHeaderNames()) { + headers.put(name, new ArrayList<>(response.getHeaders(name))); + } + return headers; + } + + @Override + public String getBody() { + ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); + if (null != wrapper) { + return StrUtil.utf8Str(wrapper.getContentAsByteArray()); + } + return StringConstants.EMPTY; + } + + @Override + public Map getParam() { + String body = this.getBody(); + return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) + ? JSONUtil.toBean(body, Map.class) + : null; + } +} diff --git a/continew-starter-log/continew-starter-log-httptrace-pro/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..63de7bbe --- /dev/null +++ b/continew-starter-log/continew-starter-log-httptrace-pro/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogAutoConfiguration \ No newline at end of file diff --git a/continew-starter-log/pom.xml b/continew-starter-log/pom.xml new file mode 100644 index 00000000..09f929f0 --- /dev/null +++ b/continew-starter-log/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + top.charles7c.continew + continew-starter + ${revision} + + + continew-starter-log + pom + + ${project.artifactId} + ContiNew Starter 日志模块 + + + continew-starter-log-common + continew-starter-log-httptrace-pro + + + + + + top.charles7c.continew + continew-starter-core + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index f3172518..047360ea 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ continew-starter-core continew-starter-json continew-starter-api-doc + continew-starter-log continew-starter-file continew-starter-captcha continew-starter-cache