基于Spring AOP实现美团API调用日志的自动埋点与性能分析
在对接美团开放平台的外卖、团购等API时,为保障系统稳定性与问题可追溯性,需对每次外部调用记录完整上下文:包括请求参数、响应结果、耗时、异常堆栈等。若在每个调用处手动打日志,将导致代码侵入性强、维护成本高。本文基于baodanbao.com.cn.*包结构,利用Spring AOP实现无侵入式自动埋点,并集成性能分析能力。
1. 定义美团API调用标识注解
首先创建自定义注解,用于标记需要监控的方法:
package baodanbao.com.cn.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MeituanApiCall {
String value() default ""; // API名称,如 "order.create"
}

2. 美团API服务接口示例
业务层方法使用该注解:
package baodanbao.com.cn.service.meituan;
import baodanbao.com.cn.annotation.MeituanApiCall;
import baodanbao.com.cn.model.meituan.CreateOrderRequest;
import baodanbao.com.cn.model.meituan.CreateOrderResponse;
public interface MeituanOrderService {
@MeituanApiCall("order.create")
CreateOrderResponse createOrder(CreateOrderRequest request);
@MeituanApiCall("order.query")
String queryOrder(String orderId);
}
3. AOP切面实现自动埋点
核心逻辑:环绕通知捕获方法执行前后的状态:
package baodanbao.com.cn.aspect;
import baodanbao.com.cn.annotation.MeituanApiCall;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MeituanApiLogAspect {
private static final Logger log = LoggerFactory.getLogger("MEITUAN_API_LOG");
private final ObjectMapper objectMapper;
public MeituanApiLogAspect(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Around("@annotation(meituanApiCall)")
public Object logMeituanApiCall(ProceedingJoinPoint joinPoint, MeituanApiCall meituanApiCall) throws Throwable {
long startTime = System.currentTimeMillis();
String apiName = meituanApiCall.value();
Object[] args = joinPoint.getArgs();
// 序列化请求参数(避免敏感字段)
String requestJson = serializeArgs(args);
Object result = null;
Exception exception = null;
try {
result = joinPoint.proceed();
return result;
} catch (Exception e) {
exception = e;
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;
String responseJson = serializeResult(result);
String errorMsg = exception != null ? exception.getMessage() : null;
// 构建结构化日志
StringBuilder logMsg = new StringBuilder();
logMsg.append("api=").append(apiName)
.append(", duration=").append(duration).append("ms")
.append(", request=").append(requestJson)
.append(", response=").append(responseJson);
if (errorMsg != null) {
logMsg.append(", error=").append(errorMsg);
}
if (exception != null) {
log.error(logMsg.toString(), exception);
} else if (duration > 2000) {
// 慢调用告警(>2秒)
log.warn(logMsg.toString());
} else {
log.info(logMsg.toString());
}
}
}
private String serializeArgs(Object[] args) {
if (args == null || args.length == 0) return "{}";
try {
return objectMapper.writeValueAsString(args.length == 1 ? args[0] : args);
} catch (JsonProcessingException e) {
return "[SERIALIZE_ERROR]";
}
}
private String serializeResult(Object result) {
if (result == null) return "null";
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
return "[SERIALIZE_ERROR]";
}
}
}
4. 敏感字段脱敏处理(增强版)
为避免日志泄露用户手机号、地址等信息,可扩展ObjectMapper:
package baodanbao.com.cn.config;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper loggingObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 注册脱敏模块
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new MaskingSerializer());
mapper.registerModule(module);
return mapper;
}
public static class MaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
// 简单规则:手机号、身份证脱敏
if (value.matches("^1[3-9]\\d{9}$")) {
gen.writeString(value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
} else if (value.matches("^\\d{17}[\\dXx]$")) {
gen.writeString(value.replaceAll("(\\d{6})\\d{8}(\\d{3}[\\dXx])", "$1********$2"));
} else {
gen.writeString(value);
}
}
}
}
并在AOP中注入此专用ObjectMapper:
private final ObjectMapper loggingObjectMapper;
public MeituanApiLogAspect(@Qualifier("loggingObjectMapper") ObjectMapper loggingObjectMapper) {
this.loggingObjectMapper = loggingObjectMapper;
}
5. 性能分析:慢调用统计与告警
除日志外,可结合Micrometer上报指标:
// 在finally块中增加
if (exception == null) {
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(Timer.builder("meituan.api.latency")
.tag("api", apiName)
.register(meterRegistry));
}
配合Prometheus可绘制P95延迟曲线,设置Grafana告警。
6. 使用示例
业务代码完全无日志侵入:
@Service
public class MeituanOrderServiceImpl implements MeituanOrderService {
@Override
public CreateOrderResponse createOrder(CreateOrderRequest request) {
// 调用美团HTTP API
return restTemplate.postForObject("/v1/order/create", request, CreateOrderResponse.class);
}
}
调用后自动输出结构化日志:
INFO MEITUAN_API_LOG - api=order.create, duration=342ms, request={"userId":"U123","items":[...]}, response={"orderId":"MT20240515123"}
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
1621

被折叠的 条评论
为什么被折叠?



