特定日志输出aop实现
使用:
@UserSpecificLog(value = "首页多数据推荐接口", logArgs = true, logResult = true, logExecutionTime = true)
具体代码
package com.yuruo.reco.aspect;import java.lang.reflect.Method;import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import com.yuruo.reco.annotation.UserSpecificLog;
import com.yuruo.reco.config.UserSpecificLogConfig;
import com.yuruo.reco.dto.RequestContext;
import com.yuruo.reco.utils.JsonUtils;
import com.yuruo.reco.utils.RequestContextHelper;import lombok.extern.slf4j.Slf4j;/*** 用户特定日志切面* 根据配置的用户ID,为特定用户记录详细的接口调用日志** @author system* @date 2025/01/18*/
@Slf4j
@Aspect
@Component
public class UserSpecificLogAspect {@Autowiredprivate UserSpecificLogConfig logConfig;// 构造函数,用于确认切面是否被加载public UserSpecificLogAspect() {log.info("[USER_SPECIFIC_LOG] UserSpecificLogAspect 切面已创建并加载");}/*** 环绕通知:拦截带有@UserSpecificLog注解的方法*/@Around("@annotation(userSpecificLog)")public Object around(ProceedingJoinPoint joinPoint, UserSpecificLog userSpecificLog) throws Throwable {// 获取方法签名和注解MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();UserSpecificLog annotation = userSpecificLog;// 获取当前请求上下文RequestContext context = RequestContextHelper.getCurrentRequestContext();String userId = context.getEffectiveUserId();// 检查是否需要记录日志if (!shouldLog(userId)) {/*** 不是目标用户,直接执行方法*/return joinPoint.proceed();}/*** 构建日志信息*/String className = joinPoint.getTarget().getClass().getSimpleName();String methodName = method.getName();String logDescription = StringUtils.isNotBlank(annotation.value()) ? annotation.value(): (className + "." + methodName);long startTime = System.currentTimeMillis();Object result = null;Throwable exception = null;try {/*** 记录方法开始日志*/logMethodStart(userId, logDescription, joinPoint, annotation);/*** 执行目标方法*/result = joinPoint.proceed();return result;} catch (Throwable e) {exception = e;throw e;} finally {/*** 记录方法结束日志*/long executionTime = System.currentTimeMillis() - startTime;logMethodEnd(userId, logDescription, result, exception, executionTime, annotation);}}/*** 判断是否需要记录日志*/private boolean shouldLog(String userId) {return logConfig.isTargetUser(userId);}/*** 记录方法开始日志*/private void logMethodStart(String userId, String description,ProceedingJoinPoint joinPoint, UserSpecificLog annotation) {try {StringBuilder logMsg = new StringBuilder();logMsg.append("[USER_SPECIFIC_LOG] 方法开始执行 - ").append("用户ID: ").append(userId).append(", 方法: ").append(description);/*** 记录方法参数*/if (annotation.logArgs()) {Object[] args = joinPoint.getArgs();if (args != null && args.length > 0) {logMsg.append(", 参数: ").append(JsonUtils.toJSONString(args));}}log.info(logMsg.toString());} catch (Exception e) {log.warn("[USER_SPECIFIC_LOG] 记录方法开始日志失败", e);}}/*** 记录方法结束日志*/private void logMethodEnd(String userId, String description, Object result,Throwable exception, long executionTime, UserSpecificLog annotation) {try {StringBuilder logMsg = new StringBuilder();logMsg.append("[USER_SPECIFIC_LOG] 方法执行完成 - ").append("用户ID: ").append(userId).append(", 方法: ").append(description);/*** 记录执行时间*/if (annotation.logExecutionTime()) {logMsg.append(", 耗时: ").append(executionTime).append("ms");}// 记录异常信息if (exception != null) {logMsg.append(", 异常: ").append(exception.getClass().getSimpleName()).append(" - ").append(exception.getMessage());log.error(logMsg.toString(), exception);return;}// 记录返回值if (annotation.logResult() && result != null) {String resultStr = JsonUtils.toJSONString(result);logMsg.append(", 返回值: ").append(resultStr);}log.info(logMsg.toString());} catch (Exception e) {log.warn("[USER_SPECIFIC_LOG] 记录方法结束日志失败", e);}}
}
package com.yuruo.reco.config;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;/*** AOP配置类* 启用AspectJ自动代理** @author dy* @date 2025/01/18*/
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
package com.yuruo.reco.config;import java.util.HashSet;
import java.util.Set;import javax.annotation.PostConstruct;
import javax.annotation.Resource;import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import com.yuruo.reco.constant.RedisConstants;
import com.yuruo.reco.utils.CommonRedisUtil;
import com.yuruo.reco.utils.JsonUtils;import lombok.Data;
import lombok.extern.slf4j.Slf4j;/*** 用户特定日志配置* 从Redis读取配置,如果Redis中没有配置则使用默认值** @author dy* @date 2025/01/18*/
@Slf4j
@Data
@Component
public class UserSpecificLogConfig {@Resourceprivate CommonRedisUtil redisUtil;/*** 是否启用用户特定日志*/private boolean enabled = false;/*** 目标用户ID集合* 支持多个用户ID,用逗号分隔*/private Set<String> targetUserIds = new HashSet<>();/*** 启动时从Redis加载配置*/@PostConstructpublic void loadConfigFromRedis() {try {/** 读取启用状态 */String enabledStr = redisUtil.getStr(RedisConstants.AOP_LOG_ENABLED);if (enabledStr != null) {this.enabled = Boolean.parseBoolean(enabledStr);log.info("从Redis加载用户特定日志启用状态: {}", this.enabled);} else {log.info("Redis中未找到用户特定日志启用配置,使用默认值: {}", this.enabled);}/** 读取目标用户ID列表 */String targetUsersStr = redisUtil.getStr(RedisConstants.AOP_LOG_TARGET_USERS);if (targetUsersStr != null && StringUtils.hasText(targetUsersStr)) {this.targetUserIds = new HashSet<>(JsonUtils.stringToJavaObjectList(targetUsersStr, String.class));log.info("从Redis加载目标用户ID列表: {}", this.targetUserIds);} else {log.info("Redis中未找到目标用户ID配置,使用默认值: {}", this.targetUserIds);}} catch (Exception e) {log.error("从Redis加载用户特定日志配置失败,使用默认值", e);}}/*** 检查是否为目标用户** @param userId 用户ID* @return true if target user, false otherwise*/public boolean isTargetUser(String userId) {if (!enabled || userId == null || targetUserIds.isEmpty()) {return false;}return targetUserIds.contains(userId);}}