1 创建springboot项目
(ps:本文不做详细介绍,可以阅读另一篇博客:https://www.cnblogs.com/liyhbk/p/13572989.html)
1.1 添加pom依赖
<dependencies> <!--Spring Boot Web 基础环境--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Spring Boot 测试环境--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.xnx3.util/xnx3-util --> <dependency> <groupId>com.xnx3.util</groupId> <artifactId>xnx3-util</artifactId> <version>1.0.0</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <!--aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.7.RELEASE</version> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.5.5</version> </dependency> <!--接口文档--> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.21</version> </dependency> </dependencies>
1.2 配置application.yml文件
# 配置端口 server: port: 8084 spring: # 配置数据源 datasource: url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: # 配置mapper.xml 文件所在的路径 mapper-locations: classpath:mapper/*.xml # 配置映射类所在的路径 type-aliases-package: com.liyh.entity # 开启驼峰映射 configuration: map-underscore-to-camel-case: true #打印sql logging: level: com.liyh.mapper: debug
1.3 创建logback.xml文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!-- 彩色日志依赖的渲染类 --> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> <!-- 彩色日志格式 --> <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> <!-- Console 输出设置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender> <!-- 日志输出级别 --> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
2 配置AOP拦截器
2.1 spring-aop注解拦截顺序
2.1.1 正常运行:
2.1.2 程序报错:
2.2 创建控制器切面类 LogAspectj
package com.liyh.log; import com.liyh.entity.ExceptionLog; import com.liyh.entity.SqlLog; import com.liyh.entity.UserLog; import com.liyh.service.LogService; import com.liyh.utils.SqlUtils; import org.apache.ibatis.session.SqlSessionFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; /** * 控制器切面 * @Author: liyh * @Date: 2020/9/17 14:08 */ @Aspect @Component public class LogAspectj { @Autowired private LogService logService; @Autowired SqlSessionFactory sqlSessionFactory; private static Logger logger = LoggerFactory.getLogger(LogAspectj.class); /** * @Pointcut : 创建一个切点,方便同一方法的复用。 * value属性就是AspectJ表达式, */ @Pointcut("execution(* com.liyh.controller.*.*(..))") //@Pointcut("@annotation(com.liyh.log.LogAnno)") public void userLog() { } @Pointcut("execution(* com.liyh.mapper.*.*(..))") public void sqlLog() { } @Pointcut("execution(* com.liyh.controller.*.*(..))") public void exceptionLog() { } //前置通知 //指定该方法是前置通知,并指定切入点 @Before("userLog()") public void userLog(JoinPoint pj) { try { UserLog userLog = new UserLog(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String method = request.getMethod(); Signature signature = pj.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method targetMethod = methodSignature.getMethod(); if ("POST".equals(method) || "GET".equals(method)) { String ipAddress = getIpAddress(request); String requestId = (String) request.getAttribute("requestId"); // 根据请求参数或请求头判断是否有“requestId”,有则使用,无则创建 if (StringUtils.isEmpty(requestId)) { requestId = "req_" + System.currentTimeMillis(); request.setAttribute("requestId", requestId); } userLog.setRequestId(requestId); //请求id userLog.setMethodName(targetMethod.getName()); //方法名 userLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的类名 userLog.setRequestUrl(request.getRequestURL().toString());//请求URI userLog.setRemoteIp(ipAddress); //操作IP地址 System.out.println("userLog = " + userLog); // logService.saveUserLog(userLog); } } catch (Throwable throwable) { throwable.printStackTrace(); } } //环绕通知 @Around("sqlLog()") public Object sqlLog(ProceedingJoinPoint pj) throws Throwable { // 发送异步日志事件 long start = System.currentTimeMillis(); SqlLog sqlLog = new SqlLog(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Signature signature = pj.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method targetMethod = methodSignature.getMethod(); String ipAddress = getIpAddress(request); String requestId = (String) request.getAttribute("requestId"); // 根据请求参数或请求头判断是否有“requestId”,有则使用,无则创建 if (StringUtils.isEmpty(requestId)) { requestId = "req_" + System.currentTimeMillis(); request.setAttribute("requestId", requestId); } //执行方法 Object object = pj.proceed(); //获取sql String sql = SqlUtils.getMybatisSql(pj, sqlSessionFactory); //执行时长(毫秒) long loadTime = System.currentTimeMillis() - start; sqlLog.setRequestId(requestId); //请求id sqlLog.setMethodName(targetMethod.getName()); //方法名 sqlLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的类名 sqlLog.setRequestUrl(request.getRequestURL().toString());//请求URI sqlLog.setRemoteIp(ipAddress); //操作IP地址 sqlLog.setSql(sql);//sql sqlLog.setLoadTime(loadTime);//执行时间 System.out.println("sqlLog = " + sqlLog); // logService.saveSqlLog(sqlLog); return object; } //异常通知 用于拦截异常日志 @AfterThrowing(pointcut = "exceptionLog()", throwing = "e") public void exceptionLog(JoinPoint pj, Throwable e) { try { ExceptionLog exceptionLog = new ExceptionLog(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Signature signature = pj.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method targetMethod = methodSignature.getMethod(); String ipAddress = getIpAddress(request); String requestId = (String) request.getAttribute("requestId"); // 根据请求参数或请求头判断是否有“requestId”,有则使用,无则创建 if (StringUtils.isEmpty(requestId)) { requestId ="req_" + System.currentTimeMillis(); request.setAttribute("requestId", requestId); } exceptionLog.setRequestId(requestId); //请求id exceptionLog.setMethodName(targetMethod.getName()); //方法名 exceptionLog.setMethodClass(signature.getDeclaringTypeName()); //方法所在的类名 exceptionLog.setRequestUrl(request.getRequestURL().toString());//请求URI exceptionLog.setMessage(e.getMessage()); //异常信息 exceptionLog.setRemoteIp(ipAddress); //操作IP地址 System.out.println("exceptionLog = " + exceptionLog); // logService.saveExceptionLog(exceptionLog); } catch (Exception ex) { ex.printStackTrace(); } } /** * 获取IP地址的方法 * @param request 传一个request对象下来 * @return */ public String getIpAddress(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.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
2.3 定义注解类 LogAnno
package com.liyh.log; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author: liyh * @Date: 2020/9/17 17:12 */ @Target({ElementType.METHOD,ElementType.TYPE}) //作用于方法 使用在类,接口 @Retention(RetentionPolicy.RUNTIME) //运行时有效 public @interface LogAnno { @AliasFor("value") String[] operating() default {}; @AliasFor("operating") String[] value() default {}; }
2.4 添加json工具类和获取sql工具类

package com.liyh.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import java.io.IOException; /** * jsonUtil工具类 * @Author: liyh * @Date: 2020/9/17 17:12 */ public class JsonUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class); private static ObjectMapper mapper = new ObjectMapper(); /** * 对象转Json格式字符串 * @param obj 对象 * @return Json格式字符串 */ public static <T> String obj2String(T obj) { if (obj == null) { return null; } try { return obj instanceof String ? (String) obj : mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { LOGGER.warn("Parse Object to String error : {}", e.getMessage()); return null; } } /** * 对象转Json格式字符串(格式化的Json字符串) * @param obj 对象 * @return 美化的Json格式字符串 */ public static <T> String obj2StringPretty(T obj) { if (obj == null) { return null; } try { return obj instanceof String ? (String) obj : mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (JsonProcessingException e) { LOGGER.warn("Parse Object to String error : {}", e.getMessage()); return null; } } /** * 字符串转换为自定义对象 * @param str 要转换的字符串 * @param clazz 自定义对象的class对象 * @return 自定义对象 */ public static <T> T jsonToObj(String str, Class<T> clazz){ if(StringUtils.isEmpty(str) || clazz == null){ return null; } try { return clazz.equals(String.class) ? (T) str : mapper.readValue(str, clazz); } catch (Exception e) { LOGGER.warn("Parse String to Object error : {}", e.getMessage()); return null; } } /** * 集合对象与Json字符串之间的转换 * @param str 要转换的字符串 * @param typeReference 集合类型如List<Object> * @param <T> * @return */ public static <T> T jsonToObj(String str, TypeReference<T> typeReference) { if (StringUtils.isEmpty(str) || typeReference == null) { return null; } try { return (T) (typeReference.getType().equals(String.class) ? str : mapper.readValue(str, typeReference)); } catch (IOException e) { LOGGER.warn("Parse String to Object error", e); return null; } } /** * 集合对象与Json字符串之间的转换 * @param str 要转换的字符串 * @param collectionClazz 集合类型 * @param elementClazzes 自定义对象的class对象 * @param <T> * @return */ public static <T> T string2Obj(String str, Class<?> collectionClazz, Class<?>... elementClazzes) { JavaType javaType = mapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes); try { return mapper.readValue(str, javaType); } catch (IOException e) { LOGGER.warn("Parse String to Object error : {}" + e.getMessage()); return null; } } }

package com.liyh.utils; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.TypeHandlerRegistry; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.DateFormat; import java.util.*; /** * @Author: liyh * @Date: 2020/8/27 17:12 */ public class SqlUtils { /** * 获取aop中的SQL语句 * @param pjp * @param sqlSessionFactory * @return * @throws IllegalAccessException */ public static String getMybatisSql(ProceedingJoinPoint pjp, SqlSessionFactory sqlSessionFactory) throws IllegalAccessException { Map<String,Object> map = new HashMap<>(); //1.获取namespace+methodName MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); String namespace = method.getDeclaringClass().getName(); String methodName = method.getName(); //2.根据namespace+methodName获取相对应的MappedStatement Configuration configuration = sqlSessionFactory.getConfiguration(); MappedStatement mappedStatement = configuration.getMappedStatement(namespace+"."+methodName,true); // //3.获取方法参数列表名 // Parameter[] parameters = method.getParameters(); //4.形参和实参的映射 Object[] objects = pjp.getArgs(); //获取实参 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = 0;i<parameterAnnotations.length;i++){ Object object = objects[i]; if (parameterAnnotations[i].length == 0){ //说明该参数没有注解,此时该参数可能是实体类,也可能是Map,也可能只是单参数 if (object.getClass().getClassLoader() == null && object instanceof Map){ map.putAll((Map<? extends String, ?>) object); //System.out.println("该对象为Map"); }else{//形参为自定义实体类 map.putAll(objectToMap(object)); //System.out.println("该对象为用户自定义的对象"); } }else{//说明该参数有注解,且必须为@Param for (Annotation annotation : parameterAnnotations[i]){ if (annotation instanceof Param){ map.put(((Param) annotation).value(),object); } } } } //5.获取boundSql BoundSql boundSql = mappedStatement.getBoundSql(map); return showSql(configuration,boundSql); } /** * 解析BoundSql,生成不含占位符的SQL语句 * @param configuration * @param boundSql * @return */ private static String showSql(Configuration configuration, BoundSql boundSql) { Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\s]+", " "); if (parameterMappings.size() > 0 && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { sql = sql.replaceFirst("\?", getParameterValue(parameterObject)); } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); String[] s = metaObject.getObjectWrapper().getGetterNames(); s.toString(); if (metaObject.hasGetter(propertyName)) { Object obj = metaObject.getValue(propertyName); sql = sql.replaceFirst("\?", getParameterValue(obj)); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); sql = sql.replaceFirst("\?", getParameterValue(obj)); } } } } return sql; } /** * 若为字符串或者日期类型,则在参数两边添加'' * @param obj * @return */ private static String getParameterValue(Object obj) { String value = null; if (obj instanceof String) { value = "'" + obj.toString() + "'"; } else if (obj instanceof Date) { DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA); value = "'" + formatter.format(new Date()) + "'"; } else { if (obj != null) { value = obj.toString(); } else { value = ""; } } return value; } /** * 获取利用反射获取类里面的值和名称 * * @param obj * @return * @throws IllegalAccessException */ private static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException { Map<String, Object> map = new HashMap<>(); Class<?> clazz = obj.getClass(); //System.out.println(clazz); for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); String fieldName = field.getName(); Object value = field.get(obj); map.put(fieldName, value); } return map; } }
2.5 创建logController
package com.liyh.controller; import com.liyh.entity.User; import com.liyh.service.LogService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author: liyh * @Date: 2020/9/12 14:12 */ @RestController @RequestMapping("/log") public class LogController { @Autowired private LogService logService; Logger logger = LoggerFactory.getLogger(LogController.class); @RequestMapping("/query/{id}") public void query(@PathVariable("id") int id) { User user = logService.query(id); logger.debug("这个是debug测试来的数据"); logger.info("这个是info测试来的数据"); logger.warn("这个是warn测试来的数据"); logger.error("这个是error测试来的数据"); System.out.println(user.getName()); } @RequestMapping("/test") public void test() { int a = 2; int b= 0; logger.debug("这个是debug测试来的数据"); logger.info("这个是info测试来的数据"); logger.warn("这个是warn测试来的数据"); logger.error("这个是error测试来的数据"); System.out.println(a/b); } }
3 访问接口,查看控制台打印日志
3.1 正常情况:
测试地址:http://127.0.0.1:8084/log/query/1
控制台:
userLog = UserLog(id=null, requestId=req_1600680898027, methodName=query, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/query/1, remoteIp=127.0.0.1)
sqlLog = SqlLog(id=null, requestId=req_1600680898027, sql=select * from t_user where id = ?, methodName=query, methodClass=com.liyh.mapper.LogMapper, requestUrl=http://127.0.0.1:8084/log/query/1, remoteIp=127.0.0.1, loadTime=8)
3.2 异常情况:
测试地址:http://127.0.0.1:8084/log/test
控制台:
userLog = UserLog(id=null, requestId=req_1600681075412, methodName=test, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/test, remoteIp=127.0.0.1)
exceptionLog = ExceptionLog(id=null, requestId=req_1600681075412, methodName=test, methodClass=com.liyh.controller.LogController, requestUrl=http://127.0.0.1:8084/log/test, message=/ by zero, remoteIp=127.0.0.1)
3.3 测试结果:
通过测试拦截,获取到用户操作日志,sql语句,异常日志成功在控制台打印日志信息,我在拦截得配置是拦截得某一个包,也可以通过切点拦截某一个方法。但是通过aop拦截日志这种方法效率比较慢,注意使用场景!!!
另外,获取得日志信息,可以创建数据库,从而把日志保存到数据库。
请关注我的后续博客,实现把拦截得日志上传到阿里云日志服务!!!(阿里日志是收费的)
3.4 项目地址:
https://gitee.com/liyhGitee/springboot/tree/master/springboot_aop