【优化】增强访问日志,支持是否记录、脱敏、操作信息等功能

This commit is contained in:
YunaiV
2024-04-03 19:52:52 +08:00
parent 9fac998a14
commit 132c1cc828
20 changed files with 483 additions and 296 deletions

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.framework.apilog.config;
import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter;
import cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
@ -10,23 +11,26 @@ import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import jakarta.servlet.Filter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@AutoConfiguration(after = YudaoWebAutoConfiguration.class)
public class YudaoApiLogAutoConfiguration {
public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
}
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
}
@ -49,4 +53,9 @@ public class YudaoApiLogAutoConfiguration {
return bean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApiAccessLogInterceptor());
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.framework.apilog.core.annotations;
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 访问日志注解
*
* @author 芋道源码
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiAccessLog {
// ========== 开关字段 ==========
/**
* 是否记录访问日志
*/
boolean enable() default true;
/**
* 是否记录请求参数
*
* 默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭
*/
boolean requestEnable() default true;
/**
* 是否记录响应结果
*
* 默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开
*/
boolean responseEnable() default false;
/**
* 敏感参数数组
*
* 添加后,请求参数、响应结果不会记录该参数
*/
String[] sanitizeKeys() default {};
// ========== 模块字段 ==========
/**
* 操作模块
*
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性
*/
String operateModule() default "";
/**
* 操作名
*
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性
*/
String operateName() default "";
/**
* 操作分类
*
* 实际并不是数组,因为枚举不能设置 null 作为默认值
*/
OperateTypeEnum[] operateType() default {};
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.framework.apilog.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作日志的操作类型
*
* @author ruoyi
*/
@Getter
@AllArgsConstructor
public enum OperateTypeEnum {
/**
* 查询
*/
GET(1),
/**
* 新增
*/
CREATE(2),
/**
* 修改
*/
UPDATE(3),
/**
* 删除
*/
DELETE(4),
/**
* 导出
*/
EXPORT(5),
/**
* 导入
*/
IMPORT(6),
/**
* 其它
*
* 在无法归类时,可以选择使用其它。因为还有操作名可以进一步标识
*/
OTHER(0);
/**
* 类型
*/
private final Integer type;
}

View File

@ -1,38 +1,56 @@
package cn.iocoder.yudao.framework.apilog.core.filter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Iterator;
import java.util.Map;
import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* API 访问日志 Filter
*
* 目的:记录 API 访问日志到数据库中
*
* @author 芋道源码
*/
@Slf4j
public class ApiAccessLogFilter extends ApiRequestFilter {
private static final String[] SANITIZE_KEYS = new String[]{"password", "token", "accessToken", "refreshToken"};
private final String applicationName;
private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
@ -44,6 +62,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
}
@Override
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 获得开始时间
@ -66,45 +85,166 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,
Map<String, String> queryString, String requestBody, Exception ex) {
ApiAccessLog accessLog = new ApiAccessLog();
ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO();
try {
this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex);
boolean enable = buildApiAccessLog(accessLog, request, beginTime, queryString, requestBody, ex);
if (!enable) {
return;
}
apiAccessLogFrameworkService.createApiAccessLog(accessLog);
} catch (Throwable th) {
log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
}
}
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, LocalDateTime beginTime,
private boolean buildApiAccessLog(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, LocalDateTime beginTime,
Map<String, String> queryString, String requestBody, Exception ex) {
// 判断:是否要记录操作日志
HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD);
ApiAccessLog accessLogAnnotation = null;
if (handlerMethod != null) {
accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class);
if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) {
return false;
}
}
// 处理用户信息
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
accessLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request))
.setUserType(WebFrameworkUtils.getLoginUserType(request));
// 设置访问结果
CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);
if (result != null) {
accessLog.setResultCode(result.getCode());
accessLog.setResultMsg(result.getMsg());
accessLog.setResultCode(result.getCode()).setResultMsg(result.getMsg());
} else if (ex != null) {
accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode());
accessLog.setResultMsg(ExceptionUtil.getRootCauseMessage(ex));
accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode())
.setResultMsg(ExceptionUtil.getRootCauseMessage(ex));
} else {
accessLog.setResultCode(0);
accessLog.setResultMsg("");
accessLog.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode()).setResultMsg("");
}
// 设置请求字段
accessLog.setTraceId(TracerUtils.getTraceId()).setApplicationName(applicationName)
.setRequestUrl(request.getRequestURI()).setRequestMethod(request.getMethod())
.setUserAgent(ServletUtils.getUserAgent(request)).setUserIp(ServletUtils.getClientIP(request));
String[] sanitizeKeys = accessLogAnnotation != null ? accessLogAnnotation.sanitizeKeys() : null;
Boolean requestEnable = accessLogAnnotation != null ? accessLogAnnotation.requestEnable() : Boolean.TRUE;
if (!BooleanUtil.isFalse(requestEnable)) { // 默认记录,所以判断 !false
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
.put("query", sanitizeMap(queryString, sanitizeKeys))
.put("body", sanitizeJson(requestBody, sanitizeKeys)).build();
accessLog.setRequestParams(toJsonString(requestParams));
}
Boolean responseEnable = accessLogAnnotation != null ? accessLogAnnotation.responseEnable() : Boolean.FALSE;
if (BooleanUtil.isTrue(responseEnable)) { // 默认不记录,默认强制要求 true
accessLog.setResponseBody(sanitizeJson(result, sanitizeKeys));
}
// 设置其它字段
accessLog.setTraceId(TracerUtils.getTraceId());
accessLog.setApplicationName(applicationName);
accessLog.setRequestUrl(request.getRequestURI());
Map<String, Object> requestParams = MapUtil.<String, Object>builder().put("query", queryString).put("body", requestBody).build();
accessLog.setRequestParams(toJsonString(requestParams));
accessLog.setRequestMethod(request.getMethod());
accessLog.setUserAgent(ServletUtils.getUserAgent(request));
accessLog.setUserIp(ServletUtils.getClientIP(request));
// 持续时间
accessLog.setBeginTime(beginTime);
accessLog.setEndTime(LocalDateTime.now());
accessLog.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));
accessLog.setBeginTime(beginTime).setEndTime(LocalDateTime.now())
.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));
// 操作模块
if (handlerMethod != null) {
Tag tagAnnotation = handlerMethod.getBeanType().getAnnotation(Tag.class);
Operation operationAnnotation = handlerMethod.getMethodAnnotation(Operation.class);
String operateModule = accessLogAnnotation != null ? accessLogAnnotation.operateModule() :
tagAnnotation != null ? StrUtil.nullToDefault(tagAnnotation.name(), tagAnnotation.description()) : null;
String operateName = accessLogAnnotation != null ? accessLogAnnotation.operateName() :
operationAnnotation != null ? operationAnnotation.summary() : null;
OperateTypeEnum operateType = accessLogAnnotation != null && accessLogAnnotation.operateType().length > 0 ?
accessLogAnnotation.operateType()[0] : parseOperateLogType(request);
accessLog.setOperateModule(operateModule).setOperateName(operateName).setOperateType(operateType.getType());
}
return true;
}
// ========== 解析 @ApiAccessLog、@Swagger 注解 ==========
private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) {
RequestMethod requestMethod = RequestMethod.resolve(request.getMethod());
if (requestMethod == null) {
return OperateTypeEnum.OTHER;
}
switch (requestMethod) {
case GET:
return OperateTypeEnum.GET;
case POST:
return OperateTypeEnum.CREATE;
case PUT:
return OperateTypeEnum.UPDATE;
case DELETE:
return OperateTypeEnum.DELETE;
default:
return OperateTypeEnum.OTHER;
}
}
// ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ==========
private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {
if (CollUtil.isNotEmpty(map)) {
return null;
}
if (sanitizeKeys != null) {
MapUtil.removeAny(map, sanitizeKeys);
}
MapUtil.removeAny(map, SANITIZE_KEYS);
return JsonUtils.toJsonString(map);
}
private static String sanitizeJson(String jsonString, String[] sanitizeKeys) {
if (StrUtil.isEmpty(jsonString)) {
return null;
}
try {
JsonNode rootNode = JsonUtils.parseTree(jsonString);
sanitizeJson(rootNode, sanitizeKeys);
return JsonUtils.toJsonString(rootNode);
} catch (Exception e) {
// 脱敏失败的情况下,直接忽略异常,避免影响用户请求
log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
return jsonString;
}
}
private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) {
if (commonResult == null) {
return null;
}
String jsonString = toJsonString(commonResult);
try {
JsonNode rootNode = JsonUtils.parseTree(jsonString);
sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉
return JsonUtils.toJsonString(rootNode);
} catch (Exception e) {
// 脱敏失败的情况下,直接忽略异常,避免影响用户请求
log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
return jsonString;
}
}
private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) {
// 情况一:数组,遍历处理
if (node.isArray()) {
for (JsonNode childNode : node) {
sanitizeJson(childNode, sanitizeKeys);
}
return;
}
// 情况二:非 Object只是某个值直接返回
if (!node.isObject()) {
return;
}
// 情况三Object遍历处理
Iterator<Map.Entry<String, JsonNode>> iterator = node.properties().iterator();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
if (ArrayUtil.contains(sanitizeKeys, entry.getKey())
|| ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {
iterator.remove();
continue;
}
sanitizeJson(entry.getValue(), sanitizeKeys);
}
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.framework.apilog.core.interceptor;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
/**
* API 访问日志 Interceptor
*
* 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
*
* @author 芋道源码
*/
@Slf4j
public class ApiAccessLogInterceptor implements HandlerInterceptor {
public static String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
private static String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 记录 HandlerMethod提供给 ApiAccessLogFilter 使用
HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
if (handlerMethod != null) {
request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
}
// 打印 request 日志
if (!SpringUtils.isProd()) {
Map<String, String> queryString = ServletUtils.getParamMap(request);
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
} else {
log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
StrUtil.nullToDefault(requestBody, queryString.toString()));
}
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 打印 response 日志
if (!SpringUtils.isProd()) {
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
stopWatch.stop();
log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
request.getRequestURI(), stopWatch.getTotalTimeMillis());
}
}
}

