订单日志应写在服务层(如OrderService::pay()末尾),而非Controller;须在事务提交后记录,优先存入结构化order_log数据库表,并包含user_id、ip、source及语义化data字段。

订单日志该写在哪一层?别在 Controller 里硬塞
Yii 应用中,订单创建、状态变更等关键操作通常发生在 OrderService 或 OrderRepository 这类服务/领域层,而不是 OrderController。把日志逻辑塞进 Controller 容易导致重复、遗漏,且违反单一职责。日志应和业务动作强绑定——订单支付成功后立刻记一条,不是“用户点了按钮之后某处再补记”。
- 推荐位置:在
OrderService::pay()、OrderService::ship()等方法末尾调用日志记录器 - 避免位置:不要在
beforeAction()或全局行为里统一拦截订单相关请求来记日志——无法区分是创建、支付还是退款 - 注意:如果用了事务(
Yii::$app->db->beginTransaction()),日志写入必须在事务提交后(或使用afterCommit回调),否则事务回滚时日志却已落盘,造成不一致
用 Yii 的 Logger 还是自定义表?优先选数据库表 + 行为式记录
Yii 内置的 Yii::info() / Yii::error() 默认写入 runtime/logs/app.log,适合调试,但查订单日志时几乎没法用:没有订单号索引、无结构化字段、不能关联用户或状态变更前值。生产环境必须落地到数据库。
- 建一张
order_log表,至少含:order_id、action(如'paid'、'shipped')、data(JSON 字段存变更详情,如{"amount":199.00,"payment_method":"alipay"})、created_at - 用 ActiveRecord 行为(
Behavior)封装日志逻辑,例如OrderLogBehavior,在afterSave或自定义事件(如Order::EVENT_PAID)中触发写入 - 别直接在模型
afterSave()里写日志——Order 模型可能被用于非业务场景(如后台导入),此时不该记日志
怎么保证日志内容可追溯?关键字段不能靠 guess
常见错误是只记 “订单已支付”,却不存原始操作上下文。一旦出问题,你根本不知道是哪个接口、哪个用户、带什么参数触发的这次支付。
- 必须记录:
user_id(当前操作人,不是Order::user_id)、ip(Yii::$app->request->getUserIP())、source(如'web'、'api_v2'、'admin') -
data字段建议用json_encode($params, JSON_UNESCAPED_UNICODE)存原始请求参数(过滤敏感字段如卡号) - 避免只存 “status changed from 1 to 2” —— 数字状态码没人记得住,应同时存语义化描述:
"status_changed": {"from": "unpaid", "to": "paid"}
性能和批量操作怎么办?别让日志拖垮下单流程
订单创建本身对延迟敏感,同步写库+写日志容易成为瓶颈,尤其在秒杀或批量导入场景。
立即学习“PHP免费学习笔记(深入)”;
- 日常单笔订单:同步写入
order_log表,用insert()而非save()(避免触发 AR 其他钩子) - 高并发或批量(如导入 5000 单):改用异步,发消息到 RabbitMQ/Kafka,由消费者写日志;或先写入 Redis 队列(
RPUSH order_log_queue ...),再由定时任务批量刷入 DB - 绝对不要在循环里对每个订单都做一次
Yii::info()—— 文件 I/O 会卡死
use yii\db\Query;
// 推荐:轻量同步插入(绕过 AR)
(new Query())->createCommand()->insert('order_log', [
'order_id' => $order->id,
'action' => 'paid',
'data' => json_encode(['amount' => $order->total, 'method' => $paymentMethod], JSON_UNESCAPED_UNICODE),
'user_id' => Yii::$app->user->id,
'ip' => Yii::$app->request->getUserIP(),
'created_at' => date('Y-m-d H:i:s'),
])->execute();
日志不是越全越好,而是要能在出问题时 3 分钟内定位到谁、什么时候、用什么操作、改了什么值。字段设计和写入时机,比用哪个组件重要得多。











