一文学会记录Controller请求信息

news/2025/1/23 11:12:18/

背景

        日常开发中,Controller作为对外暴露的接口,承担者业务请求入口的角色。但请求出现问题时,我们经常需要查看请求的参数信息,因此统一记录一下Controller的请求信息,对于后续排查问题,还是很方便的。

基于AOP记录接口信息

        为了记录接口请求日志信息,同时不影响已有的接口逻辑,使用AOP来解耦合是最合适不过了。接下来,我们就实现两种记录Controller请求信息的代码实践。

  • 基于AOP

java">package com.example;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;@Slf4j
@Aspect
@Component
public class LoggingAspect {@Pointcut("execution(public * com..*.controllers..*.*(..))")public void pointCut() {}@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {long beginTimestamp = System.currentTimeMillis();MethodSignature ms = (MethodSignature) pjp.getSignature();Method method = ms.getMethod();if (AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) {return pjp.proceed();}RequestAttributes reqa = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) reqa;HttpServletRequest request = sra.getRequest();try {return pjp.proceed();} finally {long elapsedTime = System.currentTimeMillis() - beginTimestamp ;log.info("[Controller请求{}] | {} | 耗时:{} ms | {}",         Thread.currentThread().getId(), request.getRequestURI(), elapsedTime, pjp.getArgs());}}
}
  • 基于AOP+自定义注解

        此处代码示例,参考开源项目若依(链接:若依)并简化,主要说明实现思路。

        先定义自定义注解日志,然后基于注解,进行AOP切面,记录日志。

        自定义注解

java">@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{/*** 模块*/public String title() default "";/*** 功能*/public BusinessType businessType() default BusinessType.OTHER;/*** 操作人类别*/public OperatorType operatorType() default OperatorType.MANAGE;/*** 是否保存请求的参数*/public boolean isSaveRequestData() default true;/*** 是否保存响应的参数*/public boolean isSaveResponseData() default true;
}

        AOP切面逻辑

java">@Aspect
@Component
public class LogAspect
{private static final Logger log = LoggerFactory.getLogger(LogAspect.class);/** 计算操作消耗时间 */private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");/*** 处理请求前执行*/@Before(value = "@annotation(controllerLog)")public void boBefore(JoinPoint joinPoint, Log controllerLog){TIME_THREADLOCAL.set(System.currentTimeMillis());}/*** 处理完请求后执行** @param joinPoint 切点*/@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult){handleLog(joinPoint, controllerLog, null, jsonResult);}protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult){try{// 获取当前的用户SysUser currentUser = ShiroUtils.getSysUser();// *========数据库日志=========*//SysOperLog operLog = new SysOperLog();operLog.setStatus(BusinessStatus.SUCCESS.ordinal());// 请求的地址String ip = ShiroUtils.getIp();operLog.setOperIp(ip);operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));if (currentUser != null){operLog.setOperName(currentUser.getLoginName());if (StringUtils.isNotNull(currentUser.getDept())&& StringUtils.isNotEmpty(currentUser.getDept().getDeptName())){operLog.setDeptName(currentUser.getDept().getDeptName());}}if (e != null){operLog.setStatus(BusinessStatus.FAIL.ordinal());operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));}// 设置方法名称String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operLog.setMethod(className + "." + methodName + "()");// 设置请求方式operLog.setRequestMethod(ServletUtils.getRequest().getMethod());// 处理设置注解上的参数getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);// 设置消耗时间operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());// 保存数据库AsyncManager.me().execute(AsyncFactory.recordOper(operLog));}catch (Exception exp){// 记录本地异常日志log.error("异常信息:{}", exp.getMessage());exp.printStackTrace();}finally{TIME_THREADLOCAL.remove();}}/*** 获取注解中对方法的描述信息 用于Controller层注解* * @param log 日志* @param operLog 操作日志* @throws Exception*/public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception{// 设置action动作operLog.setBusinessType(log.businessType().ordinal());// 设置标题operLog.setTitle(log.title());// 设置操作人类别operLog.setOperatorType(log.operatorType().ordinal());// 是否需要保存request,参数和值if (log.isSaveRequestData()){// 获取参数的信息,传入到数据库中。setRequestValue(joinPoint, operLog, log.excludeParamNames());}// 是否需要保存response,参数和值if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)){operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000));}}/*** 获取请求的参数,放到log中* * @param operLog 操作日志* @throws Exception 异常*/private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception{Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();if (StringUtils.isNotEmpty(map)){String params = JSONObject.toJSONString(map, excludePropertyPreFilter(excludeParamNames));operLog.setOperParam(StringUtils.substring(params, 0, 2000));}else{Object args = joinPoint.getArgs();if (StringUtils.isNotNull(args)){String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);operLog.setOperParam(StringUtils.substring(params, 0, 2000));}}}/*** 参数拼装*/private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames){String params = "";if (paramsArray != null && paramsArray.length > 0){for (Object o : paramsArray){if (StringUtils.isNotNull(o) && !isFilterObject(o)){try{Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter(excludeParamNames));params += jsonObj.toString() + " ";}catch (Exception e){}}}}return params.trim();}}

