代码测试,上线后的调试手段现在很多都喜欢用日志来处理,当我们需要查询某个接口报错的时候,直接看日志,问题描述不清晰,

会发现不知道前端传的什么参数,需要找前端确认,很耗时,所以加了一个请求入参和响应的拦截配置.

需要引入Spring的Aop相关的包:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

创建一个Controller层的切面拦截:

package com.test.logAop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;/*** MVC 日志切面 记录** @author Demon-HY* @note spring-boot-devtools 与切面有冲突,导至在本地开发swagger-ui展示不出来*/
@Aspect
public class LogAroundAop {/*** 在Controller 加日志切面* 拦截 @RestController 注解的类,忽略 @LogIgnore 注解的类或接口*/@Pointcut(value = "((@within(org.springframework.web.bind.annotation.RestController))"+ "||(@within(org.springframework.stereotype.Controller))"+ ")")public void logAround() {}// 请求进入前@Before("logAround()")public void doBefore(JoinPoint joinPoint) {}// 请求正常返回@AfterReturning(value = "logAround()", returning = "result")public void doAfterReturning(JoinPoint joinPoint, Object result) {}// 请求返回异常@AfterThrowing(value = "logAround()", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {}
}

所有注解了 @RestController和@Controller的类都可以被拦截到,里面有三个切面方法,分别是:

doBefore: 请求进入前拦截,记录请求日志

doAfterReturing:请求正常返回

doAfterThrowing:请求异常返回,这里可以拿到接口异常,但没办法处理异常,异常还是会抛给JVM,所有不要在里面使用try/catch

接下来我们在里面记录请求的入参和出参:

