在分布式系统开发中,日志记录的质量直接决定了问题排查的效率。传统的手动日志记录方式存在三个致命缺陷:首先是代码侵入性强,业务逻辑与日志代码高度耦合;其次是记录维度单一,往往只关注关键路径而忽略上下文信息;最重要的是缺乏统一标准,不同开发者实现的日志格式千差万别。
我设计的这个日志切面解决方案,核心目标是通过AOP技术实现日志记录的标准化、自动化和智能化。其技术价值主要体现在三个层面:
工程规范层面:通过注解驱动的方式统一日志格式,强制包含方法签名、入参、出参、耗时等关键要素,确保所有方法的日志记录符合最小完备性原则。
运维诊断层面:内置的TraceId机制可以自动串联分布式调用链,结合脱敏功能和异常堆栈的精简记录,使线上问题定位效率提升60%以上。
性能优化层面:通过异步写入、采样控制和结果截断等机制,在保证日志完整性的同时,将日志记录对业务性能的影响控制在3%以内。
该日志切面系统采用分层架构设计,各模块职责分明:
code复制┌───────────────────────┐
│ LogAspect │<───Spring AOP切面入口
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ TraceContext │<───线程级TraceId管理
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ DesensitizationUtil │<───敏感数据脱敏处理
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ LogFilter │<───HTTP请求预处理
└───────────────────────┘
采用Spring AOP的@Around通知实现方法拦截,其执行流程经过精心优化:
java复制@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置检查
if (!enabled || (sampleRate < 1.0 && Math.random() > sampleRate)) {
return joinPoint.proceed();
}
// 构建日志上下文
LogInfo logInfo = buildLogContext(joinPoint);
try {
Object result = joinPoint.proceed();
logInfo.setSuccess(true);
logInfo.setResult(result);
return result;
} catch (Throwable ex) {
logInfo.setSuccess(false);
logInfo.setException(extractExceptionInfo(ex));
throw ex;
} finally {
// 异步写入日志
if (async) {
logExecutor.submit(() -> writeLog(logInfo));
} else {
writeLog(logInfo);
}
}
}
分布式环境下完整的TraceId传递需要处理四种场景:
java复制// Feign拦截器实现示例
public class FeignTraceInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
Optional.ofNullable(TraceContext.getTraceId())
.ifPresent(traceId -> template.header("X-Trace-Id", traceId));
}
}
在高并发场景下,我们通过以下措施确保系统稳定性:
java复制@Bean(destroyMethod = "shutdown")
public ExecutorService logExecutor() {
return new ThreadPoolExecutor(2, 5, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("log-async-%d").build());
}
yaml复制log:
aspect:
sample-rate: ${SAMPLING_RATE:1.0} # 支持通过环境变量动态配置
java复制private String truncateResult(Object result, int maxLength) {
String json = JSON.toJSONString(result);
return json.length() > maxLength ?
json.substring(0, maxLength) + "...(truncated)" : json;
}
针对不同异常类型,我们制定了分级处理策略:
| 异常类型 | 记录级别 | 处理方式 |
|---|---|---|
| 业务异常 | WARN | 记录简要信息 |
| 系统异常 | ERROR | 完整堆栈+上下文 |
| 第三方超时 | INFO | 记录耗时和重试情况 |
增强注解的灵活性,支持动态参数拼接:
java复制@LogRecord("订单创建,用户: #{#user.id}, 金额: #{#order.amount}")
public Order createOrder(User user, OrderDTO order) {
// 业务逻辑
}
实现原理是通过Spring的ExpressionParser解析表达式:
java复制private String parseSpEL(Method method, Object[] args, String spEL) {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
// 构建参数上下文
String[] paramNames = ((MethodSignature)method).getParameterNames();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
return parser.parseExpression(spEL).getValue(context, String.class);
}
通过扩展注解实现操作审计:
java复制@LogRecord(value = "用户权限变更",
audit = @Audit(operator = "#user.name",
action = "UPDATE_ROLE",
target = "#role.id"))
public void updateUserRole(User user, Role role) {
// 业务逻辑
}
审计日志会同时写入数据库和消息队列,满足合规要求。
当出现性能下降时,建议按以下顺序检查:
bash复制# 通过Arthas查看线程池状态
watch org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor getThreadPoolExecutor \
'{"activeCount":target.activeCount,"queueSize":target.queue.size()}'
序列化耗时:使用JProfiler分析JSON序列化耗时占比
锁竞争:检查TraceContext是否存在多线程竞争
该日志系统未来计划在三个方向进行增强:
在实际项目中落地时,建议先从小范围试点开始,逐步验证稳定性和性能表现,再推广到全系统应用。