Yii::info() 可直接记录结构化操作日志,但需手动补全 user_id、action、target_id 等关键字段并指定 'operation' 分类,且须在 log 组件中单独配置对应目标,否则日志将混入默认文件难以筛选。

Yii2 中如何用 Yii::info() 记录结构化操作日志
直接写 Yii::info() 是最轻量、最可控的方式,但默认不带用户 ID、IP、请求路径等上下文——得自己补全。关键不是“能不能记”,而是“记下来能不能查”。建议在日志消息里固定包含 user_id、action、target_id 三个字段,方便后续按用户或行为筛选。
示例写法:
Yii::info([
'user_id' => Yii::$app->user->id ?? 'guest',
'action' => 'update_profile',
'target_id' => $model->id,
'ip' => Yii::$app->request->userIP,
'url' => Yii::$app->request->absoluteUrl,
], 'operation');
注意:'operation' 是自定义日志通道名,需在 config/main.php 的 log 组件中单独配置目标文件或数据库路由,否则会混进 app.log 里难以分离。
用行为(Behavior)自动注入用户行为日志到 ActiveRecord 操作
对增删改操作做统一日志埋点,别每个 save() 后手动写 Yii::info()——容易漏、难维护。推荐继承 yii\base\Behavior,监听 afterInsert、afterUpdate、afterDelete 事件。
要点:
- 行为中通过
$event->sender拿到当前模型实例,避免硬编码模型类名 - 必须判断
Yii::$app->user->isGuest,未登录用户不能取id - 不要在行为里调用耗时操作(如 DB 写入),日志应异步落盘;若用文件,确保
fileTarget的maxFileSize足够大,否则频繁 rotate 会导致日志碎片化
典型陷阱:在 afterDelete 里试图访问 $model->attributes,此时数据已从内存清除,要用 $event->sender->oldAttributes。
日志存数据库时,为什么不能直接用 DbTarget 记录操作日志
DbTarget 本身没问题,但默认只存 level、category、message、log_time 四个字段,而操作日志的核心信息(如操作对象类型、ID、参数快照)都在 JSON 字符串里塞进 message,导致无法高效查询。
更务实的做法是:自定义一个 OperationLog ActiveRecord 类,对应表含明确字段:user_id、action、model_class、model_id、data_snapshot(TEXT)、ip、created_at。再配一个轻量 LogService 封装写入逻辑,比改造 DbTarget 更易测试和审计。
性能提醒:高频操作(如点赞、浏览)不适合每条都入库,可先写文件或 Redis 队列,再由后台任务批量归档。
如何防止敏感字段(密码、token)被意外记录进操作日志
日志里出现 "password": "123456" 不是疏忽,是没做脱敏。所有传给 Yii::info() 的数组或对象,必须经过清理。
实操建议:
- 对用户输入的
$_POST或模型attributes,用白名单过滤,只保留业务必需字段(如username、email),其余一律 unset - 用正则匹配并替换常见敏感键名:
password、token、api_key、auth_token,值统一替换成[REDACTED] - 避免记录整个
$model->getErrors(),错误消息可能含数据库字段名或约束细节,暴露表结构
最容易被忽略的一点:日志消息里拼接 SQL 片段(比如 “UPDATE user SET status=1 WHERE id=123”)——看似无害,但一旦开启 query 日志且与操作日志混用,就可能把原始 SQL 带出,含参数值。


















