Java——AOP实现监控日志功能(java项目日志监控平台)

java知识点整理正在进行中,关注我,持续给您带来简单,实用的Java编程技巧。

最近在做一个接口项目,需要一个能够记录接口运行情况的监控日志。想在日志中记录:

  1. 接口的输入参数,返回结果
  2. 调用接口的IP地址,调用的是那个接口
  3. 接口运行时的异常信息
  4. 接口的响应时间

结合具体的使用环境,还需要:

  1. 希望记录日志能以统一的方式运行,记录日志的代码不写在具体的业务逻辑中
  2. 可以方便的设置是否记录日志
  3. 在设置时可以灵活地确定记录在那个日志文件中

针对以上要求,结合前阵子做过的一个自定义注解记录接口运行时间的例子,发现这个需求可以认为是之前例子的升级版。整体思路梳理了下:

  • 使用AOP来截取调用接口的相关信息,包括请求的IP,请求的是那个接口,调用的参数和返回结果,还有异常信息
  • 使用自定义注解来确定是否记录日志
  • 使用注解的参数来确定日志记录到那个文件中

这样做就可以实现:

  1. 业务代码与日志代码的解耦
  2. 监控日志业务的灵活运用,可以方便的决定那个业务进行监控,同时可以灵活的调整日志记录在那个文件中

好,说干就干,代码开撸。

1. 引入依赖

<!-- AOP依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 获取运行时长 --><dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version></dependency><!-- 转义JSON --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version></dependency>

2. 编写自定义注解MonitorLog

package com.bbzd.mws.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 用于监控日志的注解 * @author mill * @date 2022/10/14 - 11:54 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface MonitorLog { String value();}

3. AOP的实现类

package com.bbzd.mws.aop;import com.alibaba.fastjson.JSON;import com.bbzd.mws.annotation.MonitorLog;import com.google.common.base.Stopwatch;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;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.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.Request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;/** * 业务监控日志 * 记录内容:请求IP,请求URI,业务类名,方法名,输入参数,返回值,异常信息 * @date 2022/10/12 - 10:12 */@Component@Aspectpublic class RequestParameterAOP { //以注解MonitorLog标记的方法为切入点 @Pointcut("@annotation(com.bbzd.mws.annotation.MonitorLog)") public void methodArgs(){} @Around("methodArgs()") public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable{ StringBuffer stringBuffer=new StringBuffer(); Object result=null; Stopwatch stopwatch = Stopwatch.createStarted(); HttpServletRequest httpServletRequest = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); String ipAddr=getRemoteHost(httpServletRequest); String requestUrl=httpServletRequest.getRequestURI(); stringBuffer.append("n请求源IP[" ipAddr "];"); stringBuffer.append("请求URL[" requestUrl "];"); Signature signature=joinPoint.getSignature(); MethodSignature methodSignature=(MethodSignature)signature; // 类名 String[] sourceName = signature.getDeclaringTypeName().split("."); String fullName=signature.getDeclaringTypeName(); String className = sourceName[sourceName.length - 1]; // 方法名 String methodName = signature.getName(); stringBuffer.append("n" className "." methodName ";"); // 参数名数组 String[] parameterNames = methodSignature.getParameterNames(); Class[] parameterTypes=methodSignature.getParameterTypes(); // 构造参数组集合 List<Object> argList = new ArrayList<>(); for (Object arg : joinPoint.getArgs()) { // request/response无法使用toJSON if (arg instanceof HttpServletRequest) { argList.add("request"); } else if (arg instanceof HttpServletResponse) { argList.add("response"); } else { argList.add(JSON.toJSON(arg)); } } stringBuffer.append("n请求参数:" JSON.toJSON(parameterNames) "->" JSON.toJSON(argList)); try{ result=joinPoint.proceed(); }catch(exception e){ stringBuffer.append("n异常:" e.getMessage()); //log.info("获取参数失败:{}",e.getMessage()); } stopwatch.stop(); long timeConsuming = stopwatch.elapsed(TimeUnit.MILLISECONDS); if(result!=null){ stringBuffer.append("n请求结果:" JSON.toJSON(result)); }else{ stringBuffer.append("n请求结果:无"); } stringBuffer.append("n请求耗时:" timeConsuming "毫秒"); Logger logger=getLogger(fullName,methodName,parameterTypes); logger.info(stringBuffer.toString()); return result; } /** * 从请求中获取请求源IP * @param request * @return 请求源IP */ private String getRemoteHost(HttpServletRequest request){ String ip = request.getHeader("x-forwarded-for"); if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("Proxy-Client-IP"); } if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("WL-Proxy-Client-IP"); } if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){ ip = request.getRemoteAddr(); } return ip.contains("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip; } /** * 根据MonitorLog注解中的值,返回Logger * @param className MonitorLog所在方法对应的类名 * @param methodName MonitorLog所在方法对应的方法名 * @param paramTypes MonitorLog所在方法对应的参数名 * @return */ private Logger getLogger( String className, String methodName, Class[] paramTypes){ String logName="com.bbzd.mws.aop"; try{ Class clazz=Class.forName(className); logName=clazz.getDeclaredMethod(methodName, paramTypes).getAnnotation(MonitorLog.class).value(); }catch(Exception e){ e.printStackTrace(); } Logger logger= LoggerFactory.getLogger(logName); return logger; }}

4. 业务逻辑方法

@Override //com.bbzd.mws.aop是logger的名称,需要在日志文件中进行对应的配置@MonitorLog(value = "com.bbzd.mws.aop")public User getUserName(@Valid @RequestBody @WebParam(name="UserVo") UserVo vo) throws ConstraintViolationException { User user = new User(vo.getName(), vo.getAge()); try{ //模拟异常情况,测试异常信息的记录 //int i=1/0; }catch(NullPointerException exception){ exception.printStackTrace(); } return user;}

5. 日志配置文件

<!-- name属性值需要与注解的value属性值一样 --><logger name="com.bbzd.mws.aop" level="DEBUG" additivity="false"> <appender-ref ref="console"/> <appender-ref ref="monitor"/></logger>

6. 日志记录内容示例

[11:11:59.266][INFO][com.bbzd.mws.aop][http-nio-8889-exec-1] 请求源IP[127.0.0.1];请求URL[/mws/ws/user];UserServiceImpl.getUserName;请求参数:["vo"]->[{"name":"powerful","age":10}]请求结果:{"name":"powerful","age":10}请求耗时:56毫秒

总结

  1. POM文件是代码片段
  2. 配置文件是logback的代码片段
  3. 其它文件是完整的代码
  4. 关于logback日志框架及logback配置文件的使用方法,后面会整理一篇文章详细介绍下,想了解的小伙伴可以关注我。

相关新闻

联系我们
联系我们
公众号
公众号
在线咨询
分享本页
返回顶部