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