SpringBoot自定义日志–实现输出接口信息
在项目开发过程中我们排查错误过程中,为了更清晰明了的查看接口调用情况,我们会将接口的入参和响应进行Log打印,工作原理就是利用Spring的Aop进行日志打印,接下来我们一起进入正题
先上效果图
1. 请求
2. 控制台日志
一、准备工作
- SpringBoot项目
- pom依赖 (SpringBoot基础依赖,Aop依赖,Swagger依赖 )
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- aop 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--json依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency> <!--工具列--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.10</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.22</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
- 准备Controller接口,参考如下
package com.kid510.web; import com.kid510.config.WebLogAspectConfig; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * @Author kid * @Classname HelloController * @Description 测试 * @Date 2020/8/23 23:03 */ @RestController public class HelloController { private final static Logger logger = LoggerFactory.getLogger(HelloController.class); @GetMapping(value = "/hello") @ApiOperation(value = "测试请求") public Map<String, Object> hello(@RequestParam Map<String, Object> reqMap) { // 返回值map Map<String, Object> respMap = new HashMap<>(); // 额外赋值 respMap.put("key1", 123); respMap.put("key2", "123"); respMap.put("key3", true); // 入参赋值 reqMap.forEach(respMap::put); logger.info("~~~~~~~~业务逻辑日志~~~~~~~~"); return respMap; } }
二、Aop配置
-
新建Aop配置类
package com.kid510.config; import com.kid510.utils.JsonUtils; import io.swagger.annotations.ApiOperation; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.ResponseFacade; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; 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 org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.ArrayList; /** * @author kid * @Classname WebLogAspect * @Description 打印日志 -- 具体实现 * @Date 2020/4/3 13:50 */ @Aspect @Component //@Profile({"dev", "test"}) public class WebLogAspectConfig { private final static Logger logger = LoggerFactory.getLogger(WebLogAspectConfig.class); /** * 换行符 */ private static final String LINE_SEPARATOR = System.lineSeparator(); /** * 以自定义 @ApiOperation 注解为切点 */ @Pointcut("@annotation(io.swagger.annotations.ApiOperation)") public void webLog() { } /** * 在切点之前织入 */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取 @WebLog 注解的描述信息 String methodDescription = getAspectLogDescription(joinPoint); // 打印请求相关参数 logger.info("========================================== Start =========================================="); // 打印请求 url logger.info("URL : {}", request.getRequestURL().toString()); // 打印描述信息 logger.info("Description : {}", methodDescription); // 打印 Http method logger.info("HTTP Method : {}", request.getMethod()); // 打印调用 controller 的全路径以及执行方法 logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); // 打印请求的 IP logger.info("IP : {}", request.getRemoteAddr()); // 打印请求入参 Object[] args = joinPoint.getArgs(); ArrayList<Object> objects = new ArrayList<>(); // request 对象或者 response对象不打印信息 for (Object arg : args) { // 不属于这些的话 打印 if (!(arg instanceof ResponseFacade || arg instanceof RequestFacade || arg instanceof MultipartFile)) { objects.add(arg); } } logger.info("Request Args : {}", JsonUtils.serialize(objects)); logger.info("===================================== Business Log Start ==================================="); } /** * 在切点之后织入 */ @After("webLog()") public void doAfter() { } /** * 环绕 */ @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); logger.info("====================================== Business Log End ===================================="); // 打印出参 logger.info("Response Args : {}", JsonUtils.serialize(result)); // 执行耗时 logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); // 接口结束后换行,方便分割查看 logger.info("=========================================== End ===========================================" + LINE_SEPARATOR); return result; } /** * 获取切面注解的描述 * * @param joinPoint 切点 * @return 描述信息 */ public String getAspectLogDescription(JoinPoint joinPoint) throws Exception { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); StringBuilder description = new StringBuilder(""); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { description.append(method.getAnnotation(ApiOperation.class).value()); break; } } } return description.toString(); } }
-
实现原理 – 拦截了 接口上面 @ApiOperation注解,描述信息取得是 该注解的value值,大家可以根据自己的需要配置不同的信息
比如说request 对象或者 response对象不打印信息
或者MultipartFile
这些信息入参不打印,再有一些响应信息过大,觉得没必要打印,也可以通过配置进行解决
三、最后的话
- 这个接口打印日志是Aop很好的一个实践,希望有帮助到大家,项目中运用是结合logback等日志收集使用
- 最后附上gitee地址:日志输入接口信息