文档目录
错误处理机制

简介

本技术文档聚焦 DouPHP 的错误处理机制,覆盖异常体系、调试工具 Whoops 的集成与差异化配置、错误日志记录策略、用户友好错误页面生成、以及监控告警集成思路。面向初学者解释“为什么需要完善的错误处理”,同时为高级开发者提供自定义处理器与监控方案的可落地路径。

项目结构

DouPHP 将错误处理拆分为多个职责清晰的模块:

  • 领域异常与跳转异常:用于业务层中断流程并携带上下文(如返回链接、自动跳转、字段错误等)。
  • 站点调试渲染器:统一在开启调试时输出 HTML 或 JSON 调试信息,并决定是否注册 Whoops 全局处理器。
  • 日志子系统:分级、脱敏、采样、限流、按日轮转的本地日志写入。
  • 初始化与全局钩子:注册致命错误兜底、运行期诊断错误接管、未捕获异常兜底。
  • HTTP 响应异常:用于在服务层无法直接返回 Response 时短路到内核发送响应。
  • Whoops 模板辅助:在调试页中安全地展示变量与堆栈。
graph TB
A["入口/控制器"] --> B["业务服务层"]
B --> C{"是否抛出异常?"}
C -- "是" --> D["DomainException / RedirectException"]
C -- "否" --> E["正常响应"]
D --> F["三端入口捕获<br/>渲染提示页/重定向/JSON"]
F --> G["日志记录(Log)"]
A --> H["未捕获异常/致命错误"]
H --> I["InitTrait 全局处理"]
I --> J{"site.debug ?"}
J -- "是" --> K["SiteDebugExceptionRenderer<br/>Whoops HTML 或 JSON 500"]
J -- "否" --> L["仅记录日志并返回通用错误"]

核心组件

  • 领域异常 DomainException:承载已翻译消息、返回链接、自动跳转秒数、二次确认 URL、字段错误集合与 admin 样式标志,供三端入口统一渲染提示页或 JSON。
  • 跳转异常 RedirectException:用于无感跳转场景,仅携带目标 URL 与状态码,由入口直接发送重定向响应。
  • 站点调试渲染 SiteDebugExceptionRenderer:判断是否开启调试、是否为 JSON 类请求、解析渠道(front/admin/api),并在调试模式下输出 Whoops HTML 或 JSON 500。
  • 日志 Log:分级、脱敏、采样、限流、按日轮转;自动补全请求上下文(IP、路由、用户 ID 等)。
  • 初始化 InitTrait:注册全局异常/致命错误/运行期诊断错误处理,决定 Whoops 全局注册时机。
  • HTTP 响应异常 HttpResponseException:在服务层无法 return Response 时,通过异常短路到内核发送响应。
  • Whoops TemplateHelper:在调试页中对变量进行安全转义与展示。

架构总览

下图展示了从异常产生到最终呈现/记录的完整链路,包括 Whoops 的介入点与日志落盘位置。

sequenceDiagram
participant App as "应用/控制器"
participant Biz as "业务服务"
participant Core as "InitTrait"
participant Render as "SiteDebugExceptionRenderer"
participant Whoops as "Whoops"
participant Logger as "Log"
App->>Biz : 执行业务逻辑
Biz-->>App : 抛出 DomainException/RedirectException
App->>Core : 未捕获异常/致命错误
Core->>Logger : 记录未捕获异常/致命错误
Core->>Render : 判断 site.debug 与请求类型
alt JSON/Ajax/API
Render-->>App : sendApi500(含 trace)
else HTML 常规请求
Render->>Whoops : render($e, channel)
Whoops-->>App : 输出调试页(500)
end

详细组件分析

