hyperf全局异常处理需严格按优先级注册handler、分离http状态码与业务码、强制调用stoppropagation()终结异常、结构化记录带协程id和请求id的日志,任一环节缺失将导致状态码错乱或worker崩溃。

Hyperf 全局异常接管不是简单配个类就完事,而是要确保异常能被正确捕获、分类处理、响应合规、日志可溯——漏掉任一环节,轻则前端收不到 4xx 状态码,重则 Worker 进程崩溃退出。
Handler 注册顺序必须严格按优先级排列
config/autoload/exceptions.php 中的 handler 数组是**执行队列**,靠前的先试 isValid()。若把宽泛的 AppExceptionHandler 放在 HttpExceptionHandler 前面,连 404、405 都会被吞成 500,前端无法区分资源不存在和服务器错误。
- HttpExceptionHandler(框架内置)应始终放在最前,它专管 NotFoundException、MethodNotAllowedHttpException 等标准 HTTP 异常,并返回对应 status
- 自定义 Handler(如 ValidationExceptionHandler、ResourceExceptionHandler)必须在其后注册
- 兜底 Handler(捕获 Throwable)务必放在最后,且 isValid() 中需明确限定类型,避免 return true 无差别接管
HTTP 状态码与业务码必须分离
throw new FooException('msg', 504) 中的 504 是业务码,不会自动映射为 HTTP status。HTTP 响应状态码需手动设置,且应严格遵循语义规范:
- 4xx 类错误(如参数校验失败、资源未找到)用 400/404/422,不能用 500 冒充
- 5xx 类错误统一用 500,业务含义通过 JSON body 的 code 字段传达(如 "code": 504, "message": "上游服务超时")
- handle() 方法中必须显式调用 $response->withStatus(422) 或类似操作,否则默认返回 500
stopPropagation() 不是可选项,是强制动作
每个 handle() 执行完后,若已决定终结该异常,就必须调用 $this->stopPropagation()。否则异常会继续传递给下一个 Handler,导致重复记录、响应被覆盖、甚至触发兜底逻辑掩盖真实问题。
- 只在真正处理了当前异常时调用 stopPropagation()
- 若 isValid() 返回 true 但内部判断不处理(比如仅记录日志),则不该调用 stopPropagation(),让异常继续往下走
- 常见错误:写了 if ($e instanceof ValidationException) { ... } 却忘了加 stopPropagation(),结果 ValidationExceptionHandler 和兜底 Handler 各记一次日志
上下文日志必须绑定协程与请求标识
协程环境下,传统全局变量 $_SERVER 或 time() 无法隔离请求上下文,日志混杂将导致排查失效。
- 记录日志时,必须传入协程 ID 和请求 ID:['cid' => \Swoole\Coroutine::getCid(), 'rid' => $request->getAttribute('request_id') ?? 'unknown']
- 启用 LineFormatter 的 include_stacktraces(设为 true),确保堆栈不丢失
- 不要依赖容器或 Context 在日志 formatter 外部取 request_id —— 必须在 handle() 或控制器入口处显式注入并透传











