LogAdvice

简介: `LogAdvice` 类是用于日志记录的 AOP(面向切面编程)组件。它定义了在带有 `@Log` 注解的方法执行前后进行操作的切点。在方法调用前,它记录请求开始时间、描述、URL、参数和 headers。方法成功返回后,记录请求的执行时间和响应。类还包含一些辅助方法,如判断是否为 Feign 请求或控制器请求,并获取请求的相关信息。
package integration.advice;

import cn.hutool.core.util.ReflectUtil;
import integration.annotation.Log;
import integration.exception.NormalBreakException;
import integration.util.Base64Utils;
import integration.util.HttpRequestUtils;
import integration.util.LoginUtil;
import integration.util.RequestThreadLocal;
import integration.util.TryCatchUtil;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static integration.configuration.CommonConfig.isPrd;
import static integration.constant.ThreadConstants.START_TIME;
import static integration.util.AnnotationUtil.*;
import static integration.util.ErrorLogRecord.recordException;

@Component
@Aspect
@Order(110)
@Slf4j
public class LogAdvice {

    private static final String[] ERROR_SUFFIX_URL = new String[]{"not postMapping or getMapping!"};
    private static final String FEIGN_TARGET_CLASS_NAME = "HardCodedTarget";
    private static final String FIELD_TARGET = "target";

    @Pointcut("@within(com.ph.sp.integration.annotation.Log)")
    public void logCut() {
    }

    @Before("logCut()")
    public void before(JoinPoint joinPoint) {
        Map<String, Object> stackValue = RequestThreadLocal.getStack();
        if (stackValue != null) {
            stackValue.put(costKey(joinPoint), System.currentTimeMillis());
        } else {
            stackValue = new HashMap<>();
            stackValue.put(costKey(joinPoint), System.currentTimeMillis());
            RequestThreadLocal.setRequestStack(stackValue);
        }
        Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
        LogAdvice.log.info("==={} request start=== 【desc】: {} 【url】: {} 【param】: {} 【headers】:{} ",
                type(joinPoint), desc(joinPoint), url(joinPoint), printObj(log.encrypt(), joinPoint.getArgs()), LoginUtil.getHeaders());
    }

    /**
     * 判断当前请求是app请求还是feign请求
     */
    private String type(JoinPoint pjp) {
        Class<?> tClass = pjp.getTarget().getClass();
        if (isFeignClass(tClass)) {
            Target.HardCodedTarget<?> target = getFeignTarget(pjp);
            return target != null ? target.name() + " Feign" : "Feign";
        } else if (isController(tClass)) {
            return "APP";
        }
        return "??";
    }

    /**
     * todo 接口性能分析,max,min,avg,99line,95line,QPS
     * methodName set<time>,
     */
    @AfterReturning(pointcut = "logCut()", returning = "result")
    public void after(JoinPoint joinPoint, Object result) {
        Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
        LogAdvice.log.info("==={} request success=== 【desc】: {} 【url】: {} 【cost】: {}ms  【resp】: {}",
                type(joinPoint), desc(joinPoint), url(joinPoint), cost(joinPoint), printObj(log.encrypt(), result));
    }

    @AfterThrowing(pointcut = "logCut()", throwing = "t")
    public void exception(JoinPoint joinPoint, Throwable t) {
        if (t instanceof NormalBreakException) {
            Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
            LogAdvice.log.info("==={} request success=== 【desc】: {} 【url】: {} 【cost】: {}ms  【resp】: {}",
                    type(joinPoint), desc(joinPoint), url(joinPoint), cost(joinPoint), printObj(log.encrypt(), ((NormalBreakException) t).getDefaultData()));
            return;
        }
        recordException(t, "LogAdviceError, msg:{} 【url】: {}  【cost】: {}ms  【req】: {} 【headers】:{} ",
                t.toString(), url(joinPoint), cost(joinPoint), joinPoint.getArgs(), LoginUtil.getHeaders());
    }

    private String costKey(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        return START_TIME + method.toString();
    }

    private long cost(JoinPoint joinPoint) {
        Map<String, Object> stackValue = RequestThreadLocal.getStack();
        long startTime = (long) stackValue.get(costKey(joinPoint));
        return System.currentTimeMillis() - startTime;
    }

    private String desc(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        PostMapping annotation = method.getAnnotation(PostMapping.class);
        return annotation != null ? annotation.name() : "";
    }

    private String url(JoinPoint pjp) {
        Class<?> tClass = pjp.getTarget().getClass();
        if (isFeignClass(tClass)) {
            return getFeignUrlPre(pjp) + getUrlSuf(pjp);
        } else if (isController(tClass)) {
            return HttpRequestUtils.getRequestUrl();
        } else {
            recordException("not feign or controller request, actual:" + tClass);
            return "not feign or controller request ";
        }
    }

