spring cloud gateway 不支持 @controlleradvice,必须实现 errorwebexceptionhandler 拦截异常;需异步发送日志至 rabbitmq,避免阻塞响应式流,并防范 response 已提交、mq 连接失败等可靠性陷阱。

Spring Cloud Gateway 本身不内置日志投递能力,也**不直接支持 RabbitMQ 异步日志审计**。想让网关异常请求自动发到 RabbitMQ,必须手动拦截异常、构造消息、交由 RabbitTemplate 异步发送——这中间没有“开箱即用”的钩子,全靠你补全链路。
为什么不能直接在 @ControllerAdvice 里处理网关异常
因为 Spring Cloud Gateway 是基于 WebFlux 的响应式网关,@ControllerAdvice 只对 Spring MVC 生效,对网关的异常完全无感知。你看到的 503、404、超时等错误,根本不会走到任何 Controller 层,更不会触发 @ExceptionHandler。
真实调用链是:ExceptionHandlingWebHandler → ErrorWebExceptionHandler → 默认 HTML 错误页。所以必须实现 ErrorWebExceptionHandler(或继承 AbstractErrorWebExceptionHandler),才能真正捕获网关级异常。
如何在 ErrorWebExceptionHandler 中注入 RabbitTemplate
Spring 容器可以管理 RabbitTemplate,但注意:网关异常处理器是响应式组件,所有操作需保持非阻塞。向 RabbitMQ 发送消息本身是同步调用(即使底层走 AMQP TCP),若直接在 handle() 方法里调用 rabbitTemplate.convertAndSend(),会阻塞事件循环线程,影响网关吞吐。
- 必须用
Mono.fromRunnable()或publishOn(Schedulers.boundedElastic())将发送逻辑切出主线程 -
RabbitTemplate要配置setChannelTransacted(false),避免事务干扰响应式流 - 建议启用
publisher-confirm-type: correlated和publisher-returns: true,失败时记录告警而非静默丢弃 - 不要在异常处理器中做重试,RabbitMQ 自身的 confirm/return + 死信队列已足够可靠
异常消息体该包含哪些关键字段
审计日志不是 debug 日志,要能支撑问题定位和 SLA 分析。至少保留以下字段,用 JSON 序列化后发送:
-
timestamp:异常发生时间(用Instant.now(),别用new Date()) -
status:HTTP 状态码(如503、404) -
error:异常类名(如org.springframework.cloud.gateway.support.NotFoundException) -
message:精简可读提示(如"Service 'user-service' not available in registry"),别直接扔堆栈 -
path:原始请求路径(exchange.getRequest().getURI().getPath()) -
traceId:从exchange.getAttribute("X-B3-TraceId")或ReactorContext中取,确保与下游服务日志可串联 -
gatewayInstanceId:取自spring.application.name+ 实例标识,用于多节点日志归属
示例片段(非完整类):
if (ex instanceof NotFoundException) {
auditLog.put("message", "Route matched but target service unavailable");
} else if (ex instanceof TimeoutException) {
auditLog.put("message", "Upstream service timeout");
}
容易被忽略的可靠性陷阱
最常被跳过的点是:网关异常时,ServerHttpResponse 可能已被 commit。此时再试图写入响应体或设置状态码会抛 IllegalStateException,但这个新异常又会再次进入 handle(),造成无限递归或线程卡死。
务必在开头加判断:
if (exchange.getResponse().isCommitted()) {
// 只发 RabbitMQ,不碰 response
sendToRabbit(auditLog);
return Mono.error(ex);
}
另一个隐形坑是:如果 RabbitMQ 连接中断,而你的发送逻辑没兜底(比如没设 try-catch 捕获 AmqpConnectException),会导致整个异常处理流程崩溃,最终 fallback 到默认 HTML 错误页——前端看到的是白屏,而不是你设计的 JSON 错误响应。
所以,sendToRabbit() 内部必须吞掉所有 AMQP 相关异常,并记录 warn 日志,保证主流程不被 MQ 故障拖垮。











