Log::write() 直接记录 $request->param() 会导致密码等敏感信息明文泄露;应使用 maskSensitiveData() 预处理或自定义日志处理器,在序列化前递归脱敏,键名大小写不敏感,统一替换为 [REDACTED]。

Log::write() 直接记录 $request->param() 就等于泄露密码
ThinkPHP 的 Log::write() 不做任何脱敏,它原样把数组转成字符串写进日志。一旦你在登录接口里写了 Log::write($request->param(), 'info'),日志里立刻出现 "password": "123456" 这种明文——不是“可能”,是必然发生。
这不是框架缺陷,是设计选择:它默认信任你已处理好敏感字段。真实踩坑场景包括支付回调(含 access_token)、用户资料提交(含 id_card、bank_card)、第三方授权(含 refresh_token)。
- 别用
unset($params['password'])临时补救:嵌套结构如['user' => ['pwd' => 'xxx']]会被漏掉 -
$request->param()和input()行为一致,返回原始数据,不自动清洗 - 日志文件若被导出、误传或遭提权访问,等同于批量泄露用户凭证
用 Log::record() + 自定义脱敏函数替代全局 write
最轻量、可控性最强的做法:在调用日志前手动过滤,而不是改底层驱动。所有控制器/中间件中,把 Log::write($data, $level) 替换为 Log::record() 并传入处理后数据。
示例代码(可直接复用):
立即学习“PHP免费学习笔记(深入)”;
function maskSensitiveData($data) {
$sensitiveKeys = ['password', 'pwd', 'token', 'access_token', 'id_card', 'bank_card', 'mobile', 'phone', 'email', 'card_no'];
if (!is_array($data)) return $data;
foreach ($data as $k => $v) {
if (in_array(strtolower($k), $sensitiveKeys)) {
$data[$k] = '[REDACTED]';
} elseif (is_array($v)) {
$data[$k] = maskSensitiveData($v);
}
}
return $data;
}
$params = $request->param();
maskSensitiveData($params);
Log::record('请求参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE), 'info');
- 键名匹配必须大小写不敏感,因为不同接口可能传
PWD或Token - 值统一替换为
[REDACTED]比***更明确,避免被误认为有效数据 - 不要在
json_encode()后再正则替换字符串——JSON 结构会被破坏,且无法精准定位字段
想全局生效?注册自定义日志处理器比改 File 驱动更安全
如果项目有大量接口需统一脱敏,推荐继承 think\log\driver\File 并重写 save() 方法,但注意:别直接修改 vendor 里的源码,应在 app/common.php 或日志配置中动态注册。
关键点在于拦截时机——必须在数据序列化为字符串、写入磁盘前完成过滤:
- 处理器接收的
$data是未格式化的原始数组,此时递归过滤成本最低 - 用
array_walk_recursive()替代手写递归,减少栈溢出风险(尤其深层嵌套时) - 避免在处理器里做耗时操作(如 DB 查询、远程请求),否则拖慢所有日志写入
- 若使用 Monolog(如 TP7+ 或自定义集成),应实现
Monolog\Processor\ProcessorInterface,对$record['context']处理
订单/回调类日志要额外注意字段白名单和结构差异
订单日志常混入数据库原始行、关联模型、甚至 SQL 绑定参数,不能只依赖通用脱敏函数。比如 $order->toArray() 可能包含 user.phone、address.detail,而这些字段名不在顶层 ['mobile'] 名单里。
必须按实际结构定义字段白名单,并区分处理来源:
- $_POST / $_GET 输入:重点过滤
phone、id_card、full_address、real_name - ORM 模型输出:检查关联模型是否也定义了
getMobileAttr,否则需显式用withAttr() - SQL 参数日志(如 PDO 日志):对
$params数组逐项检测,而非对拼接后的 SQL 字符串正则替换 - 地址字段脱敏建议保留到区级,门牌号打星:
preg_replace('/(?
真正难的不是写脱敏逻辑,而是确认每一条日志的源头结构——同一份 $data 在登录、下单、回调三个场景下,嵌套层级和字段命名可能完全不同。



