异常体系与使用约定

  • DomainException:适合“业务规则违反”的场景,携带用户可见提示、返回链接、自动跳转秒数、二次确认 URL、字段错误集合与 admin 样式标志。三端入口捕获后统一调用消息响应器输出提示页或 JSON。
  • RedirectException:适合“无感跳转”的场景,仅携带目标 URL 与状态码,入口直接发送 302 或其他重定向头,不渲染提示页。
  • HttpResponseException:当服务层无法直接返回 Response 时,抛出自带 Response 的异常,由管道/内核捕获并发送。
classDiagram
class DomainException {
-string backUrl
-string timer
-string confirmUrl
-array errors
-string out
+__construct(message, backUrl, timer, confirmUrl, errors, out)
+getBackUrl() string
+getTimer() string
+getConfirmUrl() string
+getErrors() array
+hasErrors() bool
+getOut() string
}
class RedirectException {
-string url
-int statusCode
+__construct(url, statusCode, message)
+getUrl() string
+getStatusCode() int
}
class HttpResponseException {
-Response response
+__construct(response, message, code, previous)
+getResponse() Response
}
DomainException <|-- RedirectException : "语义区分"
HttpResponseException ..> Response : "携带响应"

站点调试与 Whoops 集成

  • 调试开关优先级:常量 DOU_DEBUG > 配置项 site.debug。
  • JSON 类请求判定:IS_API、XMLHttpRequest、Accept: application/json。
  • 渠道解析:admin/front/api。
  • 行为:
    • JSON 请求:构造标准 JSON 500 响应,包含 exception、file、line、trace(截断)。
    • HTML 请求:优先走 Whoops PrettyPage;若失败则降级为纯 HTML 500 页面。
    • 仅在非 JSON 请求且启用调试时注册 Whoops 全局处理器。
flowchart TD
Start(["未捕获异常"]) --> CheckDebug{"site.debug 开启?"}
CheckDebug -- "否" --> LogOnly["记录日志并返回通用错误"]
CheckDebug -- "是" --> JsonCheck{"JSON 类请求?"}
JsonCheck -- "是" --> Api500["sendApi500(含 trace)"]
JsonCheck -- "否" --> Whoops{"Whoops 可用?"}
Whoops -- "是" --> HtmlPage["Whoops 调试页(500)"]
Whoops -- "否" --> PlainHtml["降级为纯 HTML 500"]

错误日志记录机制

  • 级别:emergency/alert/critical/error/warning/notice/info/debug,支持最小级别过滤与白名单。
  • 存储:默认 storage/log/ 下按日命名 log_YYYY-MM-DD.log,写入前确保目录存在。
  • 上下文:自动补全 request_id、scene、ip、route、request_uri、method、module、action、user_id、admin_id、work_id。
  • 安全:敏感键名递归掩码;URL 查询串中的敏感参数值被替换为占位符。
  • 采样与限流:支持采样率与每分钟同 key 最大条数限制,避免洪泛。
  • 清理:提供 clean() 方法清理历史日志。
flowchart TD
W(["Log::write(level,message,context)"]) --> Level{"级别>=minLevel?"}
Level -- "否" --> Drop["丢弃"]
Level -- "是" --> Channel{"channel 在白名单?"}
Channel -- "否" --> Drop
Channel -- "是" --> AutoCtx["自动补全上下文"]
AutoCtx --> Normalize["脱敏/收敛 trace"]
Normalize --> Sample{"采样通过?"}
Sample -- "否" --> Drop
Sample -- "是" --> RateLimit{"限流通过?"}
RateLimit -- "否" --> Drop
RateLimit -- "是" --> Write["写入 storage/log/log_YYYY-MM-DD.log"]

用户友好的错误页面生成

  • 调试模式:HTML 请求优先 Whoops 调试页,具备语法高亮、变量查看、堆栈跟踪;不可用时降级为简洁 HTML 500。
  • 非调试模式:不暴露内部细节,仅记录日志并返回通用错误。
  • API 请求:始终返回 JSON 500,附带异常类名、文件、行号与截断后的堆栈,便于前端与监控系统消费。
  • 安全:调试页中对字符串进行转义,避免 XSS。

