日志拆分如果仅仅停留在“按天切分文件”的层面,那远远不够。真正核心的目标是:让不同业务、不同风险级别的日志实现隔离、监控与告警。换句话说,追求的不是日志数量多,而是日志的结构化和可响应性。

先给出几个关键判断:日志按通道分流是基础,SQL与错误日志必须独立存储,CLI命令的日志需要手动触发写入,而真正的闭环,在于将告警链路打通。
按通道精准分流,避免混杂
把所有日志都塞进同一个 file 通道?这虽然常见,但效能有限。更优的实践是:为每个关键模块单独设立通道,例如支付、门禁、能耗各一个。
- 在
config/log.php的channels中,显式声明'pay'、'access'、'energy'等通道。注意,type首字母需大写,比如'File';path要以/结尾,例如runtime_path('log/pay/')。 - 每个通道配置独立的
level。比如'pay' => ['info', 'error']用来记录交易流水,'access' => ['warning', 'error']只收集异常通行事件。 - 调用时使用
Log::channel('pay')->info(...),不声明通道则走默认配置,避免日志归属模糊。
SQL 与错误日志必须独立落地
SQL 日志和错误日志是排查问题的首要线索,不能仅依赖全局的 level 控制。关键点是启用 apart_level。
- 在 file 通道配置里加入
'apart_level' => ['sql', 'error']。 - 需要特别注意:
sql级别只在APP_DEBUG = true时生效,因此开发环境开启,生产环境关闭。但error必须同时列入全局level中,才能写入主日志。 - 确保
runtime/log/error/目录存在,并且 PHP 进程有写入权限,否则error.log不会生成。
CLI 命令日志必须手动落盘
定时任务、自定义命令(如能耗采集脚本)的日志很容易丢失——因为没有请求生命周期来自动触发保存。解决方案很直接:在所有 CLI 命令逻辑末尾,必须加上 Log::sa ve()。
更稳妥的做法是使用 try { ... } finally { Log::sa ve(); },确保即使发生异常退出,也能将已记录的内容刷出。调试时,可以先验证路径可写:is_writable(runtime_path('log/')),然后执行 Log::info('test')->sa ve() 看看是否生成文件。
对接运维告警链路
日志只是原始数据,告警才是真正的闭环动作。重点不是“有日志”,而是“有人看到”。
- 对于
error和critical级别的日志,可以使用 Filebeat 或自研脚本监听runtime/log/error/*.log,提取包含[db_connect_fail]、[mqtt_timeout]等关键标记的行。 - 结构化字段(比如
host=10.0.1.5 code=1040 msg="Too many connections")便于 ELK 提取指标,或者供 Prometheus 抓取。 - 对于门禁连续失败 5 次、能耗突增超基线 2σ 这类事件,更好的做法是直接在 MQTT 订阅服务内判断,然后推送告警队列,而不是依赖日志轮询。
