SpringBoot - LogAroundAop MVC请求日志拦截
代码测试,上线后的调试手段现在很多都喜欢用日志来处理,当我们需要查询某个接口报错的时候,直接看日志,问题描述不清晰,
会发现不知道前端传的什么参数,需要找前端确认,很耗时,所以加了一个请求入参和响应的拦截配置.
需要引入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请求日志拦截相关推荐
- Springboot starter开发之traceId请求日志链路追踪
一.请求链路追踪是什么? 能标识一次请求的完整流程,包括日志打印.响应标识等,以便于出现问题可以快速定位并解决问题. 二.使用步骤 1. 相关知识点 ThreadLocal:一种保证一种规避多线程访问 ...
- java创建请求拦截器_80.简单Retrofit+RxJava+日志拦截器结合使用
1.需要使用到的依赖如下(Retrofit\RxJava\RecyclerView\日志拦截器) //only Retrofit(只用Retrofit联网) implementation 'io.re ...
- SpringBoot AOP完全讲解二:统一处理请求日志
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/80669451 本文出自[赵彦军的博客] 前言 不了解 AOP 概念的同学,请先看 ...
- SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)
学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识 1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...
- Springboot使用AOP记录请求日志和返回数据
首先是日志表结构 DROP TABLE IF EXISTS `protal_logvo`; CREATE TABLE `protal_logvo` (`id` varchar(255) NOT NUL ...
- spring mvc中的拦截器
本文说下spring MVC中的拦截器 文章目录 拦截器介绍 拦截器注入适配器 自定义拦截器 controller测试 测试结果 本文小结 拦截器介绍 拦截器是在servlet执行之前执行的程序(这里 ...
- SpringBoot Spring MVC
一.初始Spring MVC 1.1 什么是Spring MVC 1.Spring MVC是Spring框架的一部分 Spring MVC是一种轻量级的.基于MVC的Web层应用框架.Spring ...
- 如何使用SpringBoot AOP 记录操作日志、异常日志?
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:咫尺的梦想_w cnblogs.com/wm-dv/ ...
- SpringBoot 之 MVC
SpringBoot 之 MVC SpringBoot MVC 和静态资源 首先,我们一定要搞清楚,mvc 配置和 static 配置的联系和区别. mvc 配置其实就是给 spring mvc 框架 ...
最新文章
- spring 下载地址
- sql server 用户'sa'登录失败(错误18456)
- oracle ora06576,创建oracle发邮件job导致的故障
- centos7.5部署ELk
- 【JavaScript】Canvas绘图整理
- 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)
- 用计算机探索奥秘规律例题,人教新课标小学五年级上册数学《用计算器探索规律》教案...
- android 关联源码
- api es7 删除所有数据_男子让月薪6万的人技术入股,结果工作3月蒙了:删除所有数据入职其他公司...
- mysql慢日志分析工具_mysql慢查日志分析工具 percona-toolkit
- php ://input 消息头 消息体,php curl_init curl 消息头和消息体
- u盘写保护终极修复方法
- 微信小程序——订阅号和服务号区别
- mysql读写分离延迟_解决Mysql读写分离数据延迟
- 英语口语收集(十六)
- ORACLE 11GR2 配置GATEWAY FOR SERVER 问题
- 应用礼学赋能新员工职业素养提升
- 好用看得见!深度操作系统Deepin装机使用体验
- 干货来了,JAVA代码实现图片分割、合并工具类
- 17暑期ACM俱乐部个人训练赛第1场 (石油oj) 7.24号