Log4j2切面AOP拦截exception

3 篇文章 0 订阅
3 篇文章 0 订阅

简介

使用AOP拦截Throwable,自定义一些信息输出

更新修改记录

20190717:大改LogAopUtil文件和ErrorAopLog(调用的LogAopUtil方法返回类型由StringBuffer改为StringBuilder.)
LogAopUtil修改根据request请求类型是GET还是POST来分不同方式获取请求的参数.(因为原有网上抄袭的方法,会遇到尝试用FASTJSON输出不是序列化的request报错问题)
修改POST时获取请求的输出内容不是标准JSON格式,把参数名和值放入Map,交给FastJson格式化输出
20190625:修改ErrorAopLog.java文件,移除logger.error("异常基本信息为:{} ", e.fillInStackTrace().toString());这行代码导致输出的报错信息会有缺失.
相关知识点链接
https://blog.csdn.net/yangkai_hudong/article/details/18409007
https://blog.csdn.net/amanicspater/article/details/72721996

实施

配置Log4j2略…

具体配置可以参考之前博文内容
Log4j无缝升级到Log4j2+Slf4j

添加Maven的切面依赖

	<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.7.1</version>
    </dependency>

开启SpringMVC的cglib代理

找到项目的springMVC的xml配置文件.修改代理模式.

    <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller -->
	<aop:aspectj-autoproxy proxy-target-class="true" />

添加一个LogAopUtil 类

LogAopUtil 为了方便输出方法的参数和对应值

package cn.com.example.util;


import com.alibaba.fastjson.JSON;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 获取AOP代理的方法的参数名称和参数值工具类
 * 修改尝试获取Request的参数内容
 * @author shengquan
 */
public class LogAopUtil {
	private static final Logger LOGGER = getLogger(LogAopUtil.class);

	private LogAopUtil(){

	}
	public static StringBuilder getNameAndArgs(
			Class<?> cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
		Map<String, Object> nameAndArgs = new HashMap<String, Object>();
		ClassPool pool = ClassPool.getDefault();
		ClassClassPath classPath = new ClassClassPath(cls);
		pool.insertClassPath(classPath);
		CtClass cc = pool.get(clazzName);
		CtMethod cm = cc.getDeclaredMethod(methodName);
		MethodInfo methodInfo = cm.getMethodInfo();
		CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
		LocalVariableAttribute attr =
				(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
		Object valueObj;
		boolean isGetMethod = false;
		boolean isPostMethod = false;
		if (attr != null) {
			int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
			for (int i = 0; i < cm.getParameterTypes().length; i++) {
				valueObj = args[i];
				// 对于 ServletRequest  ServletResponse MultipartFile 无法直接JsonToString()会抛出错误需要特殊处理
				if (!(valueObj instanceof ServletRequest
						|| valueObj instanceof ServletResponse
						|| valueObj instanceof MultipartFile)) {
					nameAndArgs.put(attr.variableName(i + pos), args[i]);
				} else {
					if (valueObj instanceof ServletRequest) {
						//GET方法的参数获取方式 放入nameAndArgs
						if ("GET".equals(((HttpServletRequest) valueObj).getMethod())) {
							isGetMethod = true;

						} else {
							//如果是POST方法的参数获取方式,不需要使用nameAndArgs,靠后面的方式去获取参数.
							if ("POST".equals(((HttpServletRequest) valueObj).getMethod())) {
								isPostMethod = true;
								break;
							}
						}
					}
				}
			}
		}

		StringBuilder sb = new StringBuilder();
		//判定 HttpServletRequest 请求方法是GET时
		if (isGetMethod) {
			try {
				sb.append(JSON.toJSONString(nameAndArgs));
			} catch (IllegalStateException ie) {
				LOGGER.error(ie.getMessage(), ie);
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}

		} else {
			//判定 HttpServletRequest 请求方法是POST时
			if (isPostMethod && args != null) {

				ServletRequest servletRequest;
				for (Object object : args) {
					if (object instanceof ServletRequest) {
						servletRequest = (ServletRequest) object;
						Enumeration<String> enumeration = servletRequest.getParameterNames();
						Map<String, Object> modelMap = new HashMap<>();
						while (enumeration.hasMoreElements()) {
							String parameterName = enumeration.nextElement();
							try {
								modelMap.put(
										parameterName,
										((ServletRequest) object).getParameter(parameterName));
							} catch (IllegalStateException ie) {
								LOGGER.error(ie.getMessage(), ie);
							} catch (Exception e) {
								LOGGER.error(e.getMessage(), e);
							}
						}
						sb.append(JSONObject.toJSONString(modelMap, SerializerFeature.WriteMapNullValue));
					}
				}
			}
		}

		return sb;
	}
}


添加切面拦截

切面拦截有两种实现方式,一种是xml配置文件,另一种是注解方式.
大家根据需求选择一种来使用即可.

xml方式配置切面

Step1.修改SpringMVC的xml配置文件

在刚才SpringMVC的xml文件添加的内容下面可以继续添加切面配置

<aop:config>
		<!--定义切面-->
		<aop:aspect id="logAspect" ref="errorAopLog ">
			<!--定义切入点-->
			<aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))"  id="logPointCut"/>
			<!--方法执行之前被调用执行的-->
			<!--<aop:before method="before" pointcut-ref="logPointCut"/>&lt;!&ndash;一个切入点的引用&ndash;&gt;-->
			<!--<aop:after method="after" pointcut-ref="logPointCut"/>&lt;!&ndash;一个切入点的引用&ndash;&gt;-->
			<!--<aop:after-throwing method="afterThrowing" pointcut-ref="logPointCut" />-->
			<!--<aop:after-returning method="afterReturn" pointcut-ref="logPointCut" />-->
			<aop:around method="around" pointcut-ref="logPointCut"/>
		</aop:aspect>
	</aop:config>