package com.test.logAop;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.xubei.framework.util.net.ServletRequestUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
import java.util.UUID;/*** MVC 日志切面 记录** @author Demon-HY* @note spring-boot-devtools 与切面有冲突,导至在本地开发swagger-ui展示不出来*/
@Aspect
public class LogAroundAop {private Logger logger = LoggerFactory.getLogger(this.getClass());// mvc 出参打印的最大长度字符数@Value("${server.mvc.print.return.limit:1024}")private Integer retStrLimit;// 记录请求时间private static final ThreadLocal<Long> REQUEST_TIME = new ThreadLocal<>();// 请求唯一标识private static final ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();private static void setRequestTime(Long requestTime) {REQUEST_TIME.set(requestTime);}private static Long getRequestTime() {return REQUEST_TIME.get();}private static void setRequestId() {REQUEST_ID.set(UUID.randomUUID().toString().trim().replaceAll("-", "").substring(0, 12).toUpperCase());}private static String getRequestId() {return REQUEST_ID.get();}// 清除本地线程的数据private static void removeThreadLocal() {REQUEST_TIME.remove();REQUEST_ID.remove();}/*** 在Controller 加日志切面,单个接口排除日志打印:  {@link com.test.logAop.LogIgnore}注解<br/>* 拦截 @RestController 注解的类,忽略 @LogIgnore 注解的类或接口*/@Pointcut(value = "((@within(org.springframework.web.bind.annotation.RestController))"
//            + "||(@within(org.springframework.stereotype.Controller))"
//            + "||(@annotation(org.springframework.web.bind.annotation.GetMapping))"
//            + "||(@annotation(org.springframework.web.bind.annotation.PostMapping))"
//            + "||(@annotation(org.springframework.web.bind.annotation.RequestMapping))"+ ") && !(@within(com.test.logAop.LogIgnore))")public void logAround() {}// 请求进入前@Before("logAround()")public void doBefore(JoinPoint joinPoint) {// 记录请求时间setRequestTime(System.currentTimeMillis());// 记录一个请求的唯一ID,将该请求ID写入响应头,方便查找到该条日志setRequestId();ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest req = attributes.getRequest();HttpServletResponse resp = attributes.getResponse();// 请求的唯一标识,客户端通过这个可以查询到该次请求记录resp.setHeader("RequestId", getRequestId());// 处理完请求,返回内容logger.info("REQ= IP:{} RequestId:{} Method:{} Uri:{} Header:{} Param:{}",getIPAddr(req), getRequestId(), req.getMethod(), getRequestUrl(req),getRequestHeader(req), getRequestParams(joinPoint));}// 请求正常返回@AfterReturning(value = "logAround()", returning = "result")public void doAfterReturning(JoinPoint joinPoint, Object result) {try {// 记录一个请求的唯一ID,将该请求ID写入响应头,方便查找到该条日志String requestId = UUID.randomUUID().toString().trim().replaceAll("-", "").substring(0, 16).toUpperCase();ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest req = attributes.getRequest();HttpServletResponse resp = attributes.getResponse();// 请求的唯一标识,客户端通过这个可以查询到该次请求记录resp.setHeader("RequestId", requestId);// 处理完请求,返回内容logger.info("RESP= IP:{} RequestId:{} Method:{} Uri:{} Header:{} Param:{} Result:{} Time:{}",getIPAddr(req), requestId, req.getMethod(), getRequestUrl(req), getRequestHeader(req),getRequestParams(joinPoint), getResponseBody(result), getRequestTaking());} finally {removeThreadLocal();}}// 请求返回异常@AfterThrowing(value = "logAround()", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {try {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest req = attributes.getRequest();HttpServletResponse resp = attributes.getResponse();// 请求的唯一标识,客户端通过这个可以查询到该次请求记录resp.setHeader("RequestId", getRequestId());// TODO 这里可以捕获异常,但无法处理异常,异常还是会抛给 JVM// 处理完请求,返回内容logger.error("RESP= IP:{} RequestId:{} Method:{} Uri:{} Header:{} Param:{} Error:{} Time:{}",getIPAddr(req), getRequestId(), req.getMethod(), getRequestUrl(req), getRequestHeader(req),getRequestParams(joinPoint), e.getMessage(), getRequestTaking(), e);} finally {removeThreadLocal();}}// 获取请求路径private String getRequestUrl(HttpServletRequest req) {return req.getRequestURL().toString();}// 获取请求头private Map<String, List<String>> getRequestHeader(HttpServletRequest req) {return ServletRequestUtil.getRequestHeaderMap(req);}// 获取请求参数private Map<String,Object> getRequestParams(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();Map<String,Object> parameters=Maps.newLinkedHashMap();Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();//-parameters  设置带参数名编译for (int i = 0; i < method.getParameters().length; i++) {if(args[i] instanceof ServletRequest || args[i] instanceof ServletResponse){continue;}parameters.put(method.getParameters()[i].getName(),args[i]);}return parameters;}// 获取返回结果private String getResponseBody(Object result) {String resultObj = "";try {resultObj = JSON.toJSONString(result);resultObj = resultObj.length() > retStrLimit ? resultObj.substring(0, retStrLimit - 1) : resultObj;} catch (Exception e) {e.printStackTrace();}return resultObj;}// 获取请求耗时,单位毫秒private Long getRequestTaking() {Long endTime = System.currentTimeMillis();return endTime - getRequestTime();}// 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,// 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值private static String getIPAddr(HttpServletRequest request) {String ipAddress;try {ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if (ipAddress.equals("127.0.0.1") || ipAddress.equals("localhost")) {// 根据网卡取本机配置的IPInetAddress inet;try {inet = InetAddress.getLocalHost();ipAddress = inet.getHostAddress();} catch (UnknownHostException e) {e.printStackTrace();}}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()// = 15if (ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));}}} catch (Exception e) {ipAddress = "";}return ipAddress;}
}

里面多了一个 LogIgnore注解,这个注解的作用是为了忽略掉不需要记录日志的请求,直接可以加在Controller层的类或方法上,

注解代码如下:

package com.test.logAop;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 忽略日志切面的注解 {@link com.test.logAop.LogAroundAop}** @author Demon-HY*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogIgnore {String value() default "";
}

接下来我们需要把它注入到Spring容器中:

package com.test.logAop;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 注入 MVC 请求日志记录 Bean,需要在 application.properties 中配置 server.mvc.print.enabled=true,默认是开启的** @author Demon-HY*/
@Configuration
public class LogAopAutoConfiguration {@Bean@ConditionalOnProperty(prefix = "server.mvc.print", name = "enabled", matchIfMissing = true)public LogAroundAop logAroundAop() {return new LogAroundAop();}
}

