简介
本文件面向 DouPHP 后台服务层,系统化说明错误处理与日志记录的设计与实践。内容覆盖:
- 自定义异常类型与捕获策略(领域异常、跳转异常、响应异常)
- 错误码与上下文约定(审计标签、通道、采样与限流)
- 日志分级、结构化格式、轮转策略与脱敏要求
- 在业务服务中正确抛出异常与记录日志的最佳实践
- 监控告警集成思路与日志分析方法
- 安全相关错误处理与日志脱敏规范
项目结构
DouPHP 将错误处理与日志能力分布在以下位置:
- 异常定义:foundation/exception 与 web/http 下提供领域异常、跳转异常、HTTP 响应异常
- 中间件与安全头:foundation/middleware 提供基线安全头中间件,保障请求边界安全
- 路由端识别:web/routing 提供路由条目端标识,便于按端隔离错误与日志行为
- 登录流程示例:admin/service/login 展示认证失败时的日志与异常使用
- 日志实现:_/.api/lib/Log.php 提供统一日志写入、采样、限流与落盘
- 审计与日志标签:core/service 下的审计服务与用户日志标签字典,用于规范化审计输出
graph TB
A["控制器/入口"] --> B["中间件管道<br/>安全头/鉴权/限流"]
B --> C["路由匹配<br/>端识别(Front/Admin/Api)"]
C --> D["服务层<br/>业务逻辑"]
D --> E["异常体系<br/>Domain/Redirect/HttpResponse"]
D --> F["日志系统<br/>分级/采样/限流/落盘"]
E --> G["三端入口捕获<br/>渲染提示/重定向/JSON"]
F --> H["日志文件<br/>按日分片/轮转"]
核心组件
- 领域异常 DomainException:承载业务错误消息、返回链接、自动跳转秒数、二次确认 URL、字段错误集合与模板样式标志,供 Controller/入口统一渲染或 JSON 响应。
- 跳转异常 RedirectException:用于无感跳转场景,携带目标 URL 与 HTTP 状态码,由入口直接发送重定向响应。
- HTTP 响应异常 HttpResponseException:携带 Response 对象,用于无法通过 return 短路到内核 send 的场景。
- 安全头中间件 AbstractSecurityHeadersMiddleware:在管道最前置下发基线安全头,仅对命中路由生效。
- 路由端识别 RouteEntry::endNamespace:根据控制器命名空间推导所属端(Front/Admin/Api),用于跨端隔离的错误与日志行为。
- 日志 Log:支持采样率控制、每分钟限流、按日分片落盘、上下文标准化与通道区分。
- 审计与标签:AuditService 提供通用审计写入;UserLogDetail 提供用户日志 details 的标签字典,收敛为稳定键名,避免 PII 落库。
架构总览
后端请求进入中间件管道后,经安全头设置与鉴权等处理后,路由解析并识别端(前台/后台/API)。服务层执行业务逻辑,遇到业务规则违反时抛出 DomainException;需要无感跳转时抛出 RedirectException;需要直接输出 Response 时抛出 HttpResponseException。三端入口捕获这些异常,分别渲染提示页、发送重定向或返回 JSON。同时,服务层通过统一日志接口记录结构化日志,包含 channel、level、message、context,并按采样率和限流策略控制写入频率,最终按日分片落盘。
sequenceDiagram
participant Client as "客户端"
participant MW as "中间件管道"
participant RT as "路由/端识别"
participant SVC as "服务层"
participant LOG as "日志系统"
participant EXC as "异常处理器"
Client->>MW : "HTTP 请求"
MW->>RT : "匹配路由并识别端"
RT->>SVC : "调用服务方法"
SVC->>LOG : "记录业务日志(含channel/context)"
alt 业务规则违反
SVC-->>EXC : "抛出 DomainException"
else 需要无感跳转
SVC-->>EXC : "抛出 RedirectException"
else 需要直接响应
SVC-->>EXC : "抛出 HttpResponseException"
end
EXC-->>Client : "提示页/重定向/JSON"
详细组件分析
异常体系与捕获策略
- 领域异常 DomainException
- 用途:服务层中断业务流程并向 Controller/入口传递已翻译消息与页面参数(返回链接、倒计时、二次确认 URL、字段错误集合、模板样式标志)。
- 捕获策略:三端入口统一捕获后调用消息响应器输出(后台/前台终止页或 API JSON)。
- 跳转异常 RedirectException
- 用途:深层调用栈中无感跳转到指定 URL,不渲染提示页。
- 捕获策略:入口捕获后直接发送 302(或自定义状态码)重定向响应。
- HTTP 响应异常 HttpResponseException
- 用途:携带 Response 对象,用于无法通过 return 短路到内核 send 的场景。
- 捕获策略:入口捕获后直接发送 Response。
classDiagram
class DomainException {
+getBackUrl() string
+getTimer() string
+getConfirmUrl() string
+getErrors() array
+hasErrors() bool
+getOut() string
}
class RedirectException {
+getUrl() string
+getStatusCode() int
}
class HttpResponseException {
+getResponse() Response
}
DomainException <|-- RedirectException : "语义差异(提示 vs 跳转)"
HttpResponseException ..> DomainException : "入口统一捕获"
日志系统与分级管理
- 分级与上下文
- 日志级别:info/warning/error 等(由调用方传入 level)
- 上下文:channel(如 auth)、ip、用户名或 ID、业务键值等,便于检索与聚合
- 采样与限流
- 采样率:按概率丢弃日志,降低高频日志开销
- 限流:按分钟粒度对同一 key(minute|channel|level|message哈希前缀)限制写入次数
- 落盘与轮转
- 按日分片:log_YYYY-MM-DD.log
- 轮转:操作系统或外部工具按大小/时间切分归档(代码层面以日为单位自然轮转)
- 敏感数据过滤
- 上下文中的敏感字段(密码、令牌、完整手机号/邮箱)应脱敏或省略
- 审计日志使用稳定标签字典(如 UserLogDetail),避免 PII 落库
flowchart TD
Start(["记录日志入口"]) --> Normalize["标准化上下文"]
Normalize --> Sample{"是否通过采样率?"}
Sample -- "否" --> Skip["跳过写入"]
Sample -- "是" --> RateLimit{"是否超过每分钟限流?"}
RateLimit -- "是" --> Skip
RateLimit -- "否" --> InitPath["初始化日志路径"]
InitPath --> Write["写入 log_YYYY-MM-DD.log"]
Write --> End(["完成"])
Skip --> End
安全相关错误处理与日志脱敏
- 安全头中间件
- 在管道最前置下发基线安全头(X-Content-Type-Options、X-Frame-Options、Referrer-Policy、Permissions-Policy;HSTS 仅在 HTTPS 且配置开启时下发)
- 仅对命中路由生效,404 由 Router 自渲染不在覆盖范围
- 审计与标签
- 审计服务写操作日志时,使用稳定的 action/detail 标签,避免明文 PII
- 用户日志详情标签字典收敛为大写下划线键,确保可读性与安全性
sequenceDiagram
participant Req as "请求"
participant MW as "安全头中间件"
participant SVC as "服务层"
participant AUD as "审计服务"
Req->>MW : "进入管道"
MW-->>Req : "下发安全头"
Req->>SVC : "执行业务"
SVC->>AUD : "写入审计日志(标签化)"
AUD-->>SVC : "成功/失败"
开发示例:在服务类中正确处理异常与记录日志
- 登录失败场景(后台)
- 当管理员账号不存在或密码无效时,记录 warning 日志(channel=auth,包含 ip、用户名或 admin_id)
- 写入审计日志(登录失败,detail 标签来自 AdminLogDetail)
- 抛出 DomainException,携带提示信息、返回链接、模板样式标志 'out'
- 最佳实践要点
- 上下文信息收集:ip、channel、业务键(如 admin_id),避免明文敏感数据
- 敏感数据过滤:不记录密码、令牌、完整手机号/邮箱
- 性能影响控制:合理使用采样率与限流,避免高频日志拖慢请求
sequenceDiagram
participant Ctrl as "控制器"
participant Flow as "登录流程"
participant Log as "日志系统"
participant Aud as "审计服务"
participant Ex as "异常处理器"
Ctrl->>Flow : "验证凭据"
alt 账号不存在
Flow->>Log : "warning(channel=auth, ip, username)"
Flow->>Aud : "writeAdminLog(action=LOGIN_FAIL, detail=INPUT_WRONG)"
Flow-->>Ex : "抛出 DomainException"
else 密码无效
Flow->>Log : "warning(channel=auth, ip, admin_id)"
Flow->>Aud : "writeAdminLog(action=LOGIN_FAIL, detail=INPUT_WRONG)"
Flow-->>Ex : "抛出 DomainException"
end
Ex-->>Ctrl : "渲染提示页/返回链接"
复杂逻辑组件:日志写入流程
- 标准化上下文:去除空值、统一键名、屏蔽敏感字段
- 采样率判断:按概率丢弃日志,降低 I/O 压力
- 限流判断:按 minute|channel|level|message 哈希前缀限制写入次数
- 落盘:创建目录(若不存在),写入 log_YYYY-MM-DD.log,追加模式并加锁
flowchart TD
S["开始"] --> N["标准化上下文"]
N --> R1{"采样率允许?"}
R1 -- "否" --> X["跳过"]
R1 -- "是" --> R2{"限流允许?"}
R2 -- "否" --> X
R2 -- "是" --> W["写入日志文件"]
W --> E["结束"]
X --> E
依赖关系分析
- 中间件与路由
- 安全头中间件在管道最前置运行,仅对命中路由生效
- 路由条目可识别端(Front/Admin/Api),用于差异化错误与日志行为
- 服务层与异常
- 服务层抛出领域异常、跳转异常或响应异常,由入口统一捕获
- 服务层与日志
- 服务层通过统一日志接口记录结构化日志,受采样与限流控制
- 审计与标签
- 审计服务使用稳定标签字典,避免 PII 落库,提升可读性与安全性
graph LR
MW["安全头中间件"] --> RT["路由端识别"]
RT --> SVC["服务层"]
SVC --> EXC["异常体系"]
SVC --> LOG["日志系统"]
SVC --> AUD["审计服务"]
EXC --> OUT["三端入口响应"]
LOG --> FILE["日志文件"]
性能考量
- 采样率:在高并发场景下,合理设置采样率以降低日志 I/O 压力
- 限流:按分钟粒度限制相同 key 的写入次数,避免日志风暴
- 上下文最小化:仅记录必要字段,减少序列化与落盘开销
- 异步写入:如需更高吞吐,可在上层引入队列异步落盘(当前实现为同步追加)
- 日志轮转:按日分片天然具备轮转特性,配合外部工具进行归档与清理
故障排查指南
- 常见问题定位
- 登录失败:检查 auth 通道的 warning 日志与审计日志(LOGIN_FAIL)
- 业务规则违反:捕获 DomainException,查看 message、errors 与 backUrl
- 无感跳转异常:捕获 RedirectException,核对 url 与 statusCode
- 直接响应异常:捕获 HttpResponseException,检查 Response 内容与状态码
- 日志分析步骤
- 按 channel 与 level 筛选关键日志
- 结合 ip、admin_id、user_id 等业务键定位具体请求
- 关注采样与限流导致的日志缺失,必要时临时调整采样率
- 安全与合规
- 确保日志中不包含密码、令牌、完整手机号/邮箱等敏感信息
- 审计日志使用稳定标签字典,避免 PII 落库
结论
DouPHP 后台服务层通过统一的异常体系与日志系统,实现了清晰的分层职责与可控的错误处理流程。领域异常、跳转异常与响应异常覆盖了常见业务场景;日志系统提供分级、结构化、采样与限流能力,并按日分片落盘。结合安全头中间件与审计标签字典,系统在安全性与可观测性方面达到良好平衡。建议在开发中遵循上下文最小化、敏感数据脱敏与合理采样限流的实践,以提升性能与合规性。
附录
- 错误码与上下文约定
- channel:如 auth、system、order 等,用于分类日志
- level:info/warning/error,用于分级
- context:ip、admin_id、user_id、业务键值等,避免敏感字段
- 监控与告警集成建议
- 将日志接入集中式日志平台(如 ELK、Sentry、Prometheus+Alertmanager)
- 基于 channel 与 level 设置阈值告警(如 auth.error 突增)
- 结合审计日志与业务指标,建立端到端可观测性
- 日志分析与故障排查方法
- 按时间窗口与 channel 筛选关键日志
- 结合 traceId 或 request id 追踪请求链路
- 针对高频错误进行根因分析与修复