xml配置含义
  1. 下面切面规则根据自己实际项目路径进行修改.
    	execution(* cn.com.example.*.controller.*.*(..))
    
  2. <aop:around method="around" pointcut-ref="logPointCut"/>
    
    **method=“around”**指向
    <aop:aspect id="logAspect" ref="errorAopLog ">
    
    中ref="errorAopLog "对应的springbean中的方法名.
    里的logPointCut指向
    <aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))"  id="logPointCut"/>
    
  3. id=“logPointCut”pointcut-ref=“logPointCut” 相对应,表示符合logPointCut切面规则(“execution(* cn.com.example..controller..*(…))”)的代码,<aop:around 标签决定在方法执行前后会触发ErrorAopLog 类的around方法处理.(具体around,before,after用法可以百度下AOP around…之类了解下)
    (ErrorAopLog .around 最后的around方法名只是起名跟aop:around一致方便对应,其实可以随便修改method=“方法名”,ErrorAopLog .方法名)
    <aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))"  id="logPointCut"/>
    
    <aop:around method="around" pointcut-ref="logPointCut"/>
    
Step2.添加拦截的处理代码

创建java类文件ErrorAopLog

package cn.com.example;


import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
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.HttpSession;
import java.util.Arrays;

import static org.slf4j.LoggerFactory.getLogger;

@Aspect
@Component
public class ErrorAopLog {
  private static final Logger logger = getLogger(ErrorAopLog.class);
  //    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

  String logStr = null;
  //    //前置通知
  //    public void before(JoinPoint jp){
  //        System.out.println("==========aaaaaaaaaaaaaa");
  //        logStr=jp.getTarget().getClass().getName()+"类的"
  //                +jp.getSignature().getName()+"方法开始执行******Start******";
  //        logger.info(logStr);
  //    }
  //      //最终通知
  //    public void after(JoinPoint jp){
  //        logStr=jp.getTarget().getClass().getName()+"类的"
  //                +jp.getSignature().getName()+"方法执行结束******End******";
  //        logger.info(logStr);
  //    }
  //      //异常抛出后通知
  //    public void afterThrowing(JoinPoint call){
  //        String className = call.getTarget().getClass().getName();
  //        String methodName = call.getSignature().getName();
  //        System.out.println(className+"."+methodName+"()方法抛出了异常...");
  //    }
  //     //后置通知
  //    public void afterReturn(JoinPoint call){
  //        String className = call.getTarget().getClass().getName();
  //        String methodName = call.getSignature().getName();
  //        System.out.println(className+"."+methodName+"()方法正常执行结束...");
  //    }