接下来就可以在代码里面直接使用了

SpringBoot - LogAroundAop MVC请求日志拦截相关推荐

  1. Springboot starter开发之traceId请求日志链路追踪

    一.请求链路追踪是什么? 能标识一次请求的完整流程,包括日志打印.响应标识等,以便于出现问题可以快速定位并解决问题. 二.使用步骤 1. 相关知识点 ThreadLocal:一种保证一种规避多线程访问 ...

  2. java创建请求拦截器_80.简单Retrofit+RxJava+日志拦截器结合使用

    1.需要使用到的依赖如下(Retrofit\RxJava\RecyclerView\日志拦截器) //only Retrofit(只用Retrofit联网) implementation 'io.re ...

  3. SpringBoot AOP完全讲解二:统一处理请求日志

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/80669451 本文出自[赵彦军的博客] 前言 不了解 AOP 概念的同学,请先看 ...

  4. SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)

    学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识  1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...

  5. Springboot使用AOP记录请求日志和返回数据

    首先是日志表结构 DROP TABLE IF EXISTS `protal_logvo`; CREATE TABLE `protal_logvo` (`id` varchar(255) NOT NUL ...

  6. spring mvc中的拦截器

    本文说下spring MVC中的拦截器 文章目录 拦截器介绍 拦截器注入适配器 自定义拦截器 controller测试 测试结果 本文小结 拦截器介绍 拦截器是在servlet执行之前执行的程序(这里 ...

  7. SpringBoot Spring MVC

    一.初始Spring MVC 1.1 什么是Spring MVC 1.Spring MVC是Spring框架的一部分   Spring MVC是一种轻量级的.基于MVC的Web层应用框架.Spring ...

  8. 如何使用SpringBoot AOP 记录操作日志、异常日志?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:咫尺的梦想_w cnblogs.com/wm-dv/ ...

  9. SpringBoot 之 MVC

    SpringBoot 之 MVC SpringBoot MVC 和静态资源 首先,我们一定要搞清楚,mvc 配置和 static 配置的联系和区别. mvc 配置其实就是给 spring mvc 框架 ...

最新文章

  1. spring 下载地址
  2. sql server 用户'sa'登录失败(错误18456)
  3. oracle ora06576,创建oracle发邮件job导致的故障
  4. centos7.5部署ELk
  5. 【JavaScript】Canvas绘图整理
  6. 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)
  7. 用计算机探索奥秘规律例题,人教新课标小学五年级上册数学《用计算器探索规律》教案...
  8. android 关联源码
  9. api es7 删除所有数据_男子让月薪6万的人技术入股,结果工作3月蒙了:删除所有数据入职其他公司...
  10. mysql慢日志分析工具_mysql慢查日志分析工具 percona-toolkit
  11. php ://input 消息头 消息体,php curl_init curl 消息头和消息体
  12. u盘写保护终极修复方法
  13. 微信小程序——订阅号和服务号区别
  14. mysql读写分离延迟_解决Mysql读写分离数据延迟
  15. 英语口语收集(十六)
  16. ORACLE 11GR2 配置GATEWAY FOR SERVER 问题
  17. 应用礼学赋能新员工职业素养提升
  18. 好用看得见!深度操作系统Deepin装机使用体验
  19. 干货来了,JAVA代码实现图片分割、合并工具类
  20. 17暑期ACM俱乐部个人训练赛第1场 (石油oj) 7.24号

热门文章

  1. 【BZOJ2069】ZAW(POI2004)-最短路+二进制分组
  2. 菩提本无树,明镜亦非台,本来无一物,何处惹尘埃(学习)
  3. 开发板浮点型运算能力测试
  4. 4个小方法,让你的抖音发出来的视频更清楚
  5. 搭建Prometheus监控报警
  6. qt 串口助手 界面美化
  7. IEEE754标准转换
  8. UltraEdit中文破解版
  9. 免费天气接口php,360免费天气Api接口(附使用示例)
  10. 从新一代华为全屋智能解决方案看智能家居的本质