自定义Log切面日志

一、自定义Log注解

    • 代码块

/**
 * @Author: AaronJonah
 * @Description: 自自定义 log aspect切面
 * @Date Created in 2022−12-28 17:45
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface Log {
    /**
     *
     */
    String value() default "";

    /**
     * 是否开启日志
     */
    boolean enabled() default true;

    /**
     * 是否打印入参日志,默认是
     */
    boolean reqLog() default true;

    /**
     * 是否打印出参日志,默认是
     */
    boolean resLog() default true;

    /**
     * 是否打印耗时日志,默认是
     */
    boolean cost() default true;

}

2.注解说明

  1. @Documented

如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成javadoc文档时,会显示@B。如果@B没有被@Documented标准,最终生成的文档中就不会显示@B。只是用来做标识,没什么实际作用。

  1. @Retention(RetentionPolicy.RUNTIME)

按生命周期 可分为3类:

  • RetentionPolicy.SOURCE:注解只保留源文件,当Java文件编译成class文件的时候,注解被遗弃;

  • RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

  • RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

那怎么来选择合适的注解生命周期呢?

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

详情参考:@Retention(RetentionPolicy.RUNTIME)-CSDN博客

  1. @Target(value = ElementType.METHOD)

修饰注解目标作用范围

ElementType.TYPE:接口、类、枚举、注解

ElementType.FIELD:字段、枚举常量 声明

ElementType.METHOD:方法声明

ElementType.PARAMETER:参数声明

ElementType.CONSTRUCTOR:构造函数声明

ElementType.LOCAL_VARIABLE:局部变量声明

ElementType.ANNOTATION_TYPE:注解声明

ElementType.PACKAGE:包声明

ElementType.TYPE_PARAMETER:类型参数声明(jdk 1.8 @since 1.8)

ElementType.TYPE_USE:使用的类型声明(jdk 1.8 @since 1.8)

二、定义Log切面类

1.代码块


/**
 * @author AaronJonah
 * @Description Log切面配置
 * @Date 2022/12/28 18:21
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 请求Id
     */
    private static final String REQ_ID = "requestId";

    /**
     * @Author AaronJonah
     * @Description 标注切点位置
     * @Date 2022/12/28 19:37
     */
    @Pointcut("@annotation(com.jdaz.ins.galaxy.settlement.service.annotation.Log)")
    public void pointcutInterface() {
    }

    /**
     * @return Object
     * @Author AaronJonah
     * @Description 切点执行
     * @Param joinPoint
     * @Date 2022/12/29 9:23
     */
    @Order(2)   //定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序
    @Around("pointcutInterface()")  // 增强方法处理
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Map<String, String> mdc = MDC.getCopyOfContextMap();
        //获取调用类 和方法
        String key = joinPoint.getTarget().getClass().getSimpleName() + "#" + getMethodName(joinPoint) + "()";
        // 请求参数
        Object[] args = joinPoint.getArgs();
        // 日志说明
        String logDesc = StringUtils.EMPTY;
        // 是否输出log日志 true-打印 false-不打印
        boolean reqLog = true, resLog = false, cost = true;
        Log insLog = getLog(joinPoint);
        if (null != insLog) {
            if (!insLog.enabled()) {
                return joinPoint.proceed();
            }
            logDesc = insLog.value();
            reqLog = insLog.reqLog();
            resLog = insLog.resLog();
            cost = insLog.cost();
        }
        //获取请求id
        String reqId = MDC.get(REQ_ID);
        if (StringUtils.isBlank(reqId)) {
            reqId = System.currentTimeMillis() + "-" + Arrays.hashCode(args);
            MDC.put(REQ_ID, reqId);
        }
        // log请求json字符串
        String logReqJson = getLogReqJson(args);
        Object res;
        if (reqLog) {
            log.info("【Log {}请求】{},参数={}", logDesc, key, logReqJson);
        }
        long beginMs = System.currentTimeMillis();
        try {
            // 调用目标方法
            res = joinPoint.proceed();
            // 由于多线程,会导致MDC清空,此处补全
            if (StringUtils.isBlank(MDC.get(REQ_ID))) {
                MDC.setContextMap(mdc);
            }
            long costTime = System.currentTimeMillis() - beginMs;
            if (resLog) {
                log.info("【Log {}返回】{}{},结果={}", logDesc, key, cost ? " 耗时=" + costTime + "ms" : "", JSONObject.toJSONString(res, SerializerFeature.DisableCircularReferenceDetect));
            } else if (cost) {
                log.info("【Log {}返回】{} 耗时={}ms", logDesc, key, costTime);
            }
            return res;
        } catch (Exception e) {
            String errorMsg = e.getMessage();
            log.error("【Log {}异常】{} {}{}", logDesc, key, errorMsg, reqLog ? ",参数=" + logReqJson : "", e);
            throw e;
        }

    }

    /**
     * @return String
     * @Author AaronJonah
     * @Description 获取Log请求参数JSON字符串
     * @Param args
     * @Date 2022/12/29 10:10
     */
    private String getLogReqJson(Object[] args) {
        if (null == args || args.length == 0) {
            return StringUtils.EMPTY;
        }
        // 无需序列化输出的字段
        PropertyFilter filter = (arg, name, val) -> {
            if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
                return false;
            }
            if (arg instanceof InputStream || arg instanceof MultipartFile) {
                return false;
            }
            return !(arg instanceof Exception);
        };
        try {
            return JSONObject.toJSONString(args, filter, SerializerFeature.DisableCircularReferenceDetect);
        } catch (Exception e) {
            log.error("getLogReqJson Exception");
            return StringUtils.EMPTY;
        }
    }

    /**
     * @return Log
     * @Author AaronJonah
     * @Description 获取Log日志
     * @Param joinPoint
     * @Date 2022/12/29 9:59
     */
    private Log getLog(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            return methodSignature.getMethod().getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * @return String
     * @Author AaronJonah
     * @Description 获取方法名
     * @Param joinPoint
     * @Date 2022/12/29 9:35
     */
    private String getMethodName(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            return methodSignature.getMethod().getName();
        }
        return "未获取到方法";
    }


}

