为了保证服务的高可用,及时发现问题,迅速解决问题,为应用添加log是必不可少的。
大同ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:028-86922220(备注:SSL证书合作)期待与您的合作!
但是随着项目的增大,方法增多,每个方法加单独加日志处理会有很多冗余
那在SpringBoot项目中如何统一的处理Web请求日志?
基本思想:
采用AOP的方式,拦截请求,写入日志
AOP 是面向切面的编程,就是在运行期通过动态代理的方式对代码进行增强处理
基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.添加依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop
引入spring-boot-starter-web 依赖之后无需在引入相关的日志依赖,spring-boot-starter-web中已经集成了slf4j 的依赖
引入spring-boot-starter-aop 依赖之后,AOP 的功能即是启动状态
2.配置
application.properties添加
# AOP spring.aop.auto=true spring.aop.proxy-target-class=true
logback-spring.xml,主要是ControllerRequest那部分
<?xml version="1.0" encoding="UTF-8"?>info ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/debug/debug.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log 100MB 15 debug ACCEPT DENY ${log.path}/info/info.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/info/info-%d{yyyy-MM-dd}.%i.log 100MB 15 info ACCEPT DENY ${log.path}/warn/warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/error/error.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/error/error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY ${log.path}/request/info.log ${log.path}/request/info.log.%d{yyyy-MM-dd} 30 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
3..实现
实现切面的注解
(1)类注解
A. @Aspect 将一个java类定义为切面类
B. @order(i) 标记切面类的处理优先级,i值越小,优先级别越高。可以注解类,也能注解到方法上
(2)方法注解
A. @Pointcut 定义一个切入点,可以是一个表达式
execution表达式,eg:
任意公共方法的执行 execution(public * *(..)) 任何一个以“set”开始的方法的执行 execution(* set*(..)) 定义在controller包里的任意方法的执行 execution(public * com.example.demo.controller.*(..)) 定义在controller包里的任意方法的执行 execution(public * com.example.demo.controller.*.*(..)) 定义在controller包和所有子包里的任意类的任意方法的执行 execution(public * com.example.demo.controller..*.*(..))
B. 实现在不同的位置切入
C.@order(i) 标记切点的优先级,i越小,优先级越高
@order(i)注解说明
注解类,i值是,值越小,优先级越高
注解方法,分两种情况
注解的是 @Before 是i值越小,优先级越高
注解的是 @After或@AfterReturning 中,i值越大,优先级越高
具体实现
package com.example.demo.configure; 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.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; 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 java.lang.reflect.Method; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; @Aspect @Component public class WebRequestLogAspect { private final Logger loggerController = LoggerFactory.getLogger("ControllerRequest"); private final Logger logger = LoggerFactory.getLogger(WebRequestLogAspect.class); ThreadLocalstartTime = new ThreadLocal<>(); ThreadLocal beanName = new ThreadLocal<>(); ThreadLocal user = new ThreadLocal<>(); ThreadLocal methodName = new ThreadLocal<>(); ThreadLocal params = new ThreadLocal<>(); ThreadLocal remoteAddr = new ThreadLocal<>(); ThreadLocal uri = new ThreadLocal<>(); private static Map getFieldsName(ProceedingJoinPoint joinPoint) { // 参数值 Object[] args = joinPoint.getArgs(); ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String[] parameterNames = pnd.getParameterNames(method); Map paramMap = new HashMap<>(32); for (int i = 0; i < parameterNames.length; i++) { paramMap.put(parameterNames[i], args[i] + "(" + args[i].getClass().getSimpleName() + ")"); } return paramMap; } @Pointcut("execution(public * com.example.demo.controller..*.*(..))") public void webRequestLog() { } /** * 前置通知,方法调用前被调用 * @param joinPoint */ @Before("webRequestLog()") public void doBefore(JoinPoint joinPoint) { try { startTime.set(System.currentTimeMillis()); // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); beanName.set(joinPoint.getSignature().getDeclaringTypeName()); methodName.set(joinPoint.getSignature().getName()); uri.set(request.getRequestURI()); remoteAddr.set(getIpAddr(request)); user.set((String) request.getSession().getAttribute("user")); } catch (Exception e) { logger.error("***操作请求日志记录失败doBefore()***", e); } } /** * 环绕通知,环绕增强,相当于MethodInterceptor * @param thisJoinPoint */ @Around("webRequestLog()") public Object proceed(ProceedingJoinPoint thisJoinPoint) throws Throwable { Object object = thisJoinPoint.proceed(); Map fieldsName = getFieldsName(thisJoinPoint); params.set(fieldsName.toString()); return object; } /** * 处理完请求返回内容 * @param result */ @AfterReturning(returning = "result", pointcut = "webRequestLog()") public void doAfterReturning(Object result) { try { long requestTime = (System.currentTimeMillis() - startTime.get()) / 1000; loggerController.info("请求耗时:" + requestTime + ", uri=" + uri.get() + "; beanName=" + beanName.get() + "; remoteAddr=" + remoteAddr.get() + "; user=" + user.get() + "; methodName=" + methodName.get() + "; params=" + params.get() + "; RESPONSE : " + result); } catch (Exception e) { logger.error("***操作请求日志记录失败doAfterReturning()***", e); } } /** * 获取登录用户远程主机ip地址 * * @param request * @return */ private String getIpAddr(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(); if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) { //根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (Exception e) { e.printStackTrace(); } ip = inet.getHostAddress(); } } // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ip != null && ip.length() > 15) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; } }
4.测试类
package com.example.demo.controller; import com.alibaba.fastjson.JSONObject; import com.example.demo.dao.UserRepository; import com.example.demo.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController public class Demo { @RequestMapping (value = "test1") public String test1(@RequestParam(defaultValue = "0") Integer id,@RequestParam(defaultValue = "0")String name){ return id+name; } @RequestMapping("hello") public String hello() { return "Hello World!"; } @PostMapping("/updateStatus") public Object updateStatus(@RequestBody JSONObject jsonParam) { return jsonParam; } }
输出到logs/request/info.log内容
2019-09-11 13:31:45.729 [http-nio-8080-exec-4] INFO ControllerRequest - 请求耗时:0, uri=/test1; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=test1; params={name=abcdef(String), id=123(Integer)}; RESPONSE : 123abcdef 2019-09-11 13:32:16.692 [http-nio-8080-exec-5] INFO ControllerRequest - 请求耗时:0, uri=/updateStatus; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=updateStatus; params={jsonParam={"id":"17","type":3,"status":2}(JSONObject)}; RESPONSE : {"id":"17","type":3,"status":2} 2019-09-11 13:33:32.584 [http-nio-8080-exec-7] INFO ControllerRequest - 请求耗时:0, uri=/hello; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=hello; params={}; RESPONSE : Hello World!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持创新互联。