Напишите лог запроса фасетов, удобнее скидывать фронт и бэкенды

Java

Недавно проект вступил в стадию совместной отладки. Интерфейс сервисного уровня должен взаимодействовать с протокольным уровнем. Протокольный уровень должен собрать входной параметр [строка json] в строку json, требуемую сервисным уровнем. легко ошибиться в процессе сборки. Проблема сбоя отладки интерфейса, вызванная ошибкой входного параметра, возникала много раз при совместной отладке, поэтому я хотел написать аспект журнала запросов, чтобы распечатать информацию о входном параметре.В то же время также несколько раз возникало, что имя интерфейса Уровень протокола, называемый сервисным уровнем, не совпадает.Из раздела журнала вы можете узнать, инициировал ли вызов верхний уровень, что удобно для передней и задней частей, чтобы сбросить банк и предоставить доказательства.

написать впереди

Эта статья носит практический характер, она не будет объяснять принцип аспекта, а лишь кратко ознакомит с очками знаний аспекта.

Введение в резку

Аспектно-ориентированное программирование — это парадигма программирования, которая дополняет объектно-ориентированное программирование ООП для решения сквозных задач, распределенных по модулям в системе, таких какуправление транзакциями,Контроль доступа,управление кешем,Журнал Печатьи Т. Д. АОП делит функциональные модули программного обеспечения на две части: основные задачи и сквозные задачи. Основная функция обработки бизнес-процессов является основной задачей, а дополнительные функции, которые необходимо расширить, являются сквозными задачами. Роль АОП заключается в разделении различных задач в системе, а также в отделении основных задач от сквозных задач.Использование фасетов имеет следующие преимущества:

  • Сосредоточьтесь на одной проблеме/сквозной логике
  • Легко добавлять/удалять проблемы
  • Менее навязчивый, повышает читабельность кода и удобство сопровождения

Следовательно, легко представить себе аспект, когда вы хотите распечатать журнал запросов и вторгнуться в код уровня управления 0.

Использование аспектов [на основе аннотаций]

  • @Aspect => объявляет класс как класс аннотации

Примечания к пунктуации:

  • @Pointcut => определяет точечный разрез, который упрощает код

Примечания к уведомлению:

  • @Before => выполнить код перед точечным вырезом
  • @After => выполнить код после pointcut
  • @AfterReturning => Код выполняется после того, как pointcut возвращает содержимое, и возвращаемое значение pointcut может быть инкапсулировано
  • @AfterThrowing => Выполнить после того, как pointcut выдаст исключение
  • @Around => окружать, выполнять код до и после pointcut

Напишите аспект журнала запросов вручную

  • Используйте @Pointcut для определения точечных разрезов
    @Pointcut("execution(* your_package.controller..*(..))")
    public void requestServer() {
    }
    
    @Pointcut определяет pointcut, потому что это вырезка края журнала запросов, поэтому pointcut определяет методы всех классов в пакете Controller. После определения pointcut вы можете напрямую использовать имя метода requestServer в аннотации уведомления.
  • Используйте @Before для выполнения до pointcut
    @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<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;
       }
      
      Параметры, переданные через аннотации @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<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;
        }
    }
    

Аспект журнала запросов при высоком уровне параллелизма

После его написания я был очень доволен своим кодом, но подумал, что могут быть какие-то идеальные места для общения с друзьями. эммм

Конечно, есть еще место для дальнейшей оптимизации. Каждое сообщение печатается на одной строке. При большом количестве одновременных запросов действительно существует проблема последовательной печати журналов между запросами. Поскольку количество запросов на этапе тестирования невелико, сериализация не выполняется. Конечно же, производственная среда — это первая группа разработчиков. Обнаружьте больше ошибок, напишите более надежный код Чтобы решить проблему сериализации журнала, вам нужно всего лишь объединить несколько строк печатной информации в одну строку, поэтому сконструируйте объект

  • 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-запроса в объект 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<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;
    }
}

Спешите и добавьте его в свое приложение [если не добавите], если лога нет, вы всегда подозреваете, что верхний слой не прав, но не можете придумать доказательства

=================== Следующий контент был обновлен 2019/3/14 ===============
Что касается отслеживания Traceid и позиционирования [Комментарий Кельвина от Jizo], вы можете отслеживать всю цепочку вызовов в соответствии с 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);
        }
    }
    
    Добавьте traceId через ThreadContext перед вызовом и удалите его после завершения вызова.
  • Изменить файл конфигурации журнала

в исходном формате журнала
Добавить заполнитель для traceId <property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

  • Эффект исполнения

    Отслеживание журнала удобнее

DMC используется для настройки logback и log4j. Использование аналогично использованию ThreadContext. Просто замените ThreadContext.put на MDC.put и одновременно измените файл конфигурации журнала.
Рекомендуется использовать log4j2, почему log4j2 рекомендуется прочитать в этой статье:Структура ведения журнала, выберите Logback или Log4j2?

===================== Следующий контент был обновлен 16.03.2019 ===============
log4j2 также можно использовать с MDC.

/
MDC находится в пакете slf4j, используемая им структура ведения журнала связана с нашими зависимостями.
===================== Следующий контент был обновлен 2021/6/15================
Восторженные пользователи комментируют

если (значение instanceof MultipartFile) этот способ обработки потока? Но что, если параметр попадает в список файлов? Кажется, это не работает таким образом.

В коде действительно не хватает внимания, решение следующее:

//如果是批量文件上传
if (value instanceof List) {
     try {
        List<MultipartFile> multipartFiles = castList(value, MultipartFile.class);
        if (multipartFiles!= null) {
            List<String> fileNames = new ArrayList<>();
            for (MultipartFile file : multipartFiles) {
                fileNames.add(file.getOriginalFilename());
            }

            requestParams.put(paramNames[i], fileNames);
            break;
        }
    } catch (ClassCastException e) {
    //忽略不是文件类型的List
    }
}

Сделайте вывод о типе списка, если это список файлов, перейдите, чтобы получить имя файла
Метод castList:

public static <T> List<T> castList(Object obj, Class<T> clazz) {
    List<T> result = new ArrayList<T>();
    if (obj instanceof List<?>) {
        for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
        }
        return result;
    }
    return null;
}

Полный код после модификации:

@Component
@Aspect
public class RequestLogAspect {
    private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);

    @Pointcut("execution(* com.hikvision.trainplatform.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();  //获取文件名
            }

            //如果是批量文件上传
            if (value instanceof List) {
                System.out.println("Yes...");
                try {
                    List<MultipartFile> multipartFiles = castList(value, MultipartFile.class);
                    if (multipartFiles!= null) {
                        List<String> fileNames = new ArrayList<>();
                        for (MultipartFile file : multipartFiles) {
                            fileNames.add(file.getOriginalFilename());
                        }

                        requestParams.put(paramNames[i], fileNames);
                        break;
                    }
                } catch (ClassCastException e) {
                    //忽略不是文件类型的List
                }
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }
    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<T>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return null;
    }
    
    @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;
    }
}