Browse Source

初始化 API 错误日志模块的代码

YunaiV 4 years ago
parent
commit
19a461b3cf

+ 7 - 8
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java

@@ -68,20 +68,20 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
 
     private void createApiAccessLog(HttpServletRequest request, Date beginTime,
                                     Map<String, String> queryString, String requestBody, Exception ex) {
+        ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO();
         try {
-            ApiAccessLogCreateDTO accessLog = this.buildApiAccessLogDTO(request, beginTime, queryString, requestBody, ex);
+            this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex);
             apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog);
-        } catch (Exception e) {
-            log.error("[createApiAccessLog][url({}) 发生异常]", request.getRequestURI(), e);
+        } catch (Throwable th) {
+            log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), JsonUtils.toJsonString(accessLog), th);
         }
     }
 
-    private ApiAccessLogCreateDTO buildApiAccessLogDTO(HttpServletRequest request, Date beginTime,
-                                                       Map<String, String> queryString, String requestBody, Exception ex) {
-        ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO();
+    private void buildApiAccessLogDTO(ApiAccessLogCreateDTO accessLog, HttpServletRequest request, Date beginTime,
+                                      Map<String, String> queryString, String requestBody, Exception ex) {
         // 处理用户信息
         accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
-        accessLog.setUserType(WebFrameworkUtils.getUsrType(request));
+        accessLog.setUserType(WebFrameworkUtils.getUesrType(request));
         // 设置访问结果
         CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);
         if (result != null) {
@@ -107,7 +107,6 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
         accessLog.setBeginTime(beginTime);
         accessLog.setEndTime(new Date());
         accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getBeginTime()));
-        return accessLog;
     }
 
 }

+ 21 - 0
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiErrorLogFrameworkService.java

@@ -0,0 +1,21 @@
+package cn.iocoder.dashboard.framework.logger.apilog.core.service;
+
+import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO;
+
+import javax.validation.Valid;
+
+/**
+ * API 错误日志 Framework Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface ApiErrorLogFrameworkService {
+
+    /**
+     * 创建 API 错误日志
+     *
+     * @param createDTO 创建信息
+     */
+    void createApiErrorLogAsync(@Valid ApiErrorLogCreateDTO createDTO);
+
+}

+ 109 - 0
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiErrorLogCreateDTO.java

@@ -0,0 +1,109 @@
+package cn.iocoder.dashboard.framework.logger.apilog.core.service.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * API 错误日志创建 DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Accessors(chain = true)
+public class ApiErrorLogCreateDTO implements Serializable {
+
+    /**
+     * 链路编号
+     */
+    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 Date 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;
+
+}

+ 0 - 1
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.dashboard.framework.logger.apilog.core.service;

+ 61 - 50
src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java

@@ -1,12 +1,24 @@
 package cn.iocoder.dashboard.framework.web.core.handler;
 
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.servlet.ServletUtil;
 import cn.iocoder.dashboard.common.exception.GlobalException;
 import cn.iocoder.dashboard.common.exception.ServiceException;
 import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiErrorLogFrameworkService;
+import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO;
 import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
+import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.dashboard.util.json.JsonUtils;
+import cn.iocoder.dashboard.util.servlet.ServletUtils;
 import io.github.resilience4j.ratelimiter.RequestNotPermitted;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.AccessDeniedException;
+import org.springframework.util.Assert;
 import org.springframework.validation.BindException;
 import org.springframework.validation.FieldError;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -17,10 +29,13 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 import org.springframework.web.servlet.NoHandlerFoundException;
 
+import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.ConstraintViolation;
 import javax.validation.ConstraintViolationException;
 import javax.validation.ValidationException;
+import java.util.Date;
+import java.util.Map;
 
 import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.*;
 
