基于Spring AOP实现美团API调用日志的自动埋点与性能分析

基于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开发者团队,转载请注明出处!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值