log::write()不能直接写缓存日志,因其默认同步写文件,与缓存驱动无关;缓存日志需通过cache::set()分片暂存+锁机制聚合+app_end钩子刷盘,确保不丢、不阻塞、可控落地。

为什么不能直接用 Log::write() 写缓存日志
ThinkPHP 的 Log::write() 默认走文件写入,哪怕你配置了缓存驱动(如 redis 或 memcached),它也不会自动把日志塞进缓存——这是两个完全独立的通道。缓存日志不是“把日志存到缓存里”,而是“用缓存机制暂存日志内容,再批量落盘”。直接调用 Log::write() 仍会触发同步 I/O,起不到降压效果。
- 日志写入是同步阻塞操作,高并发下容易拖慢响应
-
Log类不提供缓冲区、队列或异步写入接口 - 缓存驱动(如
redis)本身没有日志语义,不能直接替代Logger行为
用 Cache::set() 模拟日志缓冲区要怎么设计
核心思路:把日志条目序列化后,用缓存键做时间分片(如 log:20240520),值存 JSON 数组;每满 N 条或定时触发一次写入文件/数据库。关键不是“存进去”,而是“可控地攒、可控地刷”。
- 缓存键建议带日期维度,避免单 key 过大(
log:20240520而非log:buffer) - 每次
Cache::set()前先Cache::get()取出旧数组,array_push()新日志,再写回——注意并发覆盖风险 - 生产环境务必加锁:
Cache::lock('log:20240520:lock', 10)防止多请求同时读-改-写丢失日志 - 别用
Cache::remember(),它只适合读场景,不解决写聚合问题
Cache::lock('log:20240520:lock', 10, function () {
$logs = Cache::get('log:20240520', []);
$logs[] = ['time' => date('Y-m-d H:i:s'), 'level' => 'info', 'msg' => 'user login'];
Cache::set('log:20240520', $logs, 86400);
});
什么时候该触发日志刷盘?用什么方式最稳
不能等缓存过期,也不能全靠定时脚本——得在应用层主动控制刷盘时机。最稳妥的是“双触发”:达到条数阈值 + 请求结束前强制 flush。ThinkPHP 的 Hook::listen('app_end') 是唯一可靠出口,register_shutdown_function 在异常终止时可能失效。
- 设阈值(如 50 条)后立即刷:避免缓存堆积过多导致内存或 Redis 内存溢出
- 必须在
app_end钩子中调用刷盘逻辑,确保每个请求都有机会落地 - 刷盘失败(如磁盘满、权限不足)不能静默吞掉,要记录到系统级错误日志(
error_log()),否则日志就真丢了 - 避免在中间件或控制器里手动调
flush(),易遗漏或重复执行
Redis 缓存日志的性能和兼容性要注意什么
用 Redis 做日志缓冲看似快,但实际有隐藏成本:序列化开销、网络延迟、单线程瓶颈。尤其当日志量突增(如秒杀日志暴增),Redis 可能成为新瓶颈,甚至拖垮整个缓存服务。
- 不要用
string类型存大日志数组,改用list(LPUSH+LRANGE),避免每次读写都加载整块数据 - Redis 连接池必须配够,ThinkPHP 默认的
redis配置不启用连接池,需手动加'pool_size' => 20 - PHP Redis 扩展版本低于 5.3.2 时,
pipeline在长连接下有内存泄漏,慎用批量写 - 本地开发用
file缓存没问题,但上线必须切到redis或memcached,apcu不跨进程,日志会丢失
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











