功能概述
- 切面定义: - 
- 使用 @Aspect 注解标记该类为一个切面。
- 使用 @Component 注解将该类注册为一个 Spring Bean。
- 使用 @Order(0) 注解指定该切面的执行顺序,值越小优先级越高。
 
- 切点定义: - 
- 使用 @Pointcut 注解定义一个切点 controllerLog(),匹配 com.szx.exam.controller 包及其子包下的所有控制器方法。
 前置日志记录:
- logBefore 方法负责在控制器方法执行前记录请求的相关信息。
- 获取当前请求的 HttpServletRequest 对象。
- 记录请求的 URL、HTTP 方法、客户端 IP、客户端名称、控制器类名、方法名以及方法参数。
- 使用 lombok 注解引入日志记录器 log,并在 finally 块中输出日志信息。
 
- 环绕通知: - 
- 使用 @Around 注解定义一个环绕通知 around,在控制器方法执行前后进行操作。
- 记录方法开始执行的时间。
- 调用 logBefore 方法记录前置日志。
- 执行控制器方法,并记录返回结果。
- 记录方法执行时间。
- 清除 TraceId。
 
引入依赖
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
 
 
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>2.0.0</version>
 </dependency>
 
 | 
因为接口中有json输出,所以需要额外引入一个fastjson
代码编写
新建 ControllerLogAspect 类 ,编写如下代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 
 | import com.alibaba.fastjson.JSON;import com.szx.exam.util.RequestKit;
 import com.szx.exam.util.TraceIdKit;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Pointcut;
 import org.springframework.core.annotation.Order;
 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.util.LinkedHashMap;
 import java.util.Map;
 
 @Aspect
 @Order(0)
 @Component
 @Slf4j
 public class ControllerLogAspect {
 
 
 @Pointcut("within(com.szx.exam.controller..*)")
 public void controllerLog() {
 }
 
 
 private void logBefore(ProceedingJoinPoint pjd) {
 Map<String, String> logMap = new LinkedHashMap<>();
 
 try {
 Object[] args = pjd.getArgs();
 
 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
 if (requestAttributes == null) {
 throw new Exception("requestAttributes is null");
 }
 
 HttpServletRequest httpServletRequest = requestAttributes.getRequest();
 TraceIdKit.getTraceId(httpServletRequest);
 
 logMap.put("URL", httpServletRequest.getRequestURI());
 logMap.put("HTTP_METHOD", httpServletRequest.getMethod());
 logMap.put("IP", RequestKit.getIp(httpServletRequest));
 logMap.put("ClientName",RequestKit.getClientName(httpServletRequest));
 
 String className = pjd.getTarget().getClass().getName();
 logMap.put("CLASS", className);
 
 String methodName = pjd.getSignature().getName();
 logMap.put("METHOD", methodName);
 
 StringBuilder sb = new StringBuilder();
 for (Object object : args) {
 try {
 if (sb.length() > 0) {
 sb.append(", ");
 }
 sb.append(JSON.toJSONString(object));
 } catch (Exception ignored) {
 sb.append("null, ");
 }
 }
 
 logMap.put("ARGS", sb.toString());
 } catch (Exception e) {
 log.error("logBefore", e);
 } finally {
 for (Map.Entry<String, String> entry : logMap.entrySet()) {
 log.info("{}: {}", entry.getKey(), entry.getValue());
 }
 }
 }
 
 @Around("controllerLog()")
 public Object around(ProceedingJoinPoint pjd) throws Throwable {
 long start = System.currentTimeMillis();
 
 logBefore(pjd);
 
 try {
 Object result = pjd.proceed();
 log.info("RESP: {}", JSON.toJSONString(result));
 
 return result;
 } finally {
 log.info("COST: {} ms", System.currentTimeMillis() - start);
 TraceIdKit.clearTraceId();
 }
 }
 
 }
 
 | 
额外使用了两个工具类
RequestKit
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 
 | import org.apache.commons.lang3.StringUtils;
 import javax.servlet.http.HttpServletRequest;
 import java.net.URLEncoder;
 
 
 
 
 
 @SuppressWarnings("unused")
 public class RequestKit {
 
 
 
 
 
 
 public static String getIp(HttpServletRequest request) {
 try {
 String 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 != null && ipAddress.length() > 15) {
 if (ipAddress.indexOf(",") > 0) {
 ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
 }
 }
 return ipAddress;
 } catch (Exception e) {
 throw new IllegalArgumentException("get ip exception: " + e.getMessage());
 }
 }
 
 
 
 
 
 
 
 public static String getClientName(HttpServletRequest request) {
 String clientName = "--";
 String userAgent = request.getHeader("User-Agent");
 if (StringUtils.isEmpty(userAgent)) {
 return clientName;
 }
 if (userAgent.toLowerCase().contains("windows")) {
 clientName = "windows";
 }
 if (userAgent.toLowerCase().contains("mac")) {
 clientName = "mac";
 }
 if (userAgent.toLowerCase().contains("x11")) {
 clientName = "unix";
 }
 if (userAgent.toLowerCase().contains("android")) {
 clientName = "android";
 }
 if (userAgent.toLowerCase().contains("iphone")) {
 clientName = "iphone";
 }
 return clientName;
 }
 
 public static String encode(String value) {
 try {
 return URLEncoder.encode(value, "UTF-8");
 } catch (Exception e) {
 return value;
 }
 }
 }
 
 | 
TraceIdKit
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | import org.apache.commons.lang3.StringUtils;import org.slf4j.MDC;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.UUID;
 
 @SuppressWarnings("unused")
 public class TraceIdKit {
 
 private static final String TRACE_ID = "traceId";
 
 
 public static String getTraceId() {
 String traceId = MDC.get(TRACE_ID);
 if (StringUtils.isBlank(traceId)) {
 traceId = generateTraceId();
 MDC.put(TRACE_ID, traceId);
 }
 
 return traceId;
 }
 
 public static String getTraceIdHeader() {
 return TRACE_ID;
 }
 
 public static void getTraceId(HttpServletRequest httpServletRequest) {
 String traceId = httpServletRequest.getHeader(TRACE_ID);
 if (StringUtils.isBlank(traceId)) {
 traceId = getTraceId();
 }
 
 MDC.put(TRACE_ID, traceId);
 }
 
 public static void clearTraceId() {
 MDC.clear();
 }
 
 private static String generateTraceId() {
 UUID uuid = UUID.randomUUID();
 return uuid.toString().replace("-", "");
 }
 
 }
 
 | 
编写完成后,重启项目,当我们访问一个接口时,就会有对应的日志输出了
测试
编写一个测试接口
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | @RestController@RequestMapping("/exam")
 public class ExamController {
 
 @GetMapping("getAllList")
 public Result getAllList(String name) {
 return Result.ok("success");
 }
 
 
 @PostMapping("add")
 public Result add(@RequestBody HashMap<String,Object> map) {
 return Result.ok(map);
 }
 }
 
 | 
测试访问,查看日志输出结果


结合  logback,查看记录的日志文件

我们后面的开发中,就不用为每个接口都去处理日志信息,通过aop切片即可自动的实现日志记录功能。