PHP 日志系统设计:Monolog 库与日志分级规范
学完这篇,你能在任意 PHP 项目里接入 Monolog,并按 PSR-3 的八级标准判断每条日志该用哪个级别,做到按天切割、异常带堆栈、线上不打无效日志。
第一步:安装 Monolog
用 Composer 装,别手动拷源码:
composer require monolog/monolog
注意:Monolog 2.x 支持 PHP 7.2+,Monolog 3.x 要求 PHP 8.1+。PHP 7.4 的项目装到的就是 2.x,两者的级别常量写法不同(下面会讲),照抄网上的代码前先看版本。
第二步:最小可用配置
<?php
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Level;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/app.log', Level::Debug));
$log->info('用户登录成功', ['uid' => 12, 'ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
Monolog 2.x 里写成 `Logger::DEBUG`,3.x 用 `Level::Debug`,两者不能混。
注意:`logs` 目录要先建好,且 php-fpm 的运行用户(宝塔上通常是 `www`)得有写权限,否则第一次写日志就报错,而报错本身也没地方记。
第三步:日志分级规范(PSR-3 八级)
级别不是装饰,是"谁在半夜被叫起来"的分工:
| 级别 | 什么时候用 | 生产是否记 |
|---|---|---|
| DEBUG | 变量值、SQL、流程埋点 | 否(默认关) |
| INFO | 正常业务里程碑:登录、下单、支付成功、发帖 | 是 |
| NOTICE | 正常但特殊:走了降级方案、配置缺失已回退默认值 | 是 |
| WARNING | 可自愈的异常:缓存写失败、第三方接口超时后重试成功 | 是 |
| ERROR | 本次操作失败,需人工看:回调验签失败、业务异常导致请求 500 | 是 |
| CRITICAL | 核心组件不可用:数据库连不上、Redis 挂了 | 是,并告警 |
| ALERT | 必须立刻处理:磁盘满、证书过期、疑似被刷 | 是,并告警 |
| EMERGENCY | 整个系统不可用 | 是,并告警 |
判断口诀:"这次请求的结果对不对?" 对且正常就是 INFO,对但绕了路是 WARNING/NOTICE,不对是 ERROR,连系统都站不住了才往上走三级。最忌讳两件事:所有异常一律 `error`(告警疲劳),以及把业务失败打成 `warning`(真出事没人看见)。
第四步:切割文件 + 按级别分流
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\IntrospectionProcessor;
$log = new Logger('app');
// 全量日志,Info 起,保留 14 天
$log->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', 14, Level::Info));
// 错误单独一份,方便盯
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/error.log', Level::Error));
$log->pushProcessor(new WebProcessor()); // 自动带 URL、IP、方法
$log->pushProcessor(new IntrospectionProcessor()); // 自动带文件、行号、类名
Handler 的级别是"最低门槛":`error.log` 里只会出现 Error 及以上。这样排查时先看 error.log,要还原现场再翻 app.log。
注意:Monolog 3.x 删掉了 `FilterHandler`,老教程里用它做分流的代码在 3.x 上会直接报类不存在,用多个 Handler 各自带级别就行。
第五步:异常一定要带对象
try {
$pay->charge($order);
} catch (\Throwable $e) {
$log->error('支付失败', [
'order_no' => $order->no,
'user_id' => $uid,
'exception' => $e, // 传对象,不要传字符串
]);
throw $e;
}
`context['exception']` 是 Monolog 的约定键,传 Throwable 它会自动展开堆栈、类名、文件行号。写成 `['exception' => $e->getMessage()]` 等于自断线索。
再补一层全局兜底,把 PHP 错误和未捕获异常都收进日志:
use Monolog\ErrorHandler;
ErrorHandler::register($log);
第六步:生产环境的几条纪律
- 不记敏感信息:密码、Token、完整手机号、身份证一律脱敏后再入 context。
- 循环里别打日志:批处理十万条数据每行都打,磁盘一天就满。改成聚合统计,或按 1% 采样。
- 统一时区:先 `date_default_timezone_set('Asia/Shanghai')`,或者全用 UTC,别一半本地一半 UTC。
- 日志目录不要对公网可访问,`logs` 放在 Web 根目录之外最省心。
- 接告警:ERROR 及以上用 Monolog 的 `SyslogHandler` 或 `NativeMailHandler` 推到能响的地方,光写文件等于没告警。
小结
- `composer require monolog/monolog`,注意 2.x/3.x 的级别常量写法差异。
- 级别判断看"请求结果对不对",不要一律 error,也不要该 error 的写 warning。
- `RotatingFileHandler` 管切割与保留天数,多个 Handler 各带级别实现分流。
- 记录异常用 `context['exception'] => $e`,传对象不传字符串。
- `ErrorHandler::register($log)` 兜底 PHP 错误与未捕获异常。
- 生产纪律:脱敏、不在循环里刷日志、统一时区、日志目录不暴露、ERROR 以上要告警。
转载请注明出处,版权归原作者所有。
正式会员
认证极客






