<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫了個(gè)牛逼的日志切面,甩鍋更方便了!

          共 24872字,需瀏覽 50分鐘

           ·

          2021-05-02 14:27

          作者 | 何甜甜在嗎

          來(lái)源 | https://juejin.cn/post/6844904087964614670

          最近項(xiàng)目進(jìn)入聯(lián)調(diào)階段,服務(wù)層的接口需要和協(xié)議層進(jìn)行交互,協(xié)議層需要將入?yún)?[json 字符串] 組裝成服務(wù)層所需的 json 字符串,組裝的過(guò)程中很容易出錯(cuò)。

          入?yún)⒊鲥e(cuò)導(dǎo)致接口調(diào)試失敗問(wèn)題在聯(lián)調(diào)中出現(xiàn)很多次,因此就想寫一個(gè)請(qǐng)求日志切面把入?yún)⑿畔⒋蛴∫幌?,同時(shí)協(xié)議層調(diào)用服務(wù)層接口名稱對(duì)不上也出現(xiàn)了幾次,通過(guò)請(qǐng)求日志切面就可以知道上層是否有沒(méi)有發(fā)起調(diào)用,方便前后端甩鍋還能拿出證據(jù)。

          寫在前面

          本篇文章是實(shí)戰(zhàn)性的,對(duì)于切面的原理不會(huì)講解,只會(huì)簡(jiǎn)單介紹一下切面的知識(shí)點(diǎn)

          切面介紹

          面向切面編程是一種編程范式,它作為 OOP 面向?qū)ο缶幊痰囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、權(quán)限控制、緩存控制、日志打印等等。

          AOP 把軟件的功能模塊分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。業(yè)務(wù)處理的主要功能為核心關(guān)注點(diǎn),而非核心、需要拓展的功能為橫切關(guān)注點(diǎn)。AOP 的作用在于分離系統(tǒng)中的各種關(guān)注點(diǎn),將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)進(jìn)行分離,使用切面有以下好處:

          • 集中處理某一關(guān)注點(diǎn) / 橫切邏輯

          • 可以很方便的添加 / 刪除關(guān)注點(diǎn)

          • 侵入性少,增強(qiáng)代碼可讀性及可維護(hù)性 因此當(dāng)想打印請(qǐng)求日志時(shí)很容易想到切面,對(duì)控制層代碼 0 侵入

          切面的使用【基于注解】

          • @Aspect => 聲明該類為一個(gè)注解類

          切點(diǎn)注解:

          • @Pointcut => 定義一個(gè)切點(diǎn),可以簡(jiǎn)化代碼

          通知注解:

          • @Before => 在切點(diǎn)之前執(zhí)行代碼

          • @After => 在切點(diǎn)之后執(zhí)行代碼

          • @AfterReturning => 切點(diǎn)返回內(nèi)容后執(zhí)行代碼,可以對(duì)切點(diǎn)的返回值進(jìn)行封裝

          • @AfterThrowing => 切點(diǎn)拋出異常后執(zhí)行

          • @Around => 環(huán)繞,在切點(diǎn)前后執(zhí)行代碼

          動(dòng)手寫一個(gè)請(qǐng)求日志切面

          • 使用 @Pointcut 定義切點(diǎn)
          @Pointcut("execution(* your_package.controller..*(..))")  
          public void requestServer() {  
          }  
           

          @Pointcut 定義了一個(gè)切點(diǎn),因?yàn)槭钦?qǐng)求日志切邊,因此切點(diǎn)定義的是 Controller 包下的所有類下的方法。定義切點(diǎn)以后在通知注解中直接使用 requestServer 方法名就可以了

          • 使用 @Before 再切點(diǎn)前執(zhí)行
          @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());  
          }  
           

          在進(jìn)入 Controller 方法前,打印出調(diào)用方 IP、請(qǐng)求 URL、HTTP 請(qǐng)求類型、調(diào)用的方法名

          • 使用 @Around 打印進(jìn)入控制層的入?yún)?/section>
          @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;  
          }  
           

          打印了入?yún)?、結(jié)果以及耗時(shí)

          • getRquestParams 方法
          private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {  
            Map<String, Object> requestParams = new HashMap<>();  
            
             //參數(shù)名  
            String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();  
            //參數(shù)值  
            Object[] paramValues = proceedingJoinPoint.getArgs();  
            
            for (int i = 0; i < paramNames.length; i++) {  
            Object value = paramValues[i];  
            
            //如果是文件對(duì)象  
            if (value instanceof MultipartFile) {  
            MultipartFile file = (MultipartFile) value;  
            value = file.getOriginalFilename();  //獲取文件名  
            }  
            
            requestParams.put(paramNames[i], value);  
            }  
            
            return requestParams;  
           }  
           

          通過(guò) @PathVariable 以及 @RequestParam 注解傳遞的參數(shù)無(wú)法打印出參數(shù)名,因此需要手動(dòng)拼接一下參數(shù)名,同時(shí)對(duì)文件對(duì)象進(jìn)行了特殊處理,只需獲取文件名即可

          • @After 方法調(diào)用后執(zhí)行
          @After("requestServer()")  
          public void doAfter(JoinPoint joinPoint) {  
           LOGGER.info("===============================End========================");  
          }  
           

          沒(méi)有業(yè)務(wù)邏輯只是打印了 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========================");  
           }  
            
           /**  
            * 獲取入?yún)?nbsp; 
            * @param proceedingJoinPoint  
            *  
            * @return  
            * */  
           private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {  
           Map<String, Object> requestParams = new HashMap<>();  
            
           //參數(shù)名  
           String[] paramNames =  
          ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();  
           //參數(shù)值  
           Object[] paramValues = proceedingJoinPoint.getArgs();  
            
           for (int i = 0; i < paramNames.length; i++) {  
           Object value = paramValues[i];  
            
           //如果是文件對(duì)象  
           if (value instanceof MultipartFile) {  
           MultipartFile file = (MultipartFile) value;  
           value = file.getOriginalFilename();  //獲取文件名  
           }  
            
           requestParams.put(paramNames[i], value);  
           }  
            
           return requestParams;  
           }  
          }  
           

          高并發(fā)下請(qǐng)求日志切面

          寫完以后對(duì)自己的代碼很滿意,但是想著可能還有完善的地方就和朋友交流了一下。emmmm

          果然還有繼續(xù)優(yōu)化的地方 每個(gè)信息都打印一行,在高并發(fā)請(qǐng)求下確實(shí)會(huì)出現(xiàn)請(qǐng)求之間打印日志串行的問(wèn)題,因?yàn)闇y(cè)試階段請(qǐng)求數(shù)量較少?zèng)]有出現(xiàn)串行的情況,果然生產(chǎn)環(huán)境才是第一發(fā)展力,能夠遇到更多 bug,寫更健壯的代碼 解決日志串行的問(wèn)題只要將多行打印信息合并為一行就可以了,因此構(gòu)造一個(gè)對(duì)象

          • 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;  
          }  
           
          • 環(huán)繞通知方法體
          @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 對(duì)象,再序列化打印對(duì)象
          打印序列化對(duì)象結(jié)果而不是直接打印對(duì)象是因?yàn)樾蛄谢懈庇^、更清晰,同時(shí)可以借助在線解析工具對(duì)結(jié)果進(jìn)行解析

          是不是還不錯(cuò)

          在解決高并發(fā)下請(qǐng)求串行問(wèn)題的同時(shí)添加了對(duì)異常請(qǐng)求信息的打印,通過(guò)使用 @AfterThrowing 注解對(duì)拋出異常的方法進(jìn)行處理

          • RequestErrorInfo.java
          @Data  
          public class RequestErrorInfo {  
           private String ip;  
           private String url;  
           private String httpMethod;  
           private String classMethod;  
           private Object requestParams;  
           private RuntimeException exception;  
          }  
           
          • 異常通知環(huán)繞體
          @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));  
          }  
           

          對(duì)于異常,耗時(shí)是沒(méi)有意義的,因此不統(tǒng)計(jì)耗時(shí),而是添加了異常的打印

          最后放一下完整日志請(qǐng)求切面代碼:

          @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));  
              }  
            
              /**  
               * 獲取入?yún)?nbsp; 
               * @param proceedingJoinPoint  
               *  
               * @return  
               * */  
              private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {  
                  //參數(shù)名  
                  String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();  
                  //參數(shù)值  
                  Object[] paramValues = proceedingJoinPoint.getArgs();  
            
                  return buildRequestParam(paramNames, paramValues);  
              }  
            
              private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {  
                  //參數(shù)名  
                  String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();  
                  //參數(shù)值  
                  Object[] paramValues = joinPoint.getArgs();  
            
                  return buildRequestParam(paramNames, paramValues);  
              }  
            
              private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {  
                  Map<String, Object> requestParams = new HashMap<>();  
                  for (int i = 0; i < paramNames.length; i++) {  
                      Object value = paramValues[i];  
            
                      //如果是文件對(duì)象  
                      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;  
              }  
          }  
           

          趕緊給你們的應(yīng)用加上吧【如果沒(méi)加的話】,沒(méi)有日志的話,總懷疑上層出錯(cuò),但是卻拿不出證據(jù)

          關(guān)于 traceId 跟蹤定位,可以根據(jù) traceId 跟蹤整條調(diào)用鏈,以 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);  
           }  
          }  
           

          在調(diào)用前通過(guò) ThreadContext 加入 traceId,調(diào)用完成后移除

          • 修改日志配置文件 在原來(lái)的日志格式中
            添加 traceId 的占位符
          <property >[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>  
           
          • 執(zhí)行效果

          日志跟蹤更方便

          DMC 是配置 logback 和 log4j 使用的,使用方式和 ThreadContext 差不多,將 ThreadContext.put 替換為 MDC.put 即可,同時(shí)修改日志配置文件。

          log4j2 也是可以配合 MDC 一起使用的

          MDC 是 slf4j 包下的,其具體使用哪個(gè)日志框架與我們的依賴有關(guān)。


          1、Intellij IDEA這樣 配置注釋模板,讓你瞬間高出一個(gè)逼格!
          2、吊炸天的 Docker 圖形化工具 Portainer,必須推薦給你!
          3、最牛逼的 Java 日志框架,性能無(wú)敵,橫掃所有對(duì)手!
          4、把Redis當(dāng)作隊(duì)列來(lái)用,真的合適嗎?
          5、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
          6、全網(wǎng)最全 Java 日志框架適配方案!還有誰(shuí)不會(huì)?
          7、Spring中毒太深,離開Spring我居然連最基本的接口都不會(huì)寫了

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 27
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人三级视频在线观看 | 欧洲国产在线视频 | 黄色性爱视频欧美 | 久99久视频精品 | 国产毛片欧美毛片高潮 |