2.注解说明

  1. @Order(2)

注解定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序。

  • 注解可以作用在类(接口、枚举)、方法、字段声明(包括枚举常量)。

  • 注解有一个int类型的参数,可以不传,默认是最低优先级。

  • 通过常量类的值我们可以推测参数值越小优先级越高。

  1. @Around("pointcutInterface()") Spring AOP 增强处理

  • 既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作。

  • 可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行。

  • 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法。

  • 虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。

详情参考:@Around简单使用示例——SpringAOP增强处理_around方法是在什么时候发生-CSDN博客

三、使用方式

    • 代码块

/**
     * @return ResponseEntity
     * @Author AaronJonah
     * @Description 数据推送失败再次推送
     * @Param smallPolicyDTO
     * @Date 2022/11/2 15:06
     */
    @PostMapping(value = "/pushFailedAgainPush")
    @Log(value = "数据推送失败再次推送", resLog = false)
    public ResponseEntity pushFailedAgainPush(@RequestBody SettlementSmallPolicyDTO smallPolicyDTO) {
        if (PUSH_CODE.equals(smallPolicyDTO.getCode())) {
            settlementSmallPolicyService.pushFailedAgainPush(smallPolicyDTO);
            return Replys.ok("数据推送失败再次推送成功");
        }
        return Replys.fail("失败", "数据推送失败再次推送失败");
    }

    • 日志

com.jdaz.ins.galaxy.settlement.service.aspect.LogAspect.doAroundAdvice(LogAspect.java:89)||【Log 数据推送失败再次推送请求】ReSgBillCheckDetailController#pushFailedAgainPush(),参数=[{"checkTaskNo":"DZ20211228016","code":"jdaz","endUpdateTime":1696089600000,"insType":"JXAZ0002","isSummary":false,"planCode":"BZX-XFZLY-JDSC-POPJBX-30D","pushSgStatus":1,"pushedFlag":true,"sgRemoveAccountStatus":0,"updateTime":1633017600000}]
com.jdaz.ins.galaxy.settlement.service.aspect.LogAspect.doAroundAdvice(LogAspect.java:103)||【Log 数据推送失败再次推送返回】ReSgBillCheckDetailController#pushFailedAgainPush() 耗时=2713ms
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值