文章目录

  • 1. 背景
  • 2. 使用AOP+注解来自定义配置日志打印
  • 3. 使用dubbo filter+注解来自定义配置日志打印


1. 背景

近期服务器的磁盘总是报内存不够,排查了一下发现日志暴涨,业务量增加是无可避免的,翻了一下日志,发现很多接口把入参和出参都打印了,比如分页数据结果十分庞大,属于无效日志,不应该打印。为了自由地控制接口入参和出参的打印,做了一个决定:入参必须打,出参可自定义打印。

自定义打印可使用方法上的注解来解决,如@LogAnnotation,

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface LogAnnotation {

    /**
     * 是否打印接口出参
     */
    boolean logResult() default true;
}

若方法上加上了

@LogAnnotation(logResult()=false)

就不打印接口返回结果,思路有以下两种:

  • 使用AOP+注解来自定义配置日志打印
  • 使用dubbo filter+注解来自定义配置日志打印

第一种可使用ProceedingJoinPoint来获取目标方法,第二种方法困难在于如何从invoker中获取目标方法。

2. 使用AOP+注解来自定义配置日志打印

@Component
@Aspect
public class GlobalExceptionAspect {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionAspect.class);

    //对ao层得异常进行全局拦截
    @Around("execution(com.yt.asd.common.domain.RpcResult com.yt.cmc.biz.ao..*.*(..))")
    public RpcResult doProcess(ProceedingJoinPoint point) {
        String targetClass = point.getTarget().getClass().getSimpleName();
        MethodSignature signature = (MethodSignature) point.getSignature();
        String methodName = signature.getMethod().getName();
        RpcResult result = RpcResult.newInstance();
        try {

            log.info("[{}-{}] 请求参数:param:{}, 请求时间:{}", targetClass, methodName, Arrays.asList(point.getArgs()), DateUtil.dateToString(new Date()));
          
            Stopwatch stopwatch = Stopwatch.createStarted();
            // 方法执行
            result = (RpcResult) point.proceed();
          
            stopwatch.stop();
          
            // 若取执行方法
            Method method = point.getTarget().getClass().getMethod(methodName, signature.getMethod().getParameterTypes());
          
            if(logResponse(method)) {
                // 打印返回值
                log.info("[{}-{}] success, 返回结果:{}, 消耗:{}, 时间:{}", targetClass, methodName, JSON.toJSONString(result), stopwatch.toString(), DateUtil.dateToString(new Date()));
            } else {
                // 不打印返回值
                log.info("[{}-{}] success, 消耗:{}, 时间:{}", targetClass, methodName, stopwatch.toString(), DateUtil.dateToString(new Date()));
            }
            return result;
          
        } catch (Exception e) {
          //异常处理......
        }
    }

    private boolean logResponse(Method method) {
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
        if (annotation == null) {
            annotation = method.getDeclaringClass().getAnnotation(LogAnnotation.class);
        }
        if(annotation == null) {
            return true;
        }
        return annotation.logResult();
    }

}

上面在若取执行方法处,即

Method method = point.getTarget().getClass().getMethod(methodName, signature.getMethod().getParameterTypes());

由于dubbo对外方法都是采用动态代理的方式(默认Javassist动态代理),若直接使用以下方法来获取Method的话,只会获取到方法的签名信息,如:

public abstract com.yt.asd.common.domain.RpcResult com.yt.cmc.shop.api.BActExtendApi.getActInfo(com.yt.cmc.shop.domain.query.BactGetQuery)

而且从上面签名信息中获取不到方法上标注的注解信息。

Method method = point.getSignature()).getMethod();

除了上面的方式,也可采用以下方式来获取真正执行的Method。

Method method = ClassUtils.getMostSpecificMethod(signature.getMethod(), point.getTarget().getClass());

// method打印出
public com.yt.asd.common.domain.RpcResult com.yt.cmc.biz.ao.shop.BActExtendApiImpl.getActInfo(com.yt.cmc.shop.domain.query.BactGetQuery)

这样可通过 LogAnnotation 注解的 logResult 属性值来决定是否打印返回结果日志。

3. 使用dubbo filter+注解来自定义配置日志打印

这里使用的dubbo默认javassist动态代理

@Component
@Slf4j
public class LogFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String className = invoker.getInterface().getName();
        String methodName = invocation.getMethodName();
        String arguments = JSON.toJSONString(invocation.getArguments());

        try {
            log.info("[{}-{}] 请求参数:param:{}, 请求时间:{}", className, methodName, arguments, DateUtil.getFormantDate(new Date(), DateUtil.MILLS_FORMANT));
            Stopwatch stopwatch = Stopwatch.createStarted();

            Result invoke = invoker.invoke(invocation);

            stopwatch.stop();

            String value = JSON.toJSONString(invoke.getValue());


            Method signatureMethod = invoker.getInterface().getMethod(methodName, invocation.getParameterTypes());
            Class targetClass = getTargetClass(invoker).getClass();
            Method method = ClassUtils.getMostSpecificMethod(signatureMethod, targetClass);

            log.info("signatureMethod:{},targetClass:{}, method:{}", signatureMethod, targetClass, method);

            if(logResponse(method)) {
                // 打印返回值
                log.info("[{}-{}] success, 返回结果:{}, 消耗:{}, 时间:{}", className, methodName, value, stopwatch.toString(), DateUtil.getFormantDate(new Date(), DateUtil.MILLS_FORMANT));
            } else {
                // 不打印返回值
                log.info("[{}-{}] success, 消耗:{}, 时间:{}", className, methodName, stopwatch.toString(), DateUtil.getFormantDate(new Date(), DateUtil.MILLS_FORMANT));
            }
            return invoke;
        } catch (Exception e) {
        }

        return null;
    }


    private boolean logResponse(Method method) {
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
        if (annotation == null) {
            annotation = method.getDeclaringClass().getAnnotation(LogAnnotation.class);
        }
        if(annotation == null) {
            return true;
        }
        return annotation.logResult();
    }

    /**
    ** 获取invoker的目标方法
    **/
    private Object getTargetClass(Object invoker) throws Exception {

        Field invokerField = invoker.getClass().getDeclaredField("invoker");
        invokerField.setAccessible(true);
        AbstractProxyInvoker javassistProxyFactoryInvoker = (AbstractProxyInvoker)invokerField.get(invoker);

        Field proxy = javassistProxyFactoryInvoker.getClass().getSuperclass().getDeclaredField("proxy");
        proxy.setAccessible(true);
        Object proxyValue = proxy.get(javassistProxyFactoryInvoker);

        Field jdkDynamicAopProxy = proxyValue.getClass().getSuperclass().getDeclaredField("h");
        jdkDynamicAopProxy.setAccessible(true);
        Object jdkDynamicAopProxyValue = jdkDynamicAopProxy.get(proxyValue);

        Field advised = jdkDynamicAopProxyValue.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        return ((AdvisedSupport) advised.get(jdkDynamicAopProxyValue)).getTargetSource().getTarget();
    }

}

Dubbo Filter的配置信息就不再简介。