PHP 日志系统设计:Monolog 库与日志分级规范

晁铭
晁铭 正式会员正式会员认证极客认证极客
发布于 2026-10-02 01:06 ·5 浏览 ·0 回复

学完这篇,你能在任意 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` 推到能响的地方,光写文件等于没告警。

小结

  1. `composer require monolog/monolog`,注意 2.x/3.x 的级别常量写法差异。
  2. 级别判断看"请求结果对不对",不要一律 error,也不要该 error 的写 warning。
  3. `RotatingFileHandler` 管切割与保留天数,多个 Handler 各带级别实现分流。
  4. 记录异常用 `context['exception'] => $e`,传对象不传字符串。
  5. `ErrorHandler::register($log)` 兜底 PHP 错误与未捕获异常。
  6. 生产纪律:脱敏、不在循环里刷日志、统一时区、日志目录不暴露、ERROR 以上要告警。
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-673.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~