mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 20:57:23 +08:00
chore: log-httptrace-pro => log-interceptor
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<description>ContiNew Starter 日志模块 - 拦截器版(Spring Boot Actuator HttpTrace 增强版)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Swagger 注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 是否启用日志记录注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Documented
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.LOG, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public @interface ConditionalOnEnabledLog {
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.dao.impl.LogDaoDefaultImpl;
|
||||
import top.continew.starter.log.interceptor.handler.LogFilter;
|
||||
import top.continew.starter.log.interceptor.handler.LogInterceptor;
|
||||
|
||||
/**
|
||||
* 日志自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnEnabledLog
|
||||
@EnableConfigurationProperties(LogProperties.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LogAutoConfiguration.class);
|
||||
private final LogProperties logProperties;
|
||||
|
||||
public LogAutoConfiguration(LogProperties logProperties) {
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LogInterceptor(logDao(), logProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogFilter logFilter() {
|
||||
return new LogFilter(logProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志持久层接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LogDao logDao() {
|
||||
return new LogDaoDefaultImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Log' completed initialization.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.LOG)
|
||||
public class LogProperties {
|
||||
|
||||
/**
|
||||
* 是否启用日志
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 是否打印日志,开启后可打印访问日志(类似于 Nginx access log)
|
||||
*/
|
||||
private Boolean isPrint = false;
|
||||
|
||||
/**
|
||||
* 包含信息
|
||||
*/
|
||||
private Set<Include> includes = new HashSet<>(Include.defaultIncludes());
|
||||
|
||||
/**
|
||||
* 放行路由
|
||||
*/
|
||||
private List<String> excludePatterns = new ArrayList<>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Boolean getIsPrint() {
|
||||
return isPrint;
|
||||
}
|
||||
|
||||
public void setIsPrint(Boolean print) {
|
||||
isPrint = print;
|
||||
}
|
||||
|
||||
public Set<Include> getIncludes() {
|
||||
return includes;
|
||||
}
|
||||
|
||||
public void setIncludes(Set<Include> includes) {
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
public List<String> getExcludePatterns() {
|
||||
return excludePatterns;
|
||||
}
|
||||
|
||||
public void setExcludePatterns(List<String> excludePatterns) {
|
||||
this.excludePatterns = excludePatterns;
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
import top.continew.starter.web.util.SpringWebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志过滤器
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public class LogFilter extends OncePerRequestFilter implements Ordered {
|
||||
|
||||
private final LogProperties logProperties;
|
||||
|
||||
public LogFilter(LogProperties logProperties) {
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
@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 (!this.isFilter(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 包装输入流,可重复读取
|
||||
if (this.isRequestWrapper(request)) {
|
||||
request = new ContentCachingRequestWrapper(request);
|
||||
}
|
||||
// 包装输出流,可重复读取
|
||||
boolean isResponseWrapper = this.isResponseWrapper(response);
|
||||
if (isResponseWrapper) {
|
||||
response = new ContentCachingResponseWrapper(response);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
// 更新响应(不操作这一步,会导致接口响应空白)
|
||||
if (isResponseWrapper) {
|
||||
this.updateResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤请求
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 是否过滤请求
|
||||
*/
|
||||
private boolean isFilter(HttpServletRequest request) {
|
||||
if (!isRequestValid(request)) {
|
||||
return false;
|
||||
}
|
||||
// 不拦截 /error
|
||||
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
|
||||
if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
|
||||
return false;
|
||||
}
|
||||
// 放行
|
||||
boolean isMatch = logProperties.getExcludePatterns()
|
||||
.stream()
|
||||
.anyMatch(pattern -> SpringWebUtils.match(pattern, request.getRequestURI()));
|
||||
return !isMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求是否有效
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isRequestValid(HttpServletRequest request) {
|
||||
try {
|
||||
new URI(request.getRequestURL().toString());
|
||||
return true;
|
||||
} catch (URISyntaxException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要包装输入流
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isRequestWrapper(HttpServletRequest request) {
|
||||
Set<Include> includeSet = logProperties.getIncludes();
|
||||
return !(request instanceof ContentCachingRequestWrapper) && (includeSet
|
||||
.contains(Include.REQUEST_BODY) || includeSet.contains(Include.REQUEST_PARAM));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要包装输出流
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isResponseWrapper(HttpServletResponse response) {
|
||||
Set<Include> includeSet = logProperties.getIncludes();
|
||||
return !(response instanceof ContentCachingResponseWrapper) && (includeSet
|
||||
.contains(Include.RESPONSE_BODY) || includeSet.contains(Include.RESPONSE_PARAM));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新响应
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @throws IOException /
|
||||
*/
|
||||
private void updateResponse(HttpServletResponse response) throws IOException {
|
||||
ContentCachingResponseWrapper responseWrapper = WebUtils
|
||||
.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
||||
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
|
||||
}
|
||||
}
|
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
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 org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.log.core.annotation.Log;
|
||||
import top.continew.starter.log.core.dao.LogDao;
|
||||
import top.continew.starter.log.core.enums.Include;
|
||||
import top.continew.starter.log.core.model.LogRecord;
|
||||
import top.continew.starter.log.core.model.LogResponse;
|
||||
import top.continew.starter.log.interceptor.autoconfigure.LogProperties;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 日志拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class LogInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class);
|
||||
private final LogDao logDao;
|
||||
private final LogProperties logProperties;
|
||||
private final TransmittableThreadLocal<LogRecord.Started> timestampTtl = new TransmittableThreadLocal<>();
|
||||
|
||||
public LogInterceptor(LogDao logDao, LogProperties logProperties) {
|
||||
this.logDao = logDao;
|
||||
this.logProperties = logProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull Object handler) {
|
||||
Clock timestamp = Clock.systemUTC();
|
||||
if (this.isRequestRecord(handler)) {
|
||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
LogRecord.Started startedLogRecord = LogRecord.start(timestamp, new RecordableServletHttpRequest(request));
|
||||
timestampTtl.set(startedLogRecord);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull Object handler,
|
||||
Exception e) {
|
||||
LogRecord.Started startedLogRecord = timestampTtl.get();
|
||||
if (null == startedLogRecord) {
|
||||
return;
|
||||
}
|
||||
timestampTtl.remove();
|
||||
try {
|
||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
|
||||
Set<Include> includeSet = this.getIncludes(methodLog, classLog);
|
||||
LogRecord finishedLogRecord = startedLogRecord.finish(new RecordableServletHttpResponse(response, response
|
||||
.getStatus()), includeSet);
|
||||
// 记录日志描述
|
||||
if (includeSet.contains(Include.DESCRIPTION)) {
|
||||
this.logDescription(finishedLogRecord, methodLog, handlerMethod);
|
||||
}
|
||||
// 记录所属模块
|
||||
if (includeSet.contains(Include.MODULE)) {
|
||||
this.logModule(finishedLogRecord, methodLog, classLog, handlerMethod);
|
||||
}
|
||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||
LogResponse logResponse = finishedLogRecord.getResponse();
|
||||
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), logResponse
|
||||
.getStatus(), finishedLogRecord.getTimeTaken().toMillis());
|
||||
}
|
||||
logDao.add(finishedLogRecord);
|
||||
} catch (Exception ex) {
|
||||
log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志包含信息
|
||||
*
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param classLog 类级 Log 注解
|
||||
* @return 日志包含信息
|
||||
*/
|
||||
private Set<Include> getIncludes(Log methodLog, Log classLog) {
|
||||
Set<Include> includeSet = logProperties.getIncludes();
|
||||
if (null != classLog) {
|
||||
this.processInclude(includeSet, classLog);
|
||||
}
|
||||
if (null != methodLog) {
|
||||
this.processInclude(includeSet, methodLog);
|
||||
}
|
||||
return includeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日志包含信息
|
||||
*
|
||||
* @param includes 日志包含信息
|
||||
* @param logAnnotation Log 注解
|
||||
*/
|
||||
private void processInclude(Set<Include> includes, Log logAnnotation) {
|
||||
Include[] includeArr = logAnnotation.includes();
|
||||
if (includeArr.length > 0) {
|
||||
includes.addAll(Set.of(includeArr));
|
||||
}
|
||||
Include[] excludeArr = logAnnotation.excludes();
|
||||
if (excludeArr.length > 0) {
|
||||
includes.removeAll(Set.of(excludeArr));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录描述
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logDescription(LogRecord logRecord, Log methodLog, HandlerMethod handlerMethod) {
|
||||
// 例如:@Log("新增部门") -> 新增部门
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
|
||||
logRecord.setDescription(methodLog.value());
|
||||
return;
|
||||
}
|
||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation) {
|
||||
logRecord.setDescription(CharSequenceUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录模块
|
||||
*
|
||||
* @param logRecord 日志信息
|
||||
* @param methodLog 方法级 Log 注解
|
||||
* @param classLog 类级 Log 注解
|
||||
* @param handlerMethod 处理器方法
|
||||
*/
|
||||
private void logModule(LogRecord logRecord, Log methodLog, Log classLog, HandlerMethod handlerMethod) {
|
||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
|
||||
logRecord.setModule(methodLog.module());
|
||||
return;
|
||||
}
|
||||
if (null != classLog && CharSequenceUtil.isNotBlank(classLog.module())) {
|
||||
logRecord.setModule(classLog.module());
|
||||
return;
|
||||
}
|
||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
||||
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
|
||||
if (null != classTag) {
|
||||
String name = classTag.name();
|
||||
logRecord.setModule(CharSequenceUtil.blankToDefault(name, "请在该接口类上指定所属模块"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
Hidden methodHidden = handlerMethod.getMethodAnnotation(Hidden.class);
|
||||
if (null != methodHidden) {
|
||||
return false;
|
||||
}
|
||||
Class<?> handlerBeanType = handlerMethod.getBeanType();
|
||||
if (null != handlerBeanType.getDeclaredAnnotation(Hidden.class)) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
if (null != methodLog && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = handlerBeanType.getDeclaredAnnotation(Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.core.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 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 getIp() {
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return JakartaServletUtil.getHeaderMap(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
|
||||
if (null != wrapper) {
|
||||
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getParam() {
|
||||
String body = this.getBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
|
||||
? JSONUtil.toBean(body, Map.class)
|
||||
: Collections.unmodifiableMap(request.getParameterMap());
|
||||
}
|
||||
|
||||
private StringBuilder appendQueryString(String queryString) {
|
||||
return new StringBuilder().append(request.getRequestURL())
|
||||
.append(StringConstants.QUESTION_MARK)
|
||||
.append(queryString);
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.log.interceptor.handler;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.log.core.model.RecordableHttpResponse;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
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<String, String> getHeaders() {
|
||||
return ServletUtils.getHeaderMap(response);
|
||||
}
|
||||
|
||||
@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<String, Object> getParam() {
|
||||
String body = this.getBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
top.continew.starter.log.interceptor.autoconfigure.LogAutoConfiguration
|
Reference in New Issue
Block a user