基于AOP实现智能日志打印:告别繁琐的手动日志记录
传统日志记录的痛点
在我们的日常开发工作中,经常会遇到这样的场景:
- 每个方法都要手动添加日志记录,代码冗余严重
- 方法入参、出参、执行时间都需要手动记录
- 日志格式不统一,排查问题困难
- 敏感信息(如密码、身份证号)不小心被记录到日志中
- 临时调试日志忘记删除,污染日志文件
传统的手动日志记录方式不仅效率低下,还容易出错。今天我们就来聊聊如何用AOP实现智能日志打印。
AOP智能日志的优势
相比传统的手动日志记录,AOP方式有以下优势:
- 统一管理:日志逻辑集中处理,便于维护
- 无侵入性:业务代码不受日志代码干扰
- 灵活配置:可通过注解灵活控制日志行为
- 安全性:自动脱敏敏感信息,避免信息泄露
- 性能优化:按需记录,减少不必要的日志输出
解决方案思路
今天我们要解决的,就是如何用AOP实现智能日志打印。
核心思路是:
- 自定义注解:标记需要记录日志的方法
- 切面编程:拦截目标方法,统一处理日志逻辑
- 智能脱敏:自动识别和脱敏敏感字段
- 性能监控:记录方法执行时间和性能指标
自定义日志注解
1. 日志注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SmartLog {
/**
* 日志描述
*/
String value() default "";
/**
* 是否记录入参
*/
boolean logArgs() default true;
/**
* 是否记录返回值
*/
boolean logReturn() default true;
/**
* 是否记录执行时间
*/
boolean logTime() default true;
/**
* 是否脱敏敏感信息
*/
boolean autoMask() default true;
/**
* 需要脱敏的字段
*/
String[] maskFields() default {};
/**
* 日志级别
*/
LogLevel level() default LogLevel.INFO;
enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
}
AOP切面实现
1. 日志切面类
@Aspect
@Component
@Slf4j
public class SmartLogAspect {
private static final Logger logger = LoggerFactory.getLogger(SmartLogAspect.class);
@Around("@annotation(smartLog)")
public Object around(ProceedingJoinPoint joinPoint, SmartLog smartLog) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
// 记录方法开始
logMethodStart(joinPoint, smartLog, methodName, className);
long startTime = System.currentTimeMillis();
Object result = null;
Throwable throwable = null;
try {
result = joinPoint.proceed();
return result;
} catch (Throwable t) {
throwable = t;
throw t;
} finally {
long executionTime = System.currentTimeMillis() - startTime;
// 记录方法结束
logMethodEnd(joinPoint, smartLog, methodName, className, result,
throwable, executionTime);
}
}
private void logMethodStart(ProceedingJoinPoint joinPoint, SmartLog smartLog,
String methodName, String className) {
if (smartLog.logArgs()) {
Object[] args = joinPoint.getArgs();
String maskedArgs = maskSensitiveData(args, smartLog);
String logMessage = String.format("方法开始执行 - %s.%s(), 参数: %s",
className, methodName, maskedArgs);
logWithLevel(logger, smartLog.level(), logMessage);
} else {
String logMessage = String.format("方法开始执行 - %s.%s()", className, methodName);
logWithLevel(logger, smartLog.level(), logMessage);
}
}
private void logMethodEnd(ProceedingJoinPoint joinPoint, SmartLog smartLog,
String methodName, String className, Object result,
Throwable throwable, long executionTime) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("方法执行结束 - %s.%s()", className, methodName));
if (smartLog.logTime()) {
sb.append(String.format(", 执行时间: %dms", executionTime));
}
if (smartLog.logReturn() && result != null) {
String maskedResult = smartLog.autoMask() ?
maskSensitiveData(result, smartLog) :
String.valueOf(result);
sb.append(String.format(", 返回值: %s", maskedResult));
}
if (throwable != null) {
sb.append(String.format(", 异常: %s", throwable.getMessage()));
logWithLevel(logger, LogLevel.ERROR, sb.toString(), throwable);
} else {
logWithLevel(logger, smartLog.level(), sb.toString());
}
}
}
2. 敏感数据脱敏工具
@Component
public class SensitiveDataMasker {
private static final Set<String> SENSITIVE_FIELDS = new HashSet<>();
private static final Set<String> SENSITIVE_KEYWORDS = new HashSet<>();
static {
// 常见敏感字段
SENSITIVE_FIELDS.addAll(Arrays.asList(
"password", "pwd", "pass", "secret", "token", "key",
"phone", "mobile", "tel", "email", "id_card", "card_number",
"account", "bank_account", "credit_card", "ssn"
));
// 敏感关键词
SENSITIVE_KEYWORDS.addAll(Arrays.asList(
"password", "token", "secret", "key", "pwd", "pass"
));
}
public String maskSensitiveData(Object obj, SmartLog smartLog) {
if (obj == null) {
return "null";
}
try {
// 如果指定了特定脱敏字段,优先使用
if (smartLog.maskFields().length > 0) {
return maskSpecificFields(obj, smartLog.maskFields());
}
// 自动脱敏
return autoMaskObject(obj);
} catch (Exception e) {
log.warn("脱敏处理异常", e);
return obj.toString();
}
}
private String maskSpecificFields(Object obj, String[] maskFields) {
if (obj instanceof String) {
return maskString((String) obj);
}
// 对于复杂对象,使用JSON序列化后脱敏
String jsonStr = JSON.toJSONString(obj);
for (String field : maskFields) {
jsonStr = jsonStr.replaceAll(
"(\"" + field + "\":\")[^\"]*?(\")",
"$1***$2"
);
}
return jsonStr;
}
private String autoMaskObject(Object obj) {
if (obj instanceof String) {
return maskString((String) obj);
}
// 使用Jackson进行深度脱敏
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.valueToTree(obj);
maskJsonNode(node);
try {
return mapper.writeValueAsString(node);
} catch (JsonProcessingException e) {
log.warn("JSON序列化异常", e);
return obj.toString();
}
}
private void maskJsonNode(JsonNode node) {
if (node.isObject()) {
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
String fieldName = field.getKey().toLowerCase();
if (isSensitiveField(fieldName)) {
((ObjectNode) node).put(field.getKey(), "***");
} else {
maskJsonNode(field.getValue());
}
}
} else if (node.isArray()) {
for (JsonNode element : node) {
maskJsonNode(element);
}
}
}
private boolean isSensitiveField(String fieldName) {
return SENSITIVE_FIELDS.contains(fieldName) ||
SENSITIVE_KEYWORDS.stream().anyMatch(fieldName::contains);
}
private String maskString(String str) {
if (str == null || str.length() <= 4) {
return "***";
}
// 手机号脱敏:138****8888
if (str.matches("\\d{11}")) {
return str.substring(0, 3) + "****" + str.substring(7);
}
// 邮箱脱敏:zhang***@example.com
if (str.contains("@")) {
int atIndex = str.indexOf("@");
String prefix = str.substring(0, Math.min(3, atIndex));
return prefix + "***" + str.substring(atIndex);
}
// 身份证脱敏:110***1990********12
if (str.matches("\\d{18}|\\d{17}[Xx]")) {
return str.substring(0, 3) + "********" + str.substring(14);
}
// 一般字符串脱敏:保留前2位和后2位
if (str.length() > 4) {
return str.substring(0, 2) + "***" + str.substring(str.length() - 2);
}
return "***";
}
}
日志级别控制
1. 日志级别管理
@Component
public class LogLevelManager {
private final Map<String, SmartLog.LogLevel> methodLogLevels = new ConcurrentHashMap<>();
public void setLogLevel(String methodSignature, SmartLog.LogLevel level) {
methodLogLevels.put(methodSignature, level);
}
public SmartLog.LogLevel getLogLevel(String methodSignature) {
return methodLogLevels.getOrDefault(methodSignature, SmartLog.LogLevel.INFO);
}
private void logWithLevel(Logger logger, SmartLog.LogLevel level, String message) {
switch (level) {
case DEBUG:
logger.debug(message);
break;
case INFO:
logger.info(message);
break;
case WARN:
logger.warn(message);
break;
case ERROR:
logger.error(message);
break;
}
}
private void logWithLevel(Logger logger, SmartLog.LogLevel level,
String message, Throwable throwable) {
switch (level) {
case DEBUG:
logger.debug(message, throwable);
break;
case INFO:
logger.info(message, throwable);
break;
case WARN:
logger.warn(message, throwable);
break;
case ERROR:
logger.error(message, throwable);
break;
}
}
}
实际应用示例
1. 业务方法使用
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
@SmartLog(value = "用户登录", logArgs = true, logReturn = true, autoMask = true)
public LoginResult login(String username, String password) {
// 登录逻辑
User user = userRepository.findByUsername(username);
if (user == null) {
return LoginResult.failure("用户不存在");
}
if (!BCrypt.checkpw(password, user.getPassword())) {
return LoginResult.failure("密码错误");
}
return LoginResult.success(generateToken(user));
}
@SmartLog(value = "创建用户", maskFields = {"password", "idCard"})
public User createUser(CreateUserRequest request) {
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(BCrypt.hashpw(request.getPassword(), BCrypt.gensalt()));
user.setIdCard(request.getIdCard());
user.setEmail(request.getEmail());
return userRepository.save(user);
}
@SmartLog(value = "获取用户信息", logTime = true)
public UserInfo getUserInfo(Long userId) {
User user = userRepository.findById(userId);
return convertToUserInfo(user);
}
}
2. 控制器使用
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
@SmartLog(value = "用户登录接口", level = SmartLog.LogLevel.INFO)
public Result<LoginResult> login(@RequestBody LoginRequest request) {
LoginResult result = userService.login(request.getUsername(), request.getPassword());
return Result.success(result);
}
@PostMapping("/register")
@SmartLog(value = "用户注册接口", logArgs = false, logReturn = false)
public Result<User> register(@RequestBody CreateUserRequest request) {
User user = userService.createUser(request);
return Result.success(user);
}
}
性能优化
1. 日志开关控制
@Component
public class LogSwitchManager {
@Value("${app.log.enabled:true}")
private boolean logEnabled;
@Value("${app.log.performance-threshold:1000}") // 1秒
private long performanceThreshold;
public boolean isLogEnabled() {
return logEnabled;
}
public boolean shouldLogPerformance(long executionTime) {
return executionTime > performanceThreshold;
}
}
2. 异步日志处理
@Component
public class AsyncLogProcessor {
private final ExecutorService logExecutor =
Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "async-log-thread");
t.setDaemon(true);
return t;
});
public void asyncLog(String message, SmartLog.LogLevel level) {
if (LogSwitchManager.isLogEnabled()) {
logExecutor.submit(() -> {
// 异步处理日志
doLog(message, level);
});
}
}
private void doLog(String message, SmartLog.LogLevel level) {
// 实际的日志记录逻辑
switch (level) {
case DEBUG:
log.debug(message);
break;
case INFO:
log.info(message);
break;
case WARN:
log.warn(message);
break;
case ERROR:
log.error(message);
break;
}
}
}
配置管理
1. 配置文件
app:
log:
enabled: true
performance-threshold: 1000
sensitive-fields:
- password
- phone
- id-card
- email
mask-patterns:
phone: "(\d{3})\d{4}(\d{4})"
id-card: "(\d{6})\d{8}(\w{4})"
注意事项
在使用AOP智能日志时,需要注意以下几点:
- 性能影响:日志记录本身也会消耗性能,要注意平衡
- 循环引用:避免在日志记录中引入循环依赖
- 异常处理:日志处理异常不应影响业务逻辑
- 配置管理:不同环境应有不同的日志配置
- 安全考虑:确保敏感信息真正被脱敏
总结
通过AOP实现的智能日志打印,不仅能大幅减少手动日志记录的工作量,还能提供统一、安全、高效的日志记录能力。这种方案特别适合在大型项目中统一日志规范,提升开发效率。
希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。
原文首发于 www.jiangyi.space
转载请注明出处
标题:基于AOP实现智能日志打印:告别繁琐的手动日志记录
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/21/1768975140971.html
0 评论