gateway网关中打印记录接口请求执行时间、请求参数
·
在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;
}
}
更多推荐




所有评论(0)