Spring Cloud Gateway实战手把手教你自定义一个全局日志过滤器附完整代码在微服务架构中API网关作为系统的唯一入口承担着请求路由、负载均衡、安全认证等重要职责。而Spring Cloud Gateway作为Spring官方推出的第二代网关框架凭借其非阻塞式架构和灵活的过滤器机制已经成为众多企业的首选方案。今天我们就来深入探讨如何为Spring Cloud Gateway定制一个功能完善的全局日志过滤器帮助开发者快速掌握这一实用技能。日志记录看似简单但在网关层实现高效、可定制的全局日志却需要考虑诸多因素如何捕获完整的请求信息如何优化日志输出格式如何平衡日志详尽度与系统性能本文将从一个实际项目需求出发带你一步步构建一个工业级的日志过滤器解决方案。1. 环境准备与基础配置在开始编码之前我们需要确保开发环境已经正确配置。以下是基础环境要求JDK 1.8或更高版本Spring Boot 2.3.x及以上Spring Cloud Hoxton.SR8及以上版本日志框架推荐使用Logback或Log4j2创建一个基础的Spring Boot项目添加以下Maven依赖dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies提示在生产环境中建议同时引入spring-boot-starter-actuator用于监控网关状态以及相应的日志框架starter。2. 核心过滤器实现全局日志过滤器的核心是实现GlobalFilter接口我们将创建一个名为GatewayLoggingFilter的组件。这个过滤器需要完成以下关键功能记录请求基本信息URL、方法、头信息捕获请求体内容对于POST/PUT请求记录响应状态码和响应时间支持自定义日志格式输出Component Slf4j public class GatewayLoggingFilter implements GlobalFilter, Ordered { private static final String REQUEST_TIME_BEGIN requestTimeBegin; Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 记录请求开始时间 exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); // 记录请求基本信息 ServerHttpRequest request exchange.getRequest(); log.info(Incoming request: {} {}, request.getMethod(), request.getURI()); // 记录请求头 if (!request.getHeaders().isEmpty()) { log.debug(Request headers: {}, request.getHeaders()); } // 对于POST/PUT请求记录请求体 if (HttpMethod.POST.equals(request.getMethod()) || HttpMethod.PUT.equals(request.getMethod())) { return chain.filter(exchange.mutate().request( new RequestLoggingDecorator(exchange.getRequest())).build()) .then(Mono.fromRunnable(() - { // 记录响应信息 ServerHttpResponse response exchange.getResponse(); Long startTime exchange.getAttribute(REQUEST_TIME_BEGIN); long executeTime System.currentTimeMillis() - (startTime ! null ? startTime : 0L); log.info(Response status: {}, Execution time: {}ms, response.getStatusCode(), executeTime); })); } return chain.filter(exchange).then(Mono.fromRunnable(() - { ServerHttpResponse response exchange.getResponse(); Long startTime exchange.getAttribute(REQUEST_TIME_BEGIN); long executeTime System.currentTimeMillis() - (startTime ! null ? startTime : 0L); log.info(Response status: {}, Execution time: {}ms, response.getStatusCode(), executeTime); })); } Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }注意对于请求体的记录需要特别小心因为请求体只能被读取一次。我们使用了装饰器模式来包装原始请求确保后续过滤器仍能正常读取请求体。3. 请求体记录装饰器实现为了能够记录POST/PUT请求的请求体内容同时不影响后续过滤器的正常处理我们需要实现一个自定义的ServerHttpRequest装饰器public class RequestLoggingDecorator extends ServerHttpRequestDecorator { private static final Logger log LoggerFactory.getLogger(RequestLoggingDecorator.class); public RequestLoggingDecorator(ServerHttpRequest delegate) { super(delegate); } Override public FluxDataBuffer getBody() { return super.getBody().doOnNext(dataBuffer - { try { byte[] bytes new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); String body new String(bytes, StandardCharsets.UTF_8); log.debug(Request body: {}, body); } catch (Exception e) { log.warn(Failed to read request body, e); } }); } }这个装饰器会在请求体被读取时记录其内容同时确保原始数据缓冲区被正确释放。在实际项目中你可能需要根据业务需求对敏感信息如密码、token等进行脱敏处理。4. 高级配置与优化一个生产级的日志过滤器还需要考虑以下高级特性4.1 日志格式自定义我们可以通过配置属性来允许用户自定义日志输出格式。首先创建一个配置类ConfigurationProperties(prefix gateway.logging) Data public class GatewayLoggingProperties { private boolean enabled true; private boolean logHeaders true; private boolean logBody false; private String dateFormat yyyy-MM-dd HH:mm:ss.SSS; private ListString excludeHeaders Arrays.asList(authorization, cookie); }然后在过滤器中应用这些配置Component Slf4j RequiredArgsConstructor public class GatewayLoggingFilter implements GlobalFilter, Ordered { private final GatewayLoggingProperties properties; // ... 其他代码保持不变 private void logRequest(ServerHttpRequest request) { if (!properties.isEnabled()) return; StringBuilder logMessage new StringBuilder(); logMessage.append(Incoming request: ).append(request.getMethod()) .append( ).append(request.getURI()); if (properties.isLogHeaders() !request.getHeaders().isEmpty()) { HttpHeaders headers new HttpHeaders(); request.getHeaders().forEach((name, values) - { if (!properties.getExcludeHeaders().contains(name.toLowerCase())) { headers.addAll(name, values); } }); logMessage.append(\nHeaders: ).append(headers); } log.info(logMessage.toString()); } }4.2 性能优化策略日志记录虽然重要但过度记录可能会影响网关性能。以下是几种优化策略异步日志记录使用Logback或Log4j2的异步appender采样记录对于高频请求可以配置采样率关键路径优化将日志记录放在过滤器链的最后低优先级Override public int getOrder() { // 设置为最低优先级确保其他关键过滤器先执行 return Ordered.LOWEST_PRECEDENCE; }4.3 异常处理在网关层面我们需要妥善处理各种异常情况Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { try { // ... 原有代码 } catch (Exception e) { log.error(Logging filter error, e); return chain.filter(exchange); } }5. 测试与验证完成编码后我们需要验证过滤器的功能是否正常。可以使用Postman或curl发送各种类型的请求GET请求测试curl -X GET http://localhost:8080/api/servicePOST请求测试curl -X POST http://localhost:8080/api/service \ -H Content-Type: application/json \ -d {username:test,password:123456}预期日志输出示例2023-06-15 14:30:22.456 INFO GatewayLoggingFilter - Incoming request: POST http://localhost:8080/api/service Headers: [Content-Type:application/json, Content-Length:38] Request body: {username:test,password:******} 2023-06-15 14:30:22.478 INFO GatewayLoggingFilter - Response status: 200 OK, Execution time: 22ms6. 生产环境部署建议在实际部署时还需要考虑以下因素日志轮转策略配置适当的日志文件大小和保留周期敏感信息过滤确保密码、token等敏感信息不被记录监控集成将网关日志与ELK或Splunk等日志系统集成动态配置支持运行时调整日志级别和详细程度以下是一个推荐的Logback配置示例configuration appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/gateway.log/file rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy fileNamePatternlogs/gateway.%d{yyyy-MM-dd}.%i.log.gz/fileNamePattern maxFileSize100MB/maxFileSize maxHistory30/maxHistory totalSizeCap5GB/totalSizeCap /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender logger namecom.example.gateway.filters.GatewayLoggingFilter levelINFO additivityfalse appender-ref refFILE/ /logger root levelINFO appender-ref refFILE/ /root /configuration在实际项目中我们发现合理配置的全局日志过滤器可以显著提高问题排查效率。特别是在微服务架构中当请求需要经过多个服务时网关层的完整日志记录能够帮助我们快速定位问题发生的环节。