monolog捕获未处理异常需显式调用registerexceptionhandler和registererrorhandler;仅pushhandler无效,且须注意php 8.0+ fatalerror需配合registererrorhandler与e_error触发。

Monolog捕获未处理异常要配registerExceptionHandler
默认情况下,Monolog不会自动记录PHP未捕获的异常或致命错误。必须显式调用registerExceptionHandler和registerErrorHandler才能让日志写入生效。
- 只调用
pushHandler不等于能捕获全局异常 -
Logger::registerExceptionHandler($logger)会把set_exception_handler指向Monolog内部处理器 - 若项目已用
set_exception_handler自定义过,需先保存原回调再链式调用,否则覆盖后原有逻辑丢失 - 注意:PHP 8.0+ 的
FatalError需靠registerErrorHandler配合E_ERROR触发,不是所有致命错误都自动被捕获
Handler选错会导致日志写不进文件或丢数据
写文件最常用的是StreamHandler,但它的行为受参数影响极大:
-
StreamHandler('php://stderr', Logger::ERROR)——只在CLI下可见,Web服务器(如Nginx+PHP-FPM)中会被丢弃 -
StreamHandler('/var/log/app.log', Logger::DEBUG)——确保目录可写,且注意SELinux或AppArmor可能拦截写入 - 高并发场景慎用
StreamHandler,它默认不加锁;频繁写入可能造成日志行错乱,此时应换RotatingFileHandler并设$maxFiles = 0禁用轮转,或改用SyslogHandler - 若用
RotatingFileHandler,注意$filename不能带日期占位符(如app-%s.log),它内部会自动替换
记录异常时别直接传$e->getMessage()
这样只留了错误信息,丢了堆栈、代码位置、上下文——对排查毫无帮助。
- 正确做法是把整个
Exception对象传给error():$logger->error('API failed', ['exception' => $e]) - Monolog会自动调用
$e->__toString(),完整输出堆栈跟踪 - 如果想额外加请求ID、用户ID等上下文,必须作为第二个参数的数组键值传入,不能拼在消息字符串里
- 避免
$logger->error($e->getMessage() . ' in ' . $e->getFile())——这绕过了Monolog的上下文机制,也丢失了trace
Laravel/Symfony里别重复初始化Monolog实例
框架本身已预配置好LoggerInterface,手动new Logger()会绕过容器管理,导致配置失效、Handler重复注册、甚至内存泄漏。
- Laravel中直接
Log::error('msg', ['exception' => $e])即可,底层就是Monolog - Symfony中用
$this->logger->error('msg', ['exception' => $e])(依赖注入LoggerInterface) - 若需定制Handler,应在框架配置文件中修改(如Laravel的
config/logging.php),而不是在业务代码里重建Logger - 特别注意测试环境:有些Mock会替换掉Logger,但没转发
registerExceptionHandler,导致测试时异常不记日志










