范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

写了个牛逼的日志切面,甩锅更方便了

  最近项目进入联调阶段,服务层的接口需要和协议层进行交互,协议层需要将入参 [json 字符串] 组装成服务层所需的 json 字符串,组装的过程中很容易出错。
  入参出错导致接口调试失败问题在联调中出现很多次,因此就想写一个请求日志切面把入参信息打印一下,同时协议层调用服务层接口名称对不上也出现了几次,通过请求日志切面就可以知道上层是否有没有发起调用,方便前后端甩锅还能拿出证据。
  写在前面
  本篇文章是实战性的,对于切面的原理不会讲解,只会简单介绍一下切面的知识点
  切面介绍
  面向切面编程是一种编程范式,它作为 OOP 面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如 事务管理 、 权限控制 、 缓存控制 、 日志打印 等等。
  AOP 把软件的功能模块分为两个部分:核心关注点和横切关注点。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为 横切 关注点。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离,使用切面有以下好处: 集中处理某一关注点 / 横切逻辑 可以很方便的添加 / 删除关注点 侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码 0 侵入 切面的使用【基于注解】@Aspect => 声明该类为一个注解类
  切点注解: @Pointcut => 定义一个切点,可以简化代码
  通知注解: @Before => 在切点之前执行代码 @After => 在切点之后执行代码 @AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装 @AfterThrowing => 切点抛出异常后执行 @Around => 环绕,在切点前后执行代码 动手写一个请求日志切面使用 @Pointcut 定义切点@Pointcut("execution(* your_package.controller..*(..))")   public void requestServer() {   }  
  @Pointcut 定义了一个切点,因为是请求日志切边,因此切点定义的是 Controller 包下的所有类下的方法。定义切点以后在通知注解中直接使用 requestServer 方法名就可以了 使用 @Before 再切点前执行@Before("requestServer()")   public void doBefore(JoinPoint joinPoint) {    ServletRequestAttributes attributes = (ServletRequestAttributes)   RequestContextHolder.getRequestAttributes();    HttpServletRequest request = attributes.getRequest();       LOGGER.info("===============================Start========================");    LOGGER.info("IP                 : {}", request.getRemoteAddr());    LOGGER.info("URL                : {}", request.getRequestURL().toString());    LOGGER.info("HTTP Method        : {}", request.getMethod());    LOGGER.info("Class Method       : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());   }  
  在进入 Controller 方法前,打印出调用方 IP、请求 URL、HTTP 请求类型、调用的方法名 使用 @Around 打印进入控制层的入参@Around("requestServer()")   public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {    long start = System.currentTimeMillis();    Object result = proceedingJoinPoint.proceed();    LOGGER.info("Request Params       : {}", getRequestParams(proceedingJoinPoint));    LOGGER.info("Result               : {}", result);    LOGGER.info("Time Cost            : {} ms", System.currentTimeMillis() - start);       return result;   }  
  打印了入参、结果以及耗时 getRquestParams 方法private Map getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {     Map requestParams = new HashMap<>();         //参数名     String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();     //参数值     Object[] paramValues = proceedingJoinPoint.getArgs();        for (int i = 0; i < paramNames.length; i++) {     Object value = paramValues[i];        //如果是文件对象     if (value instanceof MultipartFile) {     MultipartFile file = (MultipartFile) value;     value = file.getOriginalFilename();  //获取文件名     }        requestParams.put(paramNames[i], value);     }        return requestParams;    }  
  通过 @PathVariable 以及 @RequestParam 注解传递的参数无法打印出参数名,因此需要手动拼接一下参数名,同时对文件对象进行了特殊处理,只需获取文件名即可 @After 方法调用后执行@After("requestServer()")   public void doAfter(JoinPoint joinPoint) {    LOGGER.info("===============================End========================");   }  
  没有业务逻辑只是打印了 End 完整切面代码@Component   @Aspect   public class RequestLogAspect {    private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);       @Pointcut("execution(* your_package.controller..*(..))")    public void requestServer() {    }       @Before("requestServer()")    public void doBefore(JoinPoint joinPoint) {    ServletRequestAttributes attributes = (ServletRequestAttributes)   RequestContextHolder.getRequestAttributes();    HttpServletRequest request = attributes.getRequest();       LOGGER.info("===============================Start========================");    LOGGER.info("IP                 : {}", request.getRemoteAddr());    LOGGER.info("URL                : {}", request.getRequestURL().toString());    LOGGER.info("HTTP Method        : {}", request.getMethod());    LOGGER.info("Class Method       : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),    joinPoint.getSignature().getName());    }          @Around("requestServer()")    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {    long start = System.currentTimeMillis();    Object result = proceedingJoinPoint.proceed();    LOGGER.info("Request Params     : {}", getRequestParams(proceedingJoinPoint));    LOGGER.info("Result               : {}", result);    LOGGER.info("Time Cost            : {} ms", System.currentTimeMillis() - start);       return result;    }       @After("requestServer()")    public void doAfter(JoinPoint joinPoint) {    LOGGER.info("===============================End========================");    }       /**     * 获取入参     * @param proceedingJoinPoint     *     * @return     * */    private Map getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {    Map requestParams = new HashMap<>();       //参数名    String[] paramNames =   ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();    //参数值    Object[] paramValues = proceedingJoinPoint.getArgs();       for (int i = 0; i < paramNames.length; i++) {    Object value = paramValues[i];       //如果是文件对象    if (value instanceof MultipartFile) {    MultipartFile file = (MultipartFile) value;    value = file.getOriginalFilename();  //获取文件名    }       requestParams.put(paramNames[i], value);    }       return requestParams;    }   }  高并发下请求日志切面
  写完以后对自己的代码很满意,但是想着可能还有完善的地方就和朋友交流了一下。emmmm
  果然还有继续优化的地方 每个信息都打印一行,在高并发请求下确实会出现请求之间打印日志串行的问题,因为测试阶段请求数量较少没有出现串行的情况,果然生产环境才是第一发展力,能够遇到更多 bug,写更健壮的代码 解决日志串行的问题只要将多行打印信息合并为一行就可以了,因此构造一个对象 RequestInfo.java@Data   public class RequestInfo {    private String ip;    private String url;    private String httpMethod;    private String classMethod;    private Object requestParams;    private Object result;    private Long timeCost;   }  环绕通知方法体@Around("requestServer()")   public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {    long start = System.currentTimeMillis();    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();    HttpServletRequest request = attributes.getRequest();    Object result = proceedingJoinPoint.proceed();    RequestInfo requestInfo = new RequestInfo();    requestInfo.setIp(request.getRemoteAddr());    requestInfo.setUrl(request.getRequestURL().toString());    requestInfo.setHttpMethod(request.getMethod());    requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),    proceedingJoinPoint.getSignature().getName()));    requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));    requestInfo.setResult(result);    requestInfo.setTimeCost(System.currentTimeMillis() - start);    LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));       return result;   }  
  将 url、http request 这些信息组装成 RequestInfo 对象,再序列化打印对象
  打印 序列化 对象结果而不是直接打印对象是因为序列化有更直观、更清晰,同时可以借助在线解析工具对结果进行解析
  是不是还不错
  在解决高并发下请求串行问题的同时添加了对 异常请求信息的打印 ,通过使用 @AfterThrowing 注解对抛出异常的方法进行处理 RequestErrorInfo.java@Data   public class RequestErrorInfo {    private String ip;    private String url;    private String httpMethod;    private String classMethod;    private Object requestParams;    private RuntimeException exception;   }  异常通知环绕体@AfterThrowing(pointcut = "requestServer()", throwing = "e")   public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();    HttpServletRequest request = attributes.getRequest();    RequestErrorInfo requestErrorInfo = new RequestErrorInfo();    requestErrorInfo.setIp(request.getRemoteAddr());    requestErrorInfo.setUrl(request.getRequestURL().toString());    requestErrorInfo.setHttpMethod(request.getMethod());    requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),    joinPoint.getSignature().getName()));    requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));    requestErrorInfo.setException(e);    LOGGER.info("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));   }  
  对于异常,耗时是没有意义的,因此不统计耗时,而是添加了异常的打印
  最后放一下完整日志请求切面代码: @Component   @Aspect   public class RequestLogAspect {       private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);          @Pointcut("execution(* your_package.controller..*(..))")       public void requestServer() {       }          @Around("requestServer()")       public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {           long start = System.currentTimeMillis();           ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();           HttpServletRequest request = attributes.getRequest();           Object result = proceedingJoinPoint.proceed();           RequestInfo requestInfo = new RequestInfo();                   requestInfo.setIp(request.getRemoteAddr());           requestInfo.setUrl(request.getRequestURL().toString());           requestInfo.setHttpMethod(request.getMethod());           requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),                   proceedingJoinPoint.getSignature().getName()));           requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));           requestInfo.setResult(result);           requestInfo.setTimeCost(System.currentTimeMillis() - start);           LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));              return result;       }             @AfterThrowing(pointcut = "requestServer()", throwing = "e")       public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {           ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();           HttpServletRequest request = attributes.getRequest();           RequestErrorInfo requestErrorInfo = new RequestErrorInfo();           requestErrorInfo.setIp(request.getRemoteAddr());           requestErrorInfo.setUrl(request.getRequestURL().toString());           requestErrorInfo.setHttpMethod(request.getMethod());           requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),                   joinPoint.getSignature().getName()));           requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));           requestErrorInfo.setException(e);           LOGGER.info("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));       }          /**        * 获取入参        * @param proceedingJoinPoint        *        * @return        * */       private Map getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {           //参数名           String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();           //参数值           Object[] paramValues = proceedingJoinPoint.getArgs();              return buildRequestParam(paramNames, paramValues);       }          private Map getRequestParamsByJoinPoint(JoinPoint joinPoint) {           //参数名           String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();           //参数值           Object[] paramValues = joinPoint.getArgs();              return buildRequestParam(paramNames, paramValues);       }          private Map buildRequestParam(String[] paramNames, Object[] paramValues) {           Map requestParams = new HashMap<>();           for (int i = 0; i < paramNames.length; i++) {               Object value = paramValues[i];                  //如果是文件对象               if (value instanceof MultipartFile) {                   MultipartFile file = (MultipartFile) value;                   value = file.getOriginalFilename();  //获取文件名               }                  requestParams.put(paramNames[i], value);           }              return requestParams;       }          @Data       public class RequestInfo {           private String ip;           private String url;           private String httpMethod;           private String classMethod;           private Object requestParams;           private Object result;           private Long timeCost;       }          @Data       public class RequestErrorInfo {           private String ip;           private String url;           private String httpMethod;           private String classMethod;           private Object requestParams;           private RuntimeException exception;       }   }  
  赶紧给你们的应用加上吧【如果没加的话】,没有日志的话,总怀疑上层出错,但是却拿不出证据
  关于 traceId 跟踪定位,可以根据 traceId 跟踪整条调用链,以 log4j2 为例介绍如何加入 traceId 添加拦截器public class LogInterceptor implements HandlerInterceptor {    private final static String TRACE_ID = "traceId";       @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {    String traceId = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();    ThreadContext.put("traceId", traceId);       return true;    }       @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)    throws Exception {    }       @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)    throws Exception {    ThreadContext. remove(TRACE_ID);    }   }  
  在调用前通过 ThreadContext 加入 traceId,调用完成后移除 修改日志配置文件 在原来的日志格式中
  添加 traceId 的占位符[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n   执行效果
  日志跟踪更方便
  DMC 是配置 logback 和 log4j 使用的,使用方式和 ThreadContext 差不多,将 ThreadContext.put 替换为 MDC.put 即可,同时修改日志配置文件。
  log4j2 也是可以配合 MDC 一起使用的
  MDC 是 slf4j 包下的,其具体使用哪个日志框架与我们的依赖有关。

曝圆形相机华为Mate50Pro,Mate40新机让路,价格不断触底曝光终于定档配置豪华版本华为Mate50Pro概念机背部依旧采用圆形相机模组,曝光终于定档配置豪华版本华为Mate50Pro概念机跟荣耀Magic4Pro极为相似,曝光终于定档配置iPhone14系列价格遭曝光,你会入手吗?Phone13将全线降价80美元左右,约合人民币508元人民币。该降价举措或为清空iPhone13的库存,进而为iPhone14的发布做准备。本次苹果降价之后预计将会在618期间出高举健康大旗的智能手表,与专业医疗诊断的距离还有多远?中国商报(记者焦立坤见习记者赵熠如)随着不断迭代升级,各大品牌智能手表的健康监测功能号称越来越专业,他们与专业医疗诊断的距离究竟还有多远?越来越健康的手表11月17日,华为全场景智比骨传导更先进,SanagA11S秘境寒鸦气传导耳机体验评测虽然骨传导耳机能够做到听歌不入耳,有自己独特的优势,但也有几个明显遗憾第一骨传导通过颅骨振动传播声音,但戴久了很麻很木有不适感第二骨传导传播漏音严重,嘈杂环境几乎只能感受振动听不到全球限量LV定制款iPhone13ProMax,给您奢华的沉浸式用机体验在2019年,国内知名iPhone定制厂商爱定族,首次跨界推出iPhoneLV定制款,受到广大用户的青睐与追捧。今年苹果秋季发布会带来全新iPhone13Pro及13ProMax,小白高中生,请问10002000的笔记本电脑相比贵的,用起来会不会影响身体健康啊?比如辐射?会的,会影响智商,越便宜的影响越大电子产品对于人体的辐射,其实是很小的,完全不必担心对人体的伤害,并且笔记本电脑不管价格高低,都属CPU主板内存硬盘电池显卡散热屏幕组成,和高端笔记电脑上有哪些免费的视频制作剪辑软件?视频编辑软件很多,都有免费下载。大多软件都是收费才能使用,不收费的也有玻解版的,个人建议还是用正版的,每月也就9元。电脑上有哪些免费的视频制作剪辑软件?题主问的正好,对上我这几年的特斯拉司机双手离开方向盘,还拍视频炫耀,结果撞上前车身亡近日,美国一名特斯拉司机不幸死于车祸,生前他在网上发布了自己双手不放在方向盘上开车的视频。加州高速公路当局认为,35岁的史蒂文迈克尔亨德里克森在事故发生前可能开启了自动驾驶模式。然为什么感觉人们一般都喜欢买1000块钱左右的手机?经过我的一些观察,用千元入门机的用户的比例还是很高的,下至学生党,上至五六十的中老年人,整个群体跨度非常大,且用户基数多。那这是为什么呢?首先消费习惯不管是年轻学生群体,还是中老年中国or美国?被捧上神坛的台积电,命运已不在自己手中文科工力量李沛围绕台积电南京厂的争议在继续。4月22日,台积电(TSMC)召开临时董事会,批准28。87亿美元预算,启动江苏南京工厂扩建项目,规划新增每月4万片12英寸(300mm罗永浩是行业冥灯?本人正面回应不能同意,都是时间巧合而已点击右上方关注,第一时间获取科技资讯技能攻略产品体验,私信我回复01,送你一份玩机技能大礼包。5月12日消息,锤子手机创始人罗永浩在近日举行的交个朋友电商学苑聊天局中,正面回应了如
超大屏真高刷电视低至15999,TCL客厅娱乐新品T7E一步到位TCL电视98T7E98英寸电视20999购买随着次时代游戏主机的火热和影视技术的不断发展,4K高清与高刷新率正逐渐成为当前家庭影音娱乐电视的标配。各大电视产商开始纷纷推出高刷新率投影仪哪家好1。本文只谈投影标准!不带任何购物链接!不恰烂饭,不赚烂钱!2。不要套用买电视的思维购买投影仪,由于显像原理的不同,同一品牌同一系列的电视机,三千价位和一万价位很多都是用的同一块面WINDOWS7旗舰版安装博途V16不能运行问题完整性监控服务检测到SINAMICSStartDrive安装程序存在变动。请修复重新安装该产品。WIN7旗舰版操作系统安装博途V16和StartDriveV16后,启动时提示完整性谁能想到,京医通竟是靠一人之力苦苦撑了7年在北京生活过的人,多多少少都使用过京医通,这个就诊卡覆盖了北京大部分的医院,让预约挂号变得简单方便。但谁能想到,方便了5500万人就医的京医通,基本没有政府资金的支持,竟是靠一人之如何评价中国移动推出的5G新通话?5G新通话时代来了!5G爆款应用来了!5G超清视话功能上线了不知道各位看官最近的朋友圈如何,总之朋友圈近几日被5GVoNR刷了屏,而且刷的满满当当,起因是4月12日,中国移动在多省国产手机离开安卓就完蛋?酷派与腾讯不愿意了,联手摆脱谷歌限制都知道,如今手机的系统两大阵型是iOS以及安卓。iOS不必多说,作为苹果自研系统,它的强大之处用过iPhone的人都能感受到。而对于安卓,虽然它是如今大部分国产手机系统的主流,但很能挡子弹的手机乌克兰士兵得救,三星手机?诺基亚手机?18日,海外网络社区Reddit上发布了一段视频,视频中一名乌克兰士兵被一名俄罗斯士兵开枪击中,但幸亏他的智能手机挡住了子弹,才保住了性命。Reddit的乌克兰战争视频报告公告板上天呐!三星S22U让我们告别单反有希望了要说当下哪款安卓手机的影像参数最厉害,我想很多人都会认为是三星S22U吧,这四个巨无霸镜头可谓是手机摄影天花板啊,1。08亿像素主摄1200万像素超广角10倍光学变焦的1000万像小米Civi1S后天发大电池高颜值,专为男生打造的自拍手机小米一直以来走的都是性价比路线,纵使在高端机型上,也能看到性价比的身影,所以说性价比一词是刻在小米基因中的,不过这也导致了小米在女性市场的空缺,但自从小米Civi系列发布后,这种情2022年,蓝牙音频传输设备出货量将突破14亿台2022年4月20日讯,在蓝牙技术联盟(BluetoothSpecialInterestGroup,SIG)近期发布的年度报告2022蓝牙市场最新资讯中,除了蓝牙音频传输设备一如既魅族春日新品观影会召开20多款新品全面亮相中国网科技4月20日讯(记者杨月月)魅族科技19日发布旗下PANDAER系列与lipro智能照明系列产品更新。其中,魅族PANDAER发布Number19和绿洲营地两大新主题,li