AOP记录日志怎么写?

wen python案例 4

本文目录导读:

AOP记录日志怎么写?

  1. 基础写法:记录方法入参和返回值
  2. 带自定义注解的写法(推荐)
  3. 异步记录日志(推荐)
  4. 完整的最佳实践示例
  5. 注意事项

AOP(面向切面编程)记录日志最常用的方式就是利用环绕通知,下面是几种常见场景的写法,从简单到复杂。

基础写法:记录方法入参和返回值

@Aspect
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    // 定义切面:所有Controller类下的所有方法
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerLog() {}
    @Around("controllerLog()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 方法开始时间
        long startTime = System.currentTimeMillis();
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
        // 获取参数
        Object[] args = joinPoint.getArgs();
        log.info("【请求】方法: {},参数: {}", methodName, args);
        Object result = null;
        try {
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            log.info("【响应】方法: {},耗时: {}ms,结果: {}", methodName, endTime - startTime, result);
        } catch (Exception e) {
            log.error("【异常】方法: {},耗时: {}ms,异常: {}", methodName, System.currentTimeMillis() - startTime, e.getMessage());
            throw e;
        }
        return result;
    }
}

带自定义注解的写法(推荐)

1 创建日志注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {
    String value() default "";        // 操作描述
    String type() default "";         // 操作类型
}

2 定义切面

@Aspect
@Component
public class OperLogAspect {
    private static final Logger log = LoggerFactory.getLogger(OperLogAspect.class);
    // 拦截所有带 @OperLog 注解的方法
    @Pointcut("@annotation(com.example.annotation.OperLog)")
    public void operLogPointcut() {}
    @Around("operLogPointcut()")
    public Object aroundOperLog(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 获取注解信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OperLog operLog = method.getAnnotation(OperLog.class);
        // 构建日志信息
        String operDescription = operLog.value();
        String operType = operLog.type();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        // 获取参数
        Object[] args = joinPoint.getArgs();
        String params = JSON.toJSONString(args);
        // 获取请求信息(Web环境)
        HttpServletRequest request = getRequest();
        String ip = getIpAddress(request);
        String url = request.getRequestURI();
        log.info("操作日志 - 描述: {}, 类型: {}, 类名: {}, 方法名: {}, IP: {}, URL: {}, 参数: {}", 
                 operDescription, operType, className, methodName, ip, url, params);
        try {
            Object result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            log.info("操作日志 - 耗时: {}ms, 结果: {}", endTime - startTime, JSON.toJSONString(result));
            return result;
        } catch (Exception e) {
            log.error("操作日志 - 异常: {}", e.getMessage());
            throw e;
        }
    }
    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes != null ? attributes.getRequest() : null;
    }
    private String getIpAddress(HttpServletRequest request) {
        if (request == null) return "unknown";
        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.getRemoteAddr();
        }
        return ip;
    }
}

3 使用注解

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/add")
    @OperLog(value = "添加用户", type = "INSERT")
    public Result addUser(@RequestBody User user) {
        // 业务逻辑
        return Result.success();
    }
    @PutMapping("/update")
    @OperLog(value = "修改用户", type = "UPDATE")
    public Result updateUser(@RequestBody User user) {
        // 业务逻辑
        return Result.success();
    }
    @DeleteMapping("/delete/{id}")
    @OperLog(value = "删除用户", type = "DELETE")
    public Result deleteUser(@PathVariable Long id) {
        // 业务逻辑
        return Result.success();
    }
}

异步记录日志(推荐)

@Aspect
@Component
public class AsyncLogAspect {
    @Around("@annotation(com.example.annotation.OperLog)")
    @Async  // 异步执行
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // ... 日志记录逻辑
        // 注意:异步环境下 RequestContextHolder 可能获取不到,需要手动传递
        return joinPoint.proceed();
    }
}

完整的最佳实践示例

@Aspect
@Component
@Order(1)
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
    // 定义切面
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void logPointcut() {}
    @Around("logPointcut()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 性能监控
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 构建日志信息
        LogInfo logInfo = buildLogInfo(joinPoint);
        try {
            Object result = joinPoint.proceed();
            stopWatch.stop();
            logInfo.setResult(JSON.toJSONString(result));
            logInfo.setCostTime(stopWatch.getTotalTimeMillis());
            logInfo.setSuccess(true);
            // 记录正常日志
            log.info("接口日志: {}", logInfo);
            return result;
        } catch (Exception e) {
            stopWatch.stop();
            logInfo.setCostTime(stopWatch.getTotalTimeMillis());
            logInfo.setSuccess(false);
            logInfo.setErrorMsg(e.getMessage());
            // 记录异常日志
            log.error("接口异常: {}", logInfo, e);
            throw e;
        }
    }
    private LogInfo buildLogInfo(ProceedingJoinPoint joinPoint) {
        LogInfo logInfo = new LogInfo();
        // 获取方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取注解
        OperLog operLog = method.getAnnotation(OperLog.class);
        if (operLog != null) {
            logInfo.setDescription(operLog.value());
            logInfo.setType(operLog.type());
        }
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            logInfo.setIp(getIpAddress(request));
            logInfo.setUrl(request.getRequestURI());
            logInfo.setMethod(request.getMethod());
        }
        // 获取类和方法名
        logInfo.setClassName(signature.getDeclaringTypeName());
        logInfo.setMethodName(signature.getName());
        // 获取参数
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            // 过滤掉 HttpServletRequest、HttpServletResponse 等
            List<Object> filteredArgs = Arrays.stream(args)
                .filter(arg -> !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse))
                .collect(Collectors.toList());
            logInfo.setParams(JSON.toJSONString(filteredArgs));
        }
        return logInfo;
    }
    @Data
    public static class LogInfo {
        private String description;
        private String type;
        private String className;
        private String methodName;
        private String ip;
        private String url;
        private String httpMethod;
        private String params;
        private String result;
        private long costTime;
        private boolean success;
        private String errorMsg;
    }
}

注意事项

  1. 性能考虑:生产环境建议异步处理,避免日志记录影响业务性能
  2. 敏感信息:对密码等敏感信息进行脱敏处理
  3. 参数过滤:排除 HttpServletRequest、HttpServletResponse 等大对象
  4. 异常处理:确保日志记录不影响业务正常执行
  5. 循环依赖:注意切面类不要被自己的切面拦截

这样,你就有了一个完整、可扩展的AOP日志记录方案。

抱歉,评论功能暂时关闭!