文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

java如何实现统一打印入参出参等日志

2023-07-05 19:39

关注

这篇文章主要介绍“java如何实现统一打印入参出参等日志”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java如何实现统一打印入参出参等日志”文章能帮助大家解决问题。

1.背景   

SpringBoot项目中,之前都是在controller方法的第一行手动打印 log,return之前再打印返回值。有多个返回点时,就需要出现多少重复代码,过多的非业务代码显得十分凌乱。

本文将采用AOP 配置自定义注解实现 入参、出参的日志打印(方法的入参和返回值都采用 fastjson 序列化)。

2.设计思路

将特定包下所有的controller生成代理类对象,并交由Spring容器管理,并重写invoke方法进行增强(入参、出参的打印).

3.核心代码

3.1 自定义注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import({InteractRecordBeanPostProcessor.class})public @interface EnableInteractRecord {        String[] basePackages() default {};        String[] exclusions() default {};}

3.2 实现BeanFactoryPostProcessor接口

作用:获取EnableInteractRecord注解对象,用于获取需要创建代理对象的包名,以及需要排除的包名

@Componentpublic class InteractRecordFactoryPostProcessor implements BeanFactoryPostProcessor {    private static Logger logger = LoggerFactory.getLogger(InteractRecordFactoryPostProcessor.class);    private EnableInteractRecord enableInteractRecord;    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        try {            String[] names = beanFactory.getBeanNamesForAnnotation(EnableInteractRecord.class);            for (String name : names) {                enableInteractRecord = beanFactory.findAnnotationOnBean(name, EnableInteractRecord.class);                logger.info("开启交互记录 ", enableInteractRecord);            }        } catch (Exception e) {            logger.error("postProcessBeanFactory() Exception ", e);        }    }    public EnableInteractRecord getEnableInteractRecord() {        return enableInteractRecord;    }}

3.3 实现MethodInterceptor编写打印日志逻辑

作用:进行入参、出参打印,包含是否打印逻辑

