Java-AOP实现监控日志功能

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

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

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

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

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

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

这样做就可以实现:

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

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

1. 引入依赖



	org.springframework.boot
	spring-boot-starter-aop



	com.google.guava
	guava
	23.0



	com.alibaba
	fastjson
	1.2.75

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
@Aspect
public 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("
请求源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("
"+className+"."+methodName+";");

        // 参数名数组
        String[] parameterNames = methodSignature.getParameterNames();
        Class[] parameterTypes=methodSignature.getParameterTypes();
        // 构造参数组集合
        List 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("
请求参数:"+JSON.toJSON(parameterNames)+"->"+JSON.toJSON(argList));

        try{
            result=joinPoint.proceed();
        }catch(Exception e){
            stringBuffer.append("
异常:"+e.getMessage());
            //log.info("获取参数失败:{}",e.getMessage());
        }

        stopwatch.stop();
        long timeConsuming = stopwatch.elapsed(TimeUnit.MILLISECONDS);

        if(result!=null){
            stringBuffer.append("
请求结果:"+JSON.toJSON(result));
        }else{
            stringBuffer.append("
请求结果:无");
        }


        stringBuffer.append("
请求耗时:"+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. 日志配置文件



    
    

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配置文件的使用方法,后面会整理一篇文章详细介绍下,想了解的小伙伴可以关注我。

页面更新:2024-04-29

标签:法名   日志   注解   异常   接口   参数   代码   功能   业务   文件   方法

1 2 3 4 5

上滑加载更多 ↓
Top