在API网关(如Spring Cloud Gateway)中打印记录接口请求执行时间和请求参数,是一种常见的运维实践,旨在提升系统的可观测性、稳定性和可维护性。以下我将逐步解释其核心原因,确保内容清晰可靠。

1. 监控系统性能和瓶颈(执行时间记录)

  • 为什么记录执行时间?
    接口请求的执行时间(从请求进入网关到响应返回的耗时)是衡量API性能的关键指标。在网关层记录它:
    • 识别慢速接口:通过分析执行时间数据,可以快速发现哪些API响应延迟较高(如超过阈值),从而定位性能瓶颈。例如,如果一个接口的平均执行时间从$10ms$上升到$100ms$,可能表示后端服务或数据库负载过高。
    • 优化资源分配:基于时间数据,可以调整负载均衡策略或扩容服务实例,确保系统高效运行。
    • 实时告警:结合监控工具(如Prometheus),当执行时间异常时触发告警,避免影响用户体验。

2. 调试问题和故障排查(请求参数记录)

  • 为什么记录请求参数?
    请求参数包括用户输入、Header信息、Query参数等,是调试的核心依据:
    • 复现错误场景:当API返回错误(如$400$ Bad Request 或 $500$ Internal Server Error)时,参数日志能帮助开发者快速复现问题。例如,一个参数格式错误导致失败,日志中可查看具体值。
    • 追踪请求链路:在微服务架构中,请求可能经过多个服务。网关作为入口,记录参数后,结合Trace ID(如Jaeger),能实现端到端追踪,简化故障定位。
    • 避免数据丢失:某些错误可能只在特定参数组合下发生,记录参数确保问题不遗漏。

3. 增强安全性和合规性

  • 安全审计
    记录请求参数可用于检测异常行为,如恶意攻击(SQL注入、暴力破解)。例如,频繁的非法参数请求可能触发安全规则。
  • 合规要求
    在金融或医疗等行业,法规(如GDPR)要求记录关键操作日志。网关层的参数记录提供审计依据,满足合规需求。

4. 提升系统可维护性和数据分析

  • 数据分析基础
    执行时间和参数数据可聚合为报表,用于业务分析。例如,计算API的平均响应时间$(\bar{t} = \frac{\sum t_i}{n})$,或分析高频参数模式。
  • 预防性维护
    长期日志能揭示趋势,如参数错误率上升,提示需优化输入校验逻辑,减少错误请求。

下面是实现代码:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@Slf4j
public class GateWayRequestInfoFilter implements GlobalFilter, Ordered {

    private static final String START_TIME = "startTime";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 记录开始时间
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());

        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethod().name();

        // 构建基础请求信息


        // 对于POST/PUT等有body的请求,需要特殊处理来读取body内容
        if ("POST".equals(method) || "PUT".equals(method)) {
            log.info("请求方法:{},请求来源ip:{},请求路径:{}", request.getMethod(), request.getURI().getHost(), request.getURI().getPath());
            return logRequestBody(exchange, chain);
        } else {
            log.info("请求方法:{},请求来源ip:{},请求路径:{},请求参数:{}", request.getMethod(), request.getURI().getHost(), request.getURI().getPath(), request.getQueryParams());
            return chain.filter(exchange).then(logResponseTime(exchange));
        }
    }

    private Mono<Void> logRequestBody(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        return DataBufferUtils.join(serverHttpRequest.getBody())
                .defaultIfEmpty(exchange.getResponse().bufferFactory().allocateBuffer(0))
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);

                    String body = new String(bytes);
                    String fullRequestInfo = " 请求参数:" + "\n"+body ;
                    log.info(fullRequestInfo);

                    // 重新构造请求,将body放回
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(serverHttpRequest) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
                        }
                    };

                    ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                    return chain.filter(mutatedExchange).then(logResponseTime(mutatedExchange));
                });
    }

    private Mono<Void> logResponseTime(ServerWebExchange exchange) {
        return Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute(START_TIME);
            if (startTime != null) {
                Long executeTime = (System.currentTimeMillis() - startTime);
                log.info("请求接口所花时间:" + exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
            }
        });
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