@Componentpublic class ControllerMethodInterceptor implements MethodInterceptor {    private static Logger logger = LoggerFactory.getLogger(ControllerMethodInterceptor.class);    // 请求开始时间    ThreadLocal<Long> startTime = new ThreadLocal<>();    private String localIp = "";    @PostConstruct    public void init() {        try {            localIp = InetAddress.getLocalHost().getHostAddress();        } catch (UnknownHostException e) {            logger.error("本地IP初始化失败 : ", e);        }    }    @Override    public Object invoke(MethodInvocation invocation) {        pre(invocation);        Object result;        try {            result = invocation.proceed();            post(invocation, result);            return result;        } catch (Throwable ex) {            logger.error("controller 执行异常: ", ex);            error(invocation, ex);        }        return null;    }    public void error(MethodInvocation invocation, Throwable ex) {        String msgText = ex.getMessage();        logger.info(startTime.get() + " 异常,请求结束");        logger.info("RESPONSE : " + msgText);        logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));    }    private void pre(MethodInvocation invocation) {        long now = System.currentTimeMillis();        startTime.set(now);        logger.info(now + " 请求开始");        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = attributes.getRequest();        logger.info("URL : " + request.getRequestURL().toString());        logger.info("HTTP_METHOD : " + request.getMethod());        logger.info("REMOTE_IP : " + getRemoteIp(request));        logger.info("LOCAL_IP : " + localIp);        logger.info("METHOD : " + request.getMethod());        logger.info("CLASS_METHOD : " + getTargetClassName(invocation) + "." + invocation.getMethod().getName());        // 获取请求头header参数        Map<String, String> map = new HashMap<String, String>();        Enumeration<String> headerNames = request.getHeaderNames();        while (headerNames.hasMoreElements()) {            String key = (String) headerNames.nextElement();            String value = request.getHeader(key);            map.put(key, value);        }        logger.info("HEADERS : " + JSONObject.toJSONString(map));        Date createTime = new Date(now);        // 请求报文        Object[] args = invocation.getArguments();// 参数        String msgText = "";        Annotation[][] annotationss = invocation.getMethod().getParameterAnnotations();        for (int i = 0; i < args.length; i++) {            Object arg = args[i];            if (!(arg instanceof ServletRequest)                    && !(arg instanceof ServletResponse)                    && !(arg instanceof Model)) {                RequestParam rp = null;                Annotation[] annotations = annotationss[i];                for (Annotation annotation : annotations) {                    if (annotation instanceof RequestParam) {                        rp = (RequestParam) annotation;                    }                }                if (msgText.equals("")) {                    msgText += (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg);                } else {                    msgText += "," + (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg);                }            }        }        logger.info("PARAMS : " + msgText);    }    private void post(MethodInvocation invocation, Object result) {        logger.info(startTime.get() + " 请求结束");        if (!(result instanceof ModelAndView)) {            String msgText = JSONObject.toJSONString(result);            logger.info("RESPONSE : " + msgText);        }        logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));    }    private String getRemoteIp(HttpServletRequest request) {        String remoteIp = null;        String remoteAddr = request.getRemoteAddr();        String forwarded = request.getHeader("X-Forwarded-For");        String realIp = request.getHeader("X-Real-IP");        if (realIp == null) {            if (forwarded == null) {                remoteIp = remoteAddr;            } else {                remoteIp = remoteAddr + "/" + forwarded.split(",")[0];            }        } else {            if (realIp.equals(forwarded)) {                remoteIp = realIp;            } else {                if (forwarded != null) {                    forwarded = forwarded.split(",")[0];                }                remoteIp = realIp + "/" + forwarded;            }        }        return remoteIp;    }    private String getTargetClassName(MethodInvocation invocation) {        String targetClassName = "";        try {            targetClassName = AopTargetUtils.getTarget(invocation.getThis()).getClass().getName();        } catch (Exception e) {            targetClassName = invocation.getThis().getClass().getName();        }        return targetClassName;    }}

AopTargetUtils:

public class AopTargetUtils {                    public static Object getTarget(Object proxy) throws Exception {                    if(!AopUtils.isAopProxy(proxy)) {            return proxy;//不是代理对象          }                    if(AopUtils.isJdkDynamicProxy(proxy)) {            return getJdkDynamicProxyTargetObject(proxy);          } else { //cglib              return getCglibProxyTargetObject(proxy);          }                                    }          private static Object getCglibProxyTargetObject(Object proxy) throws Exception {          Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");          h.setAccessible(true);        Object dynamicAdvisedInterceptor = h.get(proxy);                    Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");          advised.setAccessible(true);                    Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();                  return getTarget(target);    }          private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {          Field h = proxy.getClass().getSuperclass().getDeclaredField("h");          h.setAccessible(true);          AopProxy aopProxy = (AopProxy) h.get(proxy);                  Field advised = aopProxy.getClass().getDeclaredField("advised");          advised.setAccessible(true);                    Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();                  return getTarget(target);     }        }

3.4 实现BeanPostProcessor接口

作用:筛选出需要生成代理的类,并生成代理类,返回给Spring容器管理。

public class InteractRecordBeanPostProcessor implements BeanPostProcessor {    private static Logger logger = LoggerFactory.getLogger(InteractRecordBeanPostProcessor.class);    @Autowired    private InteractRecordFactoryPostProcessor interactRecordFactoryPostProcessor;    @Autowired    private ControllerMethodInterceptor controllerMethodInterceptor;    private String BASE_PACKAGES[];//需要拦截的包    private String EXCLUDING[];// 过滤的包    //一层目录匹配    private static final String ONE_REGEX = "[a-zA-Z0-9_]+";    //多层目录匹配    private static final String ALL_REGEX = ".*";    private static final String END_ALL_REGEX = "*";    @PostConstruct    public void init() {        EnableInteractRecord ir = interactRecordFactoryPostProcessor.getEnableInteractRecord();        BASE_PACKAGES = ir.basePackages();        EXCLUDING = ir.exclusions();    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        try {            if (interactRecordFactoryPostProcessor.getEnableInteractRecord() != null) {                // 根据注解配置的包名记录对应的controller层                if (BASE_PACKAGES != null && BASE_PACKAGES.length > 0) {                    Object proxyObj = doEnhanceForController(bean);                    if (proxyObj != null) {                        return proxyObj;                    }                }            }        } catch (Exception e) {            logger.error("postProcessAfterInitialization() Exception ", e);        }        return bean;    }    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;    }    private Object doEnhanceForController(Object bean) {        String beanPackageName = getBeanPackageName(bean);        if (StringUtils.isNotBlank(beanPackageName)) {            for (String basePackage : BASE_PACKAGES) {                if (matchingPackage(basePackage, beanPackageName)) {                    if (EXCLUDING != null && EXCLUDING.length > 0) {                        for (String excluding : EXCLUDING) {                            if (matchingPackage(excluding, beanPackageName)) {                                return bean;                            }                        }                    }                    Object target = null;                    try {                        target = AopTargetUtils.getTarget(bean);                    } catch (Exception e) {                        logger.error("AopTargetUtils.getTarget() exception", e);                    }                    if (target != null) {                        boolean isController = target.getClass().isAnnotationPresent(Controller.class);                        boolean isRestController = target.getClass().isAnnotationPresent(RestController.class);                        if (isController || isRestController) {                            ProxyFactory proxy = new ProxyFactory();                            proxy.setTarget(bean);                            proxy.addAdvice(controllerMethodInterceptor);                            return proxy.getProxy();                        }                    }                }            }        }        return null;    }    private static boolean matchingPackage(String basePackage, String currentPackage) {        if (StringUtils.isEmpty(basePackage) || StringUtils.isEmpty(currentPackage)) {            return false;        }        if (basePackage.indexOf("*") != -1) {            String patterns[] = StringUtils.split(basePackage, ".");            for (int i = 0; i < patterns.length; i++) {                String patternNode = patterns[i];                if (patternNode.equals("*")) {                    patterns[i] = ONE_REGEX;                }                if (patternNode.equals("**")) {                    if (i == patterns.length - 1) {                        patterns[i] = END_ALL_REGEX;                    } else {                        patterns[i] = ALL_REGEX;                    }                }            }            String basePackageRegex = StringUtils.join(patterns, "\\.");            Pattern r = Pattern.compile(basePackageRegex);            Matcher m = r.matcher(currentPackage);            return m.find();        } else {            return basePackage.equals(currentPackage);        }    }    private String getBeanPackageName(Object bean) {        String beanPackageName = "";        if (bean != null) {            Class<?> beanClass = bean.getClass();            if (beanClass != null) {                Package beanPackage = beanClass.getPackage();                if (beanPackage != null) {                    beanPackageName = beanPackage.getName();                }            }        }        return beanPackageName;    }}

3.5 启动类配置注解

@EnableInteractRecord(basePackages = “com.test.test.controller”,exclusions = “com.test.demo.controller”)

以上即可实现入参、出参日志统一打印,并且可以将特定的controller集中管理,并不进行日志的打印(及不进生成代理类)。

4.出现的问题(及其解决办法)

实际开发中,特定不需要打印日志的接口,无法统一到一个包下。大部分需要打印的接口,和不需要打印的接口,大概率会参杂在同一个controller中,根据以上设计思路,无法进行区分。

解决办法:

自定义排除入参打印注解

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExcludeReqLog {}

自定义排除出参打印注解

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExcludeRespLog {}

增加逻辑

// 1.在解析requestParam之前进行判断        Method method = invocation.getMethod();        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();        boolean flag = true;        for (Annotation annotation : declaredAnnotations) {            if (annotation instanceof ExcludeReqLog) {                flag = false;            }        }        if (!flag) {            logger.info("该方法已排除,不打印入参");            return;        }// 2.在解析requestResp之前进行判断        Method method = invocation.getMethod();        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();        boolean flag = true;        for (Annotation annotation : declaredAnnotations) {            if (annotation instanceof ExcludeRespLog) {                flag = false;            }        }        if (!flag) {            logger.info("该方法已排除,不打印出参");            return;        }

使用方法

// 1.不打印入参    @PostMapping("/uploadImg")    @ExcludeReqLog    public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) {        return demoService.uploadIdeaImg(imgFile);    }//2.不打印出参    @PostMapping("/uploadImg")    @ExcludeRespLog     public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) {        return demoService.uploadIdeaImg(imgFile);    }

关于“java如何实现统一打印入参出参等日志”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