  // 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
  public Object around(ProceedingJoinPoint call) throws Throwable {
    Object result = null;

    // 取得类名和方法名
    //        String className = call.getTarget().getClass().getName();
    //        String methodName = call.getSignature().getName();

    // 相当于前置通知
    //        logStr=className+"类的"+methodName+"方法开始执行******Start******";
    //        logger.info(logStr);

    try {
      result = call.proceed();
      // 相当于后置通知
      //            logStr=className+"."+methodName+"()方法正常执行结束...";
      //            logger.info(logStr);

    } catch (Throwable e) {
      // 相当于异常抛出后通知
      //            StackTraceElement stackTraceElement= e.getStackTrace()[
      // e.getStackTrace().length-1];
      StackTraceElement stackTraceElement = e.getStackTrace()[0];
      String className = stackTraceElement.getClassName();
      String methodName = stackTraceElement.getMethodName();
      Object[] args = call.getArgs();

      logger.error(
          "----------------------------------------------------------------------------------");
      HttpServletRequest request =
          ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
      if (request != null) {
       

        HttpSession session = request.getSession();
        if (session != null) {

          // 读取session中的用户
          EnterpriseLogin user = (EnterpriseLogin) session.getAttribute(EnterpriseLogin.USER);
          if (user != null) {
            logger.error(
                "用户id:{},用户登录名:,企业id:{}",
                user.getId(),
                user.getLoginName(),
                user.getEnterprise().getindustry_id());
          }
        }
 		// 获取Ip 来源IpAddressUtil:https://blog.csdn.net/qq_19167629/article/details/85280943	
        String ipAddress = IpAddressUtil.getIpAdrress(request);
        if (StringUtils.isNotBlank(ipAddress)) {
          logger.error("访问的IP:{}", ipAddress);
        }
      }

      logger.error("执行{}类的{}()方法的{}行", className, methodName, stackTraceElement.getLineNumber());
    //  logger.error("异常基本信息为:{}  ", e.fillInStackTrace().toString());
      //            logger.error("===参数信息为:{}  ",args);
      //            logger.error("===异常信息e.getStackTrace()为:{}  ",e.getStackTrace());
      logger.error("请求类方法:{}", call.getSignature());
      logger.error("请求类方法参数:{}", Arrays.toString(call.getArgs()));

      String classType = call.getTarget().getClass().getName();
      Class<?> clazz = Class.forName(classType);
      String clazzName = clazz.getName();
      String methodName2 = call.getSignature().getName(); // 获取方法名称
      // 获取参数名称和值
      StringBuilder sb = LogAopUtil.getNameAndArgs(this.getClass(), clazzName, methodName2, args);
      logger.error("请求类方法参数名称和值:" + sb);
      logger.error("异常详细信息:", e);

      // 下面这种输出e的 不需要占位符 {},属于logger本身输出方法error(String str,Throwable throwable)
      //            logger.error("===异常信息e为:{}  ",e);
      throw e;

    } finally {
      // 相当于最终通知
      //            logStr=className+"类的" +methodName+"方法执行结束******End******";
      //            logger.info(logStr);
    }
    return result;
  }
}

备注:

java注解方式配置切面

就一个java文件

package cn.com.example.analysis.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

import static org.slf4j.LoggerFactory.getLogger;


@Aspect
@Component
public class ErrorAopLog {
    private static final Logger logger = getLogger(ErrorAopLog.class);
    String logStr=null;

    /**
     * 切点 与xml配置的语法一致,自行根据需求调整适合自己的项目结构
     */
    @Pointcut(value="execution(* cn.com.example.*.controller.*.*(..))")
    public void pointCutOne(){

    }

    //@Around对应xml里的around
    @Around(value = "pointCutOne()")
    public Object around(ProceedingJoinPoint call) throws Throwable {
        //TODO:与上面类似省略
    }

    @AfterThrowing(pointcut="pointCutOne()",throwing="e")
    public void afterThrowable(Throwable e)  throws Throwable{
        //TODO:抛出异常后想做的事情
    }

}

效果展现

在项目里随便写了一个报错代码

int i=1/0;

输出日志如下

2019-05-10 16:39:48.548 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:90) - ----------------------------------------------------------------------------------
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:113) - 访问的IP:127.0.0.1
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:117) - 执行cn.com.example.login.service.impl.LoginServiceImpl类的findEnLogin()方法的33行
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:118) - 异常基本信息为:java.lang.ArithmeticException: / by zero  
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:121) - 请求类方法:String cn.com.example.login.controller.LoginController.LoginAction(HttpServletRequest,HttpServletResponse)
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:122) - 请求类方法参数:[cn.com.example.util.ParameterRequestWrapper@5e4f2dd1, org.apache.catalina.connector.ResponseFacade@606e938c]
2019-05-10 16:39:48.550 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:130) - 请求类方法参数名称和值:loginName:"保密";password:"保密";checkcode:"5f90";
2019-05-10 16:39:48.550 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:131) - 异常详细信息:
java.lang.ArithmeticException: / by zero
	at cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:118) [classes/:?]
	at sun.reflect.GeneratedMethodAccessor189.invoke(Unknown Source) ~[?:?]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_181]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_181]
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]

遗留问题

看上面测试的日志结果不难看出,抛出了异常,但是并不是原有的异常输出内容,被抛出位置是在AOP的java方法类上抛出.还没有解决希望抛出在原始的那层异常处抛出.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值