开发环境与生产环境的差异化处理

  • 开发环境:建议开启 site.debug,以便获得 Whoops 调试页与更详细的 JSON 错误载荷;日志级别可设为 DEBUG。
  • 生产环境:关闭 site.debug,仅记录必要日志;对运行期诊断错误(notice/warning/deprecated/strict)进行去重限流后以 warning 级记录,阻断默认输出防止泄漏。
  • Whoops 全局注册:仅在非 JSON 请求且调试开启时注册,避免干扰 API 响应。

依赖关系分析

  • 入口/控制器依赖业务服务,服务可能抛出领域异常或跳转异常。
  • 未捕获异常/致命错误由 InitTrait 统一接管,并委托 SiteDebugExceptionRenderer 决定输出形式。
  • SiteDebugExceptionRenderer 依赖 Whoops 进行 HTML 调试页渲染,否则回退到纯 HTML。
  • 所有异常与运行期诊断错误均通过 Log 记录,受级别、采样、限流与安全策略控制。
graph LR
Controller["控制器/入口"] --> Service["业务服务"]
Service --> |抛出| DomainEx["DomainException"]
Service --> |抛出| RedirectEx["RedirectException"]
Controller --> |未捕获| InitT["InitTrait"]
InitT --> Renderer["SiteDebugExceptionRenderer"]
Renderer --> Whoops["Whoops"]
InitT --> Logger["Log"]
Renderer --> Logger

性能与可观测性

  • 日志性能:通过采样率与每分钟同 key 限流降低高频错误对 IO 的压力;非 debug 模式收敛 trace 长度。
  • 内存占用:去重与限流计数为请求级静态变量,避免跨请求累积。
  • 可观测性:每条日志自动携带 request_id、scene、ip、route、用户身份等上下文,便于追踪与聚合分析。
  • 安全:敏感字段与 URL 查询串参数自动脱敏,避免凭据泄露。

故障排查指南

  • 未捕获异常:检查 InitTrait 的全局异常处理是否生效,确认 site.debug 与 Whoops 可用性。
  • 运行期诊断错误:确认 error_reporting 与 @ 抑制符行为,确保 handleRuntimeError 正确拦截并记录。
  • 日志缺失:检查 Log::setMinLevel、enabledLevels、enabledChannels、sampleRate、maxPerMinutePerKey 等配置。
  • 敏感信息泄露:核对 redactSensitive 与 URL 查询串掩码逻辑,必要时扩展敏感键名片段。
  • 调试页不可用:确认 Whoops 类是否存在,若自身渲染失败会降级为纯 HTML 500。

结论

DouPHP 的错误处理体系以“异常分层 + 调试渲染 + 日志可观测”为核心,兼顾开发与生产的不同需求。通过 Whoops 提升调试效率,通过 Log 保障生产稳定性与安全性,通过领域异常与跳转异常规范业务错误传播。结合监控与告警,可实现从发现到定位再到修复的闭环。

附录:最佳实践与示例

  • 业务错误:在服务层抛出 DomainException,携带已翻译消息与必要上下文;入口统一渲染提示页或 JSON。
  • 无感跳转:使用 RedirectException 指定目标 URL 与状态码,避免层层 return。
  • 调试开关:开发环境开启 site.debug,生产环境关闭;API 请求始终返回 JSON 错误。
  • 日志规范:使用合适的级别,避免在循环中频繁写日志;利用 context 携带关键上下文;注意敏感数据脱敏。
  • 监控告警:基于 Log 的 level 与 channel 聚合,设置 critical/error 阈值告警;对 API 500 错误进行专项监控。
  • 自定义处理器:可在 InitTrait 中扩展 handleGlobalException/handleShutdownFatal,或在 SiteDebugExceptionRenderer 中定制 JSON 500 载荷与 Whoops 渲染。
添加日期:2026-10-05