Monolog 实战指南:PHP 日志的事实标准
Monolog 中文实战指南:Logger 频道、Handler/Processor/Formatter 三层架构、StreamHandler 与 RotatingFileHandler 轮转、八个日志级别、context 结构化字段、JsonFormatter JSON 输出、异常与全局异常处理,以及观测云 DataKit 采集落地方案。
Monolog 是 PHP 日志的事实标准——Laravel、Symfony 等主流框架的默认日志组件。它以 Handler(去向)、Processor(上下文)、Formatter(格式)三层架构提供了完整的日志能力。本文覆盖 Monolog 的核心用法与生产实践,并给出观测云平台接入方案。
核心要点速览
- Logger 按频道(channel)组织:
new Logger("payments"),不同子系统独立频道、独立路由。 - 三层架构:Handler 决定写到哪、Processor 附加公共字段、Formatter 控制输出格式。
- RotatingFileHandler 内建轮转:按天切分、自动清理历史文件,无需 logrotate。
- JsonFormatter 一行切换结构化:JSON 输出后观测云 DataKit 自动解析为字段。
快速上手:Logger 与 StreamHandler
composer require monolog/monolog
<?php
require __DIR__ . "/vendor/autoload.php";
use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger("order-service");
$logger->pushHandler(new StreamHandler(__DIR__ . "/logs/app.log", Level::Info));
$logger->debug("调试细节"); // 不输出(Handler 级别 Info)
$logger->info("订单创建", ["order_id" => 1024]);
$logger->warning("库存不足", ["sku" => "A-01"]);
$logger->error("扣款失败", ["order_id" => 1024, "reason" => "余额不足"]);
输出(默认 LineFormatter):
[2026-08-25T14:55:22.123456+08:00] order-service.INFO: 订单创建 {"order_id":1024} []
第二个参数就是 context 数组——结构化字段的载体,会 JSON 序列化进日志。Monolog 遵循 PSR-3 的八个级别:debug/info/notice/warning/error/critical/alert/emergency。
频道(Logger 名)的价值:payments、security、api 各自建 Logger、挂不同 Handler,实现按子系统分流与独立级别控制。
Handler:日志去哪里
常用 Handler:
| Handler | 用途 |
|---|---|
| StreamHandler | 文件、php://stdout、任意流 |
| RotatingFileHandler | 按天轮转文件 |
| SyslogHandler / SyslogUdpHandler | 系统日志 |
| FilterHandler | 只放行指定级别的日志 |
| FingersCrossedHandler | 缓冲日志,出错时一次性写出(降噪神器) |
use Monolog\Handler\RotatingFileHandler;
// 按天轮转,保留 30 天
$logger->pushHandler(new RotatingFileHandler(__DIR__ . "/logs/app.log", 30, Level::Info));
// 错误单独存档
$logger->pushHandler(new StreamHandler(__DIR__ . "/logs/error.log", Level::Error));
多 Handler 按 push 逆序匹配——更严格的级别限制放在后 push 的位置(栈顶先判断)。容器化部署直接 new StreamHandler("php://stdout")。
Formatter:控制输出格式
use Monolog\Formatter\JsonFormatter;
$handler = new StreamHandler("php://stdout", Level::Info);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
输出每行一个 JSON 对象(message、context、level、channel、datetime)——生产环境标准姿势,采集侧零解析成本。
Processor:自动附加公共字段
$logger->pushProcessor(function ($record) {
$record->extra["hostname"] = gethostname();
$record->extra["request_id"] = $_SERVER["HTTP_X_REQUEST_ID"] ?? null;
return $record;
});
Monolog 内置了 WebProcessor(请求信息)、MemoryUsageProcessor、UidProcessor 等——公共字段一次配置,全频道生效。
异常与全局兜底
try {
$payment->charge($order);
} catch (Throwable $e) {
$logger->error("扣款异常", ["order_id" => $order->id, "exception" => $e]);
throw $e;
}
// 全局兜底:未捕获异常一律记录
set_exception_handler(function (Throwable $e) use ($logger) {
$logger->critical("未捕获异常", ["exception" => $e]);
});
"exception" => $e 是约定写法:JsonFormatter 会把异常的消息、类、堆栈完整序列化进 context。
观测云落地:Monolog 日志采集与分析
- 应用侧:JsonFormatter +
php://stdout(容器)或 RotatingFileHandler(主机)。不建议在应用内用 Socket/HTTP Handler 直推日志后端——网络故障会拖累 PHP-FPM 进程,采集交给 DataKit。 - DataKit 采集:容器 stdout 自动采集;主机
conf.d/log/logging.conf配置logfiles指向日志目录,source: php-app、service标识服务。JSON 自动解析。 - Pipeline 标准化:Monolog 的
level_name(ERROR 等)映射标准status,datetime解析为time;context 字段按需提取顶层化。 - 检索告警:日志查看器按 status/channel/service 过滤,聚类分析归纳异常;监控器对 error 及以上级别设阈值,告警策略路由钉钉/企业微信/飞书。
- 链路关联:Processor 注入 trace_id(OpenTelemetry PHP),与观测云 APM 双向跳转。
- 成本控制:多索引按级别拆保留策略,历史数据转发归档对象存储。
常见问题(FAQ)
Monolog 频道和 Handler 级别怎么配合?
频道是逻辑分组(payments/security),每个频道挂自己的 Handler 栈;Handler 上的 Level 是最低放行级别。想"error 单独一个文件",就给该频道多 push 一个 Level::Error 的 StreamHandler。
FingersCrossedHandler 是什么原理?
它把日志先缓存在内存,一旦触发指定级别(如 Error)就把缓冲的全部上下文一次性写出——平时安静、出事时有完整现场,适合高 QPS 接口的错误排查。注意崩溃时缓冲内容会丢,需与常驻文件 Handler 搭配。
日志写入会影响 PHP-FPM 性能吗?
同步写文件通常很快,但网络类 Handler(TCP/HTTP)会阻塞 worker 进程,高并发下可能打满 FPM 进程池。生产只写本地 stdout/文件,传输交给 DataKit。
CLI 长进程(消费者)退出时日志会丢吗?
Monolog 默认同步写入,无缓冲丢失问题;若用了缓冲类 Handler,注册 pcntl_signal 在 SIGINT/SIGTERM 时优雅退出即可。
系列阅读
- 上一篇:PHP 日志实战
- 下一篇:Laravel 日志实战
- 相关阅读:Log4php 实战 | PHP 日志库对比