@@ -33,6 +48,12 @@ import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstan
 @Slf4j
 public class GlobalExceptionHandler {
 
+    @Value("spring.application.name")
+    private String applicationName;
+
+    @Resource
+    private ApiErrorLogFrameworkService apiErrorLogFrameworkService;
+
     /**
      * 处理所有异常,主要是提供给 Filter 使用
      * 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
@@ -232,57 +253,47 @@ public class GlobalExceptionHandler {
         return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage());
     }
 
-    // TODO 芋艿:增加异常日志
-    public void createExceptionLog(HttpServletRequest req, Throwable e) {
-//        // 插入异常日志
-//        SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO();
-//        try {
-//            // 增加异常计数 metrics TODO 暂时去掉
-////            EXCEPTION_COUNTER.increment();
-//            // 初始化 exceptionLog
-//            initExceptionLog(exceptionLog, req, e);
-//            // 执行插入 exceptionLog
-//            createExceptionLog(exceptionLog);
-//        } catch (Throwable th) {
-//            log.error("[createExceptionLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
-//        }
+    private void createExceptionLog(HttpServletRequest req, Throwable e) {
+        // 插入错误日志
+        ApiErrorLogCreateDTO errorLog = new ApiErrorLogCreateDTO();
+        try {
+            // 初始化 errorLog
+            initExceptionLog(errorLog, req, e);
+            // 执行插入 errorLog
+            apiErrorLogFrameworkService.createApiErrorLogAsync(errorLog);
+        } catch (Throwable th) {
+            log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(),  JsonUtils.toJsonString(errorLog), th);
+        }
     }
 
-//    // TODO 优化点:后续可以增加事件
-//    @Async
-//    public void createExceptionLog(SystemExceptionLogCreateDTO exceptionLog) {
-//        try {
-//            systemExceptionLogRpc.createSystemExceptionLog(exceptionLog);
-//        } catch (Throwable th) {
-//            log.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
-//        }
-//    }
-//
-//    private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) {
-//        // 设置账号编号
-//        exceptionLog.setUserId(CommonWebUtil.getUserId(request));
-//        exceptionLog.setUserType(CommonWebUtil.getUserType(request));
-//        // 设置异常字段
-//        exceptionLog.setExceptionName(e.getClass().getName());
-//        exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
-//        exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
-//        exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e));
-//        StackTraceElement[] stackTraceElements = e.getStackTrace();
-//        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
-//        StackTraceElement stackTraceElement = stackTraceElements[0];
-//        exceptionLog.setExceptionClassName(stackTraceElement.getClassName());
-//        exceptionLog.setExceptionFileName(stackTraceElement.getFileName());
-//        exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName());
-//        exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
-//        // 设置其它字段
-//        exceptionLog.setTraceId(MallUtils.getTraceId())
-//                .setApplicationName(applicationName)
-//                .setUri(request.getRequestURI())
-//                .setQueryString(HttpUtil.buildQueryString(request))
-//                .setMethod(request.getMethod())
-//                .setUserAgent(HttpUtil.getUserAgent(request))
-//                .setIp(HttpUtil.getIp(request))
-//                .setExceptionTime(new Date());
-//    }
+    private void initExceptionLog(ApiErrorLogCreateDTO errorLog, HttpServletRequest request, Throwable e) {
+        // 处理用户信息
+        errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
+        errorLog.setUserType(WebFrameworkUtils.getUesrType(request));
+        // 设置异常字段
+        errorLog.setExceptionName(e.getClass().getName());
+        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
+        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
+        errorLog.setExceptionStackTrace(ExceptionUtils.getStackTrace(e));
+        StackTraceElement[] stackTraceElements = e.getStackTrace();
+        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
+        StackTraceElement stackTraceElement = stackTraceElements[0];
+        errorLog.setExceptionClassName(stackTraceElement.getClassName());
+        errorLog.setExceptionFileName(stackTraceElement.getFileName());
+        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
+        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
+        // 设置其它字段
+        errorLog.setTraceId(TracerUtils.getTraceId());
+        errorLog.setApplicationName(applicationName);
+        errorLog.setRequestUrl(request.getRequestURI());
+        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
+                .put("query", ServletUtil.getParamMap(request))
+                .put("body", ServletUtil.getBody(request)).build();
+        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
+        errorLog.setRequestMethod(request.getMethod());
+        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
+        errorLog.setUserIp(ServletUtil.getClientIP(request));
+        errorLog.setExceptionTime(new Date());
+    }
 
 }

+ 1 - 1
src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java

@@ -31,7 +31,7 @@ public class WebFrameworkUtils {
         return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);
     }
 
-    public static Integer getUsrType(HttpServletRequest request) {
+    public static Integer getUesrType(HttpServletRequest request) {
         return UserTypeEnum.ADMIN.getValue(); // TODO 芋艿:等后续优化
     }
 

+ 4 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiAccessLogDO.java

@@ -51,6 +51,8 @@ public class InfApiAccessLogDO extends BaseDO {
      */
     private String applicationName;
 
+    // ========== 请求相关字段 ==========
+
     /**
      * 请求方法名
      */
@@ -75,6 +77,8 @@ public class InfApiAccessLogDO extends BaseDO {
      */
     private String userAgent;
 
+    // ========== 执行相关字段 ==========
+
     /**
      * 开始请求时间
      */

+ 149 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java

@@ -1,4 +1,152 @@
 package cn.iocoder.dashboard.modules.infra.dal.dataobject.logger;
 
-public class InfApiErrorLogDO {
+import cn.iocoder.dashboard.common.enums.UserTypeEnum;
+import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.dashboard.modules.infra.enums.logger.ApiErrorLogProcessStatusEnum;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.util.Date;
+
+/**
+ * API 异常数据
+ *
+ * @author 芋道源码
+ */
+@TableName("inf_api_error_log")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfApiErrorLogDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    private Integer id;
+    /**
+     * 用户编号
+     */
+    private Integer userId;
+    /**
+     * 链路追踪编号
+     *
+     * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
+     */
+    private String traceId;
+    /**
+     * 用户类型
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 应用名
+     *
+     * 目前读取 spring.application.name
+     */
+    private String applicationName;
+
+    // ========== 请求相关字段 ==========
+
+    /**
+     * 请求方法名
+     */
+    private String requestMethod;
+    /**
+     * 访问地址
+     */
+    private String requestUrl;
+    /**
+     * 请求参数
+     *
+     * query: Query String
+     * body: Quest Body
+     */
+    private String requestParams;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 浏览器 UA
+     */
+    private String userAgent;
+
+    // ========== 异常相关字段 ==========
+
+    /**
+     * 异常发生时间
+     */
+    private Date exceptionTime;
+    /**
+     * 异常名
+     *
+     * {@link Throwable#getClass()} 的类全名
+     */
+    private String exceptionName;
+    /**
+     * 异常导致的消息
+     *
+     * {@link cn.hutool.core.exceptions.ExceptionUtil#getMessage(Throwable)}
+     */
+    private String exceptionMessage;
+    /**
+     * 异常导致的根消息
+     *
+     * {@link cn.hutool.core.exceptions.ExceptionUtil#getRootCauseMessage(Throwable)}
+     */
+    private String exceptionRootCauseMessage;
+    /**
+     * 异常的栈轨迹
+     *
+     * {@link org.apache.commons.lang3.exception.ExceptionUtils#getStackTrace(Throwable)}
+     */
+    private String exceptionStackTrace;
+    /**
+     * 异常发生的类全名
+     *
+     * {@link StackTraceElement#getClassName()}
+     */
+    private String exceptionClassName;
+    /**
+     * 异常发生的类文件
+     *
+     * {@link StackTraceElement#getFileName()}
+     */
+    private String exceptionFileName;
+    /**
+     * 异常发生的方法名
+     *
+     * {@link StackTraceElement#getMethodName()}
+     */
+    private String exceptionMethodName;
+    /**
+     * 异常发生的方法所在行
+     *
+     * {@link StackTraceElement#getLineNumber()}
+     */
+    private Integer exceptionLineNumber;
+
+    // ========== 处理相关字段 ==========
+
+    /**
+     * 处理状态
+     *
+     * 枚举 {@link ApiErrorLogProcessStatusEnum}
+     */
+    private Integer processStatus;
+    /**
+     * 处理时间
+     */
+    private Date processTime;
+    /**
+     * 处理管理员编号
+     *
+     * 关联 {@link}
+     */
+    private Integer processUserId;
+
 }

+ 28 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/enums/logger/ApiErrorLogProcessStatusEnum.java

@@ -0,0 +1,28 @@
+package cn.iocoder.dashboard.modules.infra.enums.logger;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * API 异常数据的处理状态
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum ApiErrorLogProcessStatusEnum {
+
+    INIT(0, "未处理"),
+    DONE(1, "已处理"),
+    IGNORE(2, "已忽略");
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 资源类型名
+     */
+    private final String name;
+
+}

+ 12 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/InfApiErrorLogService.java

@@ -0,0 +1,12 @@
+package cn.iocoder.dashboard.modules.infra.service.logger;
+
+import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiErrorLogFrameworkService;
+
+/**
+ * API 错误日志 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfApiErrorLogService extends ApiErrorLogFrameworkService {
+
+}

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiAccessLogServiceImpl.java

@@ -30,7 +30,7 @@ public class InfApiAccessLogServiceImpl implements InfApiAccessLogService {
 
     @Override
     @Async
-    public void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO) {
+    public void createApiAccessLogAsync(ApiAccessLogCreateDTO createDTO) {
         // 插入
         InfApiAccessLogDO apiAccessLog = InfApiAccessLogConvert.INSTANCE.convert(createDTO);
         apiAccessLogMapper.insert(apiAccessLog);

+ 24 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiErrorLogServiceImpl.java

@@ -0,0 +1,24 @@
+package cn.iocoder.dashboard.modules.infra.service.logger.impl;
+
+import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO;
+import cn.iocoder.dashboard.modules.infra.service.logger.InfApiErrorLogService;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * API 错误日志 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfApiErrorLogServiceImpl implements InfApiErrorLogService {
+
+    @Override
+    @Async
+    public void createApiErrorLogAsync(ApiErrorLogCreateDTO createDTO) {
+
+    }
+
+}