I wrote a log section, which makes it more convenient to shake the pot!

Recently, the project has entered the joint debugging stage. The interface of the service layer needs to interact with the protocol layer. The protocol layer needs to assemble the input parameter [json string] into the json string required by the service layer, which is prone to errors during the assembly process.

The problem of interface debugging failure caused by input parameter error occurred many times in joint debugging, so I wanted to write a request log aspect to print the input parameter information, and at the same time, the protocol layer called the service layer interface name and it also appeared several times. From the log section, you can know whether the upper layer has initiated a call, so that it is convenient for the front and back ends to throw the pot and come up with evidence.

 

Write in front

This article is practical and will not explain the principles of the aspect, but will only briefly introduce the knowledge points of the aspect

Introduction

Aspect-oriented programming is a programming paradigm, as a supplement to OOP object-oriented programming, used to deal with cross-cutting concerns distributed in various modules in the system, such as transaction management , permission control , cache control , log printing, and so on.

AOP divides the functional modules of the software into two parts: core concerns and cross-cutting concerns. The main function of business processing is the core focus, while the non-core and need to be expanded functions are the crosscutting focus. The function of AOP is to separate the various concerns in the system and separate the core concerns from the cross-cutting concerns. The use of aspects has the following advantages:

  • Concentrate on a certain focus / cross-cutting logic

  • You can easily add/remove points of interest

  • Less intrusive, enhancing code readability and maintainability. Therefore, when you want to print the request log, it is easy to think of the aspect and invade the control layer code 0

The use of aspects [based on annotations]

  • @Aspect => declare this class as an annotation class

Cut point annotation:

  • @Pointcut => Define a pointcut to simplify the code

Notice note:

  • @Before => execute code before pointcut

  • @After => execute code after pointcut

  • @AfterReturning => The code is executed after the pointcut returns the content, and the return value of the pointcut can be encapsulated

  • @AfterThrowing => Execute after the pointcut throws an exception

  • @Around => surround, execute code before and after the point of cut

Write a request log aspect

  • Use @Pointcut to define the cut point

@Pointcut("execution(* your_package.controller..*(..))")  
public void requestServer() {  
}  
 

@Pointcut defines a pointcut, because it is requesting log cut edges, so the pointcut defines methods under all classes in the Controller package. After defining the pointcut, you can directly use the requestServer method name in the notification annotation.

  • Use @Before to execute before the cut point

@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());  
}  
 

Before entering the Controller method, print out the caller IP, request URL, HTTP request type, and the name of the method called

  • Use @Around to print the input parameters that enter the control layer

@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;  
}  
 

The input parameters, results and time-consuming are printed

  • getRquestParams method

private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {  
  Map<String, Object> 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;  
 }  
 

The parameters passed through @PathVariable and @RequestParam annotations cannot print out the parameter names, so you need to manually splice the parameter names. At the same time, special processing is performed on the file object, just get the file name.

  • Execute after @After method is called

@After("requestServer()")  
public void doAfter(JoinPoint joinPoint) {  
 LOGGER.info("===============================End========================");  
}  
 

There is no business logic but End is printed

  • Full section code

@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<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {  
 Map<String, Object> 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;  
 }  
}  
 

High concurrency request log aspect

After writing, I was very satisfied with my code, but I thought there might be something to improve, so I communicated with my friends. emmmm

Sure enough, there is still a place to continue to optimize each message to print one line. Under high concurrent requests, there will indeed be a problem of printing logs serially between requests, because the number of requests in the test phase is small and there is no serial situation. It is the first development force, can encounter more bugs, write more robust code to solve the log serial problem, just merge multiple lines of printing information into one line, so construct an object

  • 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;  
}  
 
  • Surround notification method body

@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;  
}  
 

Assemble url and http request information into RequestInfo objects, and then serialize and print the objects.
Printing the serialized object results instead of directly printing the objects is because serialization is more intuitive and clearer, and the results can be parsed with the help of online analysis tools

Is it not bad

While solving the problem of request serialization under high concurrency, the printing of exception request information is added , and the method of throwing exception is handled by using @AfterThrowing annotation

  • RequestErrorInfo.java

@Data  
public class RequestErrorInfo {  
 private String ip;  
 private String url;  
 private String httpMethod;  
 private String classMethod;  
 private Object requestParams;  
 private RuntimeException exception;  
}  
 
  • Abnormal notification wrapper

@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));  
}  
 

For exceptions, time-consuming is meaningless, so time-consuming is not counted, but abnormal printing is added

Finally, let's put the complete log request aspect code:

@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<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {  
        //参数名  
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();  
        //参数值  
        Object[] paramValues = proceedingJoinPoint.getArgs();  
  
        return buildRequestParam(paramNames, paramValues);  
    }  
  
    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {  
        //参数名  
        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();  
        //参数值  
        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];  
  
            //如果是文件对象  
            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;  
    }  
}  
 

Hurry up and add it to your application [if not added], if there is no log, you always suspect that the upper layer is wrong, but you can't show evidence

Regarding traceId tracking and positioning, the entire call chain can be traced according to traceId. Take log4j2 as an example to introduce how to add traceId

  • Add interceptor

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);  
 }  
}  
 

Add traceId through ThreadContext before calling and remove it after calling

  • Modify the log configuration file to
    add a placeholder for traceId in the original log format

<property >[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>  
 
  • Execution effect

Log tracking is more convenient

DMC is used to configure logback and log4j. The usage is similar to ThreadContext, just replace ThreadContext.put with MDC.put, and modify the log configuration file at the same time.

log4j2 can also be used with MDC

MDC is under the slf4j package, and which logging framework it uses is related to our dependencies.

Guess you like

Origin blog.csdn.net/baidu_39322753/article/details/109379778