        使用方式:在Controller的接口方法上,加上上述日志注解即可。


http://www.ppmy.cn/news/1565466.html

相关文章

Spark/Kafka

文章目录 项目地址一、Spark1. RDD1.1 五大核心属性1.2 执行原理1.3 四种创建方式二、Kafka2.1 生产者(1)分区器(2)生产者提高吞吐量(3) 生产者数据可靠性数据传递语义幂等性和事务数据有序2.2 Broker(1)Broker工作流程(2)节点服役和退役2.3 副本(1)Follower故障细…

无序向量唯一化算法

最坏情况下需要 Ω ( n 2 ) \Omega(n^2) Ω(n2) 时间&#xff0c;最好情况下仅需 O ( n ) O(n) O(n) 时间。 删除无序向量中的重复元素 template <typename T> Rank Vector<T>::dedup(){Rank oldSize_size;//原来的规模for(Rank i1;i<_size;)//从前往后枚举…

十三、数据的的输入与输出(3)

数据的输出 writeClipboard&#xff08;&#xff09;函数 writeClipboard&#xff08;&#xff09;函数可以将数据输出至剪贴板。 例如&#xff0c;将R的内置数据集iris输出到剪贴板&#xff0c;在进入Excel中点击"粘贴"。 head(iris) #查看数据集Sepal.L…

最新-CentOS 7安装1 Panel Linux 服务器运维管理面板

CentOS 7安装1 Panel Linux 服务器运维管理面板 一、前言二、环境要求三、在线安装四、离线安装1.点击下面1 Panel官网链接访问下载&#xff0c;如未登录或注册&#xff0c;请登录/注册后下载2.使用将离线安装包上传至目标终端/tem目录下3.进入到/tem目录下解压离线安装包4.执行…

vscode的字体图标库-icomoon

icomoon官网下载地址&#xff1a;SVG Icon Libraries and Custom Icon Font Organizer ❍ IcoMoon Easily mange your icons and integrate them in your projects. Browse free icons or import your own SVG icons to export as icon font, SVG, PNG, sprite and more.https:…

RV1126+FFMPEG推流项目源码

源码在我的gitee上面&#xff0c;感兴趣的可以自行了解 nullhttps://gitee.com/x-lan/rv126-ffmpeg-streaming-project

c#配置config文件

1&#xff0c;引用命名空间 Configuration 及配置信息

Servlet快速入门

Servlet 由于目前主流使用SpringBoot进行开发Servlet可以说是时代的眼泪&#xff0c;这篇文章主要介绍我基于SpringBoot对应Servlet的浅薄认知&#xff0c;有利于更好的理解前端界面和java服务器的数据交换过程 快速入门 我比较推荐这篇文章来对Servlet有一个大概的了解 都2…