    private String getFeignUrlPre(JoinPoint pjp) {
        Target.HardCodedTarget<?> target = getFeignTarget(pjp);
        return target == null ? "" : target.url() + target.type().getAnnotation(FeignClient.class).path();
    }

    private static String getUrlSuf(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        return Optional.ofNullable(method.getAnnotation(PostMapping.class)).map(PostMapping::value)
                .orElseGet(() -> Optional.ofNullable(method.getAnnotation(GetMapping.class)).map(GetMapping::value)
                        .orElse(ERROR_SUFFIX_URL))[0];
    }

    private Target.HardCodedTarget<?> getFeignTarget(JoinPoint pjp) {
        Proxy target = (Proxy) pjp.getTarget();
        if (target.toString().contains(FEIGN_TARGET_CLASS_NAME)) {
            InvocationHandler feignInvocationHandler = Proxy.getInvocationHandler(target);
            return (Target.HardCodedTarget<?>) ReflectUtil.getFieldValue(feignInvocationHandler, FIELD_TARGET);
        } else {
            recordException("have @FeignClient but target is not HardCodedTarget, actual:" + target.toString());
            return null;
        }
    }

    private Object printObj(boolean encrypt, Object obj) {
        return isPrd && encrypt ? encrypt(obj) : obj;
    }

    private Object encrypt(Object obj) {
        return TryCatchUtil.ignoreError(() -> Base64Utils.encode64(obj), obj);
    }

}
相关文章
|
6月前
|
缓存 API 定位技术
使用Python调用百度地图API实现地址查询
使用Python调用百度地图API实现地址查询
353 0
|
开发者
语雀
简要讲述语雀文档编辑器的注册使用及个人感受
语雀
|
2月前
|
存储 缓存 NoSQL
webFilter实现mock接口
这段代码实现了一个名为 `MockFilter` 的类,继承自 `WebFilter` 接口,用于处理 HTTP 请求和响应。它通过从 Redis 缓存中获取配置信息来决定是否使用模拟数据或缓存数据来响应请求。如果开启了生产模式或关闭了模拟和缓存功能,则直接放行请求。否则,它会检查请求体并根据配置返回相应的模拟或缓存数据。同时,该过滤器支持对响应结果进行处理,并将结果存储回 Redis 中。
|
2月前
|
存储 NoSQL Java
aspect实现mock-feign接口
该代码为一个用于Feign接口的模拟(Mock)实现类`FeignMockAspect`,通过切面编程方式对带有`@FeignClient`注解的接口提供模拟响应。在非生产环境中,根据特定配置从Redis中获取Mock数据并转换为对应类型的对象返回,以减少对外部系统的依赖和提高测试效率。使用Hutool工具类和Spring Data Redis进行数据处理与存储操作。
|
4月前
|
机器学习/深度学习 Kubernetes 云计算
阿里云的「技术文档工程师」和「技术翻译」
- 阿里云智能集团招聘技术岗,位于杭州和北京。 - 技术文档工程师岗位要求包括独立编写代码能力、快速学习新技术、简化复杂技术概念、扎实的技术理解和良好的时间管理。 - 翻译工程师还需具备相关学历背景、技术翻译经验和云产品知识。 **团队成员分享:** - 昱心(南洋理工大学,机器学习)和骞腾(UIUC,计算机科学)分享了他们在技术文档岗位上的成长,涉及大模型和K8S等技术。 - 舟预(北京交通大学,信息管理)强调技术文档的重要性,认为它是阿里云对外的权威发言人。 - 天蒙(南开大学,信息与通信工程)提到工作中与代码的紧密联系,团队支持技术成长。
23811 24
阿里云的「技术文档工程师」和「技术翻译」
|
6月前
|
JSON 数据安全/隐私保护 数据格式
动态切面
`AdviceConfiguration` 类用于动态注册一个基于 AspectJ 的切面顾问,该顾问通过 `@ConditionalOnExpression` 注解控制是否生效。配置中包含一个从 Apollo 获取的 JSON 值,用于构建方法拦截器的切入点表达式,涉及特定包和类。拦截器 `ControllerAdvice` 实现了 `MethodInterceptor`,用于记录请求日志,包括 URL、参数和执行时间,同时根据配置决定是否对返回结果进行加密。
|
6月前
|
NoSQL Redis
RedisUtils+切面日志
redis常用工具类,包括string/hash/list/set基本类型操作,redisson发布订阅功能
|
Cloud Native Go 项目管理
如何在技术面试中展示领导能力
如何在技术面试中展示领导能力
173 0
springBoot feign请求日志切面
springBoot feign请求日志切面
热部署instrumentation工具类
利用jvm的instrumentation类实现实时修改class文件,用来更新线上代码。