功能概述
切面定义:
- 使用 @Aspect 注解标记该类为一个切面。
- 使用 @Component 注解将该类注册为一个 Spring Bean。
- 使用 @Order(0) 注解指定该切面的执行顺序,值越小优先级越高。
切点定义:
- 使用 @Pointcut 注解定义一个切点 controllerLog(),匹配 com.szx.exam.controller 包及其子包下的所有控制器方法。
前置日志记录:
- logBefore 方法负责在控制器方法执行前记录请求的相关信息。
- 获取当前请求的 HttpServletRequest 对象。
- 记录请求的 URL、HTTP 方法、客户端 IP、客户端名称、控制器类名、方法名以及方法参数。
- 使用 lombok 注解引入日志记录器 log,并在 finally 块中输出日志信息。
环绕通知:
- 使用 @Around 注解定义一个环绕通知 around,在控制器方法执行前后进行操作。
- 记录方法开始执行的时间。
- 调用 logBefore 方法记录前置日志。
- 执行控制器方法,并记录返回结果。
- 记录方法执行时间。
- 清除 TraceId。
引入依赖
1 2 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 类 ,编写如下代码
1 2 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
1 2 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
1 2 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("-", ""); }
}
|
编写完成后,重启项目,当我们访问一个接口时,就会有对应的日志输出了
测试
编写一个测试接口
1 2 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切片即可自动的实现日志记录功能。