View File

@ -1,85 +0,0 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* API 访问日志
*
* @author 芋道源码
*/
@Data
public class ApiAccessLog {
/**
* 链路追踪编号
*/
private String traceId;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 请求方法名
*/
@NotNull(message = "http 请求方法不能为空")
private String requestMethod;
/**
* 访问地址
*/
@NotNull(message = "访问地址不能为空")
private String requestUrl;
/**
* 请求参数
*/
@NotNull(message = "请求参数不能为空")
private String requestParams;
/**
* 用户 IP
*/
@NotNull(message = "ip 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotNull(message = "User-Agent 不能为空")
private String userAgent;
/**
* 开始请求时间
*/
@NotNull(message = "开始请求时间不能为空")
private LocalDateTime beginTime;
/**
* 结束请求时间
*/
@NotNull(message = "结束请求时间不能为空")
private LocalDateTime endTime;
/**
* 执行时长,单位:毫秒
*/
@NotNull(message = "执行时长不能为空")
private Integer duration;
/**
* 结果码
*/
@NotNull(message = "错误码不能为空")
private Integer resultCode;
/**
* 结果提示
*/
private String resultMsg;
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
/**
* API 访问日志 Framework Service 接口
*
@ -10,7 +12,8 @@ public interface ApiAccessLogFrameworkService {
/**
* 创建 API 访问日志
*
* @param apiAccessLog API 访问日志
* @param reqDTO API 访问日志
*/
void createApiAccessLog(ApiAccessLog apiAccessLog);
void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import lombok.RequiredArgsConstructor;
@ -20,8 +19,7 @@ public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkSe
@Override
@Async
public void createApiAccessLog(ApiAccessLog apiAccessLog) {
ApiAccessLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiAccessLog, ApiAccessLogCreateReqDTO.class);
public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
apiAccessLogApi.createApiAccessLog(reqDTO);
}

View File

@ -1,107 +0,0 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* API 错误日志
*
* @author 芋道源码
*/
@Data
public class ApiErrorLog {
/**
* 链路编号
*/
private String traceId;
/**
* 账号编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 请求方法名
*/
@NotNull(message = "http 请求方法不能为空")
private String requestMethod;
/**
* 访问地址
*/
@NotNull(message = "访问地址不能为空")
private String requestUrl;
/**
* 请求参数
*/
@NotNull(message = "请求参数不能为空")
private String requestParams;
/**
* 用户 IP
*/
@NotNull(message = "ip 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotNull(message = "User-Agent 不能为空")
private String userAgent;
/**
* 异常时间
*/
@NotNull(message = "异常时间不能为空")
private LocalDateTime exceptionTime;
/**
* 异常名
*/
@NotNull(message = "异常名不能为空")
private String exceptionName;
/**
* 异常发生的类全名
*/
@NotNull(message = "异常发生的类全名不能为空")
private String exceptionClassName;
/**
* 异常发生的类文件
*/
@NotNull(message = "异常发生的类文件不能为空")
private String exceptionFileName;
/**
* 异常发生的方法名
*/
@NotNull(message = "异常发生的方法名不能为空")
private String exceptionMethodName;
/**
* 异常发生的方法所在行
*/
@NotNull(message = "异常发生的方法所在行不能为空")
private Integer exceptionLineNumber;
/**
* 异常的栈轨迹异常的栈轨迹
*/
@NotNull(message = "异常的栈轨迹不能为空")
private String exceptionStackTrace;
/**
* 异常导致的根消息
*/
@NotNull(message = "异常导致的根消息不能为空")
private String exceptionRootCauseMessage;
/**
* 异常导致的消息
*/
@NotNull(message = "异常导致的消息不能为空")
private String exceptionMessage;
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
/**
* API 错误日志 Framework Service 接口
*
@ -10,7 +12,8 @@ public interface ApiErrorLogFrameworkService {
/**
* 创建 API 错误日志
*
* @param apiErrorLog API 错误日志
* @param reqDTO API 错误日志
*/
void createApiErrorLog(ApiErrorLog apiErrorLog);
void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.framework.apilog.core.service;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import lombok.RequiredArgsConstructor;
@ -20,8 +19,7 @@ public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkServ
@Override
@Async
public void createApiErrorLog(ApiErrorLog apiErrorLog) {
ApiErrorLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiErrorLog, ApiErrorLogCreateReqDTO.class);
public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
apiErrorLogApi.createApiErrorLog(reqDTO);
}

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.web.core.handler;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@ -11,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
@ -46,6 +46,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@Slf4j
public class GlobalExceptionHandler {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private final String applicationName;
private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
@ -237,10 +238,10 @@ public class GlobalExceptionHandler {
private void createExceptionLog(HttpServletRequest req, Throwable e) {
// 插入错误日志
ApiErrorLog errorLog = new ApiErrorLog();
ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
try {
// 初始化 errorLog
initExceptionLog(errorLog, req, e);
buildExceptionLog(errorLog, req, e);
// 执行插入 errorLog
apiErrorLogFrameworkService.createApiErrorLog(errorLog);
} catch (Throwable th) {
@ -248,7 +249,7 @@ public class GlobalExceptionHandler {
}
}
private void initExceptionLog(ApiErrorLog errorLog, HttpServletRequest request, Throwable e) {
private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
// 处理用户信息
errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));