ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Monolog 三大核心组件实战指南:Handler、Formatter 与 Processor 完全解析

Monolog 三大核心组件实战指南:Handler、Formatter 与 Processor 完全解析 Monolog 三大核心组件实战指南Handler、Formatter 与 Processor 完全解析【免费下载链接】monologSends your logs to files, sockets, inboxes, databases and various web services项目地址: https://gitcode.com/gh_mirrors/mo/monolog本文是 Monolog 的深度使用指南聚焦日志处理链路中最核心的三个组件负责写到哪里的 Handler处理器、负责写成什么格式的 Formatter格式化器、以及负责写前加工的 Processor处理器/增强器。你将掌握文件、syslog、邮件、Slack、数据库等数十种日志输出目标的选择与配置学会用 FingersCrossed、Deduplication、Buffer 等包装型 Handler 构造复杂日志策略并理解 Formatter 与 Processor 在 Logger → Handler → 目标这条流水线中的真实执行顺序与调用时机。本文所有结论均可在本仓库源码src/Monolog/ 目录中找到对应实现。从一次日志调用说起三大组件的协作链路在展开逐个组件的清单之前先建立整体认知。当你调用$logger-info(...)时Monolog 内部会依次经历Logger 级别处理器Processors先按注册顺序对LogRecord做增补或改写如补充调用行号、内存占用、请求 URLHandler 栈Logger将记录依次交给通过pushHandler()注册的各个 HandlerLogger::pushHandler栈按后进先出顺序消费Handler 自身逻辑每个 Handler 先通过isHandling()判断级别是否达标再执行 Handler 级 Processor、用 Formatter 格式化记录、最后write()写出见 AbstractProcessingHandler::handle 的标准流程Bubble 冒泡若 Handler 的bubble为true记录会继续沿栈向上传递最终被更外层的 Handler 接收若为false则在此处终止。级别过滤由 AbstractHandler 统一实现构造函数的$level参数默认Level::Debug定义该 Handler 的最低触发级别isHandling()通过$record-level-value $this-level-value比较。8 个标准级别及其内部数值定义在 LevelDebug(100)、Info(200)、Notice(250)、Warning(300)、Error(400)、Critical(500)、Alert(550)、Emergency(600)。Handlers日志要写到哪里Handler 决定记录的最终去向。文档按用途把 Handler 分为六类下面逐一展开并补充构造参数与典型使用场景。文件与 syslogHandler用途关键参数/说明StreamHandler写入任意 PHP 流最常用于日志文件$stream可为文件路径或资源$filePermission默认 0644 仅所有者读写、$useLocking、$fileOpenMode默认a追加RotatingFileHandler按天生成一个日志文件并清理过期文件$maxFiles0 表示不限制数量、$filenameFormat默认{filename}-{date}、$dateFormat默认FILE_PER_DAY Y-m-dSyslogHandler写入系统 syslog需指定$ident程序标识与$facilityErrorLogHandler写入 PHP 的error_log()可指定消息类型如OPERATING_SYSTEM、SAPI、WEB与消息级别FrankenPhpHandler使用 FrankenPHP 的frankenphp_log()记录仅能在 FrankenPHP SAPI 下运行ProcessHandler将记录写入指定进程的 STDIN需提供进程命令可指定cwd与env文件场景的典型写法use Monolog\Logger; use Monolog\Handler\StreamHandler; $log new Logger(app); $log-pushHandler(new StreamHandler(__DIR__ . /logs/app.log, Logger::WARNING));StreamHandler的实现有不少值得了解的细节StreamHandler.php首次写入时会自动mkdir()创建缺失目录createDir使用0777通过fopen($url, $this-fileOpenMode)打开文件默认按memory_limit的 10%至少 100KB最多 10MB计算单次流写入块大小streamChunkSize并在streamWrite()中用循环fwrite()保证非阻塞流上整条记录完整写入。RotatingFileHandler是StreamHandler的子类其轮转机制RotatingFileHandler.php值得专门说明构造时通过getTimedFilename()生成带日期的文件名并计算下一次轮转时间nextRotation每次write()时若发现当前日期文件不存在或nextRotation已过期则触发close()→rotate()rotate()更新文件名为新日期并利用findRotatedFiles()找出历史文件按名称倒序排序后删除超出$maxFiles数量的旧文件文件名格式占位符为{filename}与{date}{date}是必须的否则抛InvalidArgumentException支持按小时Y-m-d-H、按天Y-m-d、按月Y-m、按年Y四种粒度对应类的FILE_PER_*常量日期分隔符可用/、_、.替换-源码注释明确该机制只是quick and dirty的快速方案高流量生产环境应改用系统级 logrotate。use Monolog\Handler\RotatingFileHandler; // 每天一个文件最多保留 30 个文件名形如 app-2026-09-19.log $log-pushHandler(new RotatingFileHandler(__DIR__ . /logs/app.log, 30, Logger::INFO));告警与邮件Handler用途NativeMailerHandler用 PHPmail()发送邮件SymfonyMailerHandler用symfony/mailer实例发送邮件Monolog 3 推荐方式PushoverHandler通过 Pushover API 发移动端推送SlackWebhookHandler通过 Slack Incoming Webhooks 发送到 Slack 频道简单配置SlackHandler通过 Slack API 发送配置复杂支持更多选项SendGridHandler通过 SendGrid API 发送邮件MandrillHandler通过 Mandrill API Swift_Message发送邮件FleepHookHandler通过 Webhook 发送到 Fleep 会话IFTTTHandler触发 IFTTT Maker 事件携带 channel、level name 与 messageTelegramBotHandler发送到 Telegram botHipChatHandler已废弃Monolog 2.0 起移除请改用 Slack 系列 HandlerSwiftMailerHandler已废弃Monolog 3.0 起移除请改用 SymfonyMailerHandlerSlack 的两种 Handler 分工明确SlackWebhookHandler只需一个 webhook URL 即可投递适合快速接入SlackHandler走完整 Slack API需要 token 与更多参数频道、用户名、图标等适合定制。两类都通过 Slack/SlackRecord.php 组装 Slack 消息格式可配置附件与上下文展示。特定服务器与网络日志Handler用途依赖SocketHandler写入 UNIX/TCP socket无详见 sockets.mdAmqpHandler写入 AMQP 兼容服务器php-amqp 扩展(1.0) 或 php-amqplib 库GelfHandler写入 Graylog2graylog2/gelf-php 包ZendMonitorHandler写入 Zend Server 的 Zend MonitorZend Server 环境NewRelicHandler写入 NewRelic 应用newrelic 扩展LogglyHandler写入 Loggly账号 tokenRollbarHandler写入 RollbarRollbar 客户端SyslogUdpHandler通过 UDP 写入远程 Syslogdrsyslog 等远程主机与端口见 SyslogUdp/UdpSocket.phpLogEntriesHandler写入 LogEntries账号 tokenInsightOpsHandler写入 InsightOpsRapid7账号 tokenLogmaticHandler写入 Logmatic账号 tokenSqsHandler写入 AWS SQS 队列aws-sdk-phpRavenHandler写入 Sentry已废弃Monolog 2.0 起移除请改用 sentry/sentry 2.x 的Sentry\Monolog\HandlerSocketHandler是网络日志的基础设施它对内管理连接建立、超时与重连SyslogUdpHandler、InsightOpsHandler、LogglyHandler等均可构建于其上的封装。若想用 TCP/UNIX socket 对接自建日志服务doc/sockets.md 提供了完整示例。开发调试Handler用途FirePHPHandler在 FireBug 中以内联 console 消息展示ChromePHPHandler在 Chrome 中以内联 console 消息展示BrowserConsoleHandler无需浏览器扩展直接发送到浏览器 JS console兼容大多数支持 console API 的浏览器BrowserConsoleHandler是当前最实用的开发期方案它不需要安装任何浏览器扩展通过setHeader()与getHeaders()把日志编码进响应头前端读取后打印到 DevTools console调试体验最接近原生。数据库Handler用途依赖RedisHandler通过 RPUSH 写入 Redis keyredis 扩展/客户端RedisPubSubHandler通过 PUBLISH 发布到 Redis 频道redis 扩展/客户端MongoDBHandler写入 MongoDBmongodb 扩展连接CouchDBHandler写入 CouchDBHTTP 客户端DoctrineCouchDBHandler通过 Doctrine CouchDB ODM 写入 CouchDBdoctrine 相关包ElasticaHandler写入 Elasticsearchruflin/elasticaElasticsearchHandler直接写入 Elasticsearchelasticsearch 客户端DynamoDbHandler写入 DynamoDB 表aws-sdk-php注意RedisHandler与RedisPubSubHandler的差异前者把记录追加进 key 对应的 listRPUSH适合事后批量消费后者把记录实时发布到频道PUBLISH适合下游即时订阅。ElasticsearchHandler与ElasticaHandler分别面向官方客户端与 Elastica 抽象层对应 ElasticsearchFormatter 与 ElasticaFormatter。包装型 / 特殊 Handler这是 Monolog 最具设计巧思的一类它们不直接写日志而是包装另一个 Handler改变记录的流向时机与数量。FingersCrossedHandler十字手指缓冲器接收一个被包装 Handler先缓冲所有级别的记录直到某条记录达到触发级别默认Level::Warning可传ActivationStrategyInterface自定义策略内置 ErrorLevelActivationStrategy 与 ChannelLevelActivationStrategy此时才把缓冲的全部记录含低级别一次性交给被包装 Handler。效果平时日志文件干干净净一旦出错则能看到包含 debug/info 在内的完整上下文。源码实现FingersCrossedHandler.php缓冲期间每条记录入$buffer超出$bufferSize时array_shift丢弃最旧条目策略被触发后调用activate()→handleBatch($this-buffer)。构造函数还支持$stopBuffering触发后是否停止缓冲默认 true与$passthruLevel即使未触发close 时也把不低于该级别的记录冲刷给被包装 Handler。注意放在它后面的 Handler 级 Processor 只会在触发时执行一次所有记录会共享触发点的 file/line 数据。use Monolog\Handler\FingersCrossedHandler; // 平时不写一旦出现 Error 及以上把整个请求周期的 debug/info 都写进文件 $log-pushHandler(new FingersCrossedHandler( new StreamHandler(__DIR__ . /logs/app.log), activationStrategy: Level::Error ));DeduplicationHandler去重器包装另一个 Handler在请求结束或显式flush()前累积记录然后只把在给定时间窗口内唯一的记录交付出去默认窗口 60 秒重复记录直接丢弃。典型场景数据库不可达导致每个请求都失败、每次都发告警邮件时用它把通知量压到可控水平。可配合 Deduplication/Fingerprint 存储 自定义去重存储如 Redis、文件等。WhatFailureGroupHandler继承 GroupHandler但忽略每个子 Handler 抛出的异常——某个远程 TCP 连接挂了不会让整个应用崩溃其余 Handler 照常工作。FallbackGroupHandler同样继承 GroupHandler但逐个尝试子 Handler直到某个成功处理且不抛异常为止。适合主存储失败后降级到备用存储的容错场景。BufferHandler缓冲所有记录直到close()已通过register_shutdown_function注册Fatal error 时也会执行才调用被包装 Handler 的handleBatch()一次性批量交付。最典型用途是一封邮件汇总所有记录而不是每条记录一封邮件。$bufferLimit超过后默认丢弃最旧条目设置$flushOnOverflow true则改为溢满即冲刷BufferHandler.php。GroupHandler把每条记录同时发给配置的所有子 Handler是一份日志多路输出的基础设施。FilterHandler只放行指定级别的记录到被包装 Handler相当于在管道上加了一道精细的级别闸门比 Handler 级$level更灵活可指定多个级别。SamplingHandler按概率抽样记录如只存 10% 的 debug 日志降低海量日志的存储成本。NoopHandler对任何记录都不做事但不阻断后续 Handler 继续处理。适合测试或在覆盖配置时临时停用某个 Handler。NullHandler把所有它能处理的记录直接丢弃。放在 Handler 栈顶端可临时禁用整个栈与 NoopHandler 的只禁自己形成对照。PsrHandler把记录转发给已有的 PSR-3 logger实现新旧日志体系的桥接。TestHandler测试专用记录所有送入的记录并提供访问器读取断言如hasInfoRecords()、hasRecordThatContains()配套测试见 TestHandlerTest。HandlerWrapper一个简单的 Handler 包装基类继承它即可快速编写自己的包装 Handler。OverflowHandler缓冲所有消息直到某级别消息数达到阈值之后把所有消息交给被包装 Handler。适合批处理场景——只关心成批的显著失败而不是零星的单次错误事件。LogMonsterHandler饥饿怪兽开关一个死亡开关dead-mans switch包装器。它监视经过的日志记录期望在进程结束前至少被喂入$hunger条记录若直到close()仍饥饿且未通过feed()手动喂食怪兽会愤怒地以配置的$angerLevel向被包装 Handler 发出一条抱怨记录。用于捕捉在完成预期工作前就挂掉的 cron 任务或 worker或确保每条代码路径至少产生日志。启用$wantsContextChips后只有携带 context 的记录才算真正的食物。Formatters记录写成什么格式Handler 在写出前会把记录交给 Formatter 完成序列化。绝大多数输出型 Handler 默认使用LineFormatter也可通过setFormatter()替换。核心 Formatter 速查Formatter输出格式典型搭配LineFormatter单行字符串文件、syslog 等几乎所有 HandlerHtmlFormatter人类可读的 HTML 表格邮件类 HandlerNormalizerFormatter将对象/资源归一化为字符串/数组各 Formatter 的基类ScalarFormatter标量组成的关联数组数据库类 HandlerJsonFormatterJSON 字符串数据库、远程 API、日志服务WildfireFormatterWildfire/FirePHP 协议FirePHPHandlerChromePHPFormatterChromePHP 格式ChromePHPHandlerGelfMessageFormatterGelf 消息实例GelfHandlerLogstashFormatterLogstash 事件 JSONSocketHandler 等 Logstash 输入端ElasticaFormatterElastica\Document 对象ElasticaHandlerElasticsearchFormatter添加 index/type 键ElasticsearchHandlerLogglyFormatterLoggly 消息LogglyHandlerMongoDBFormatter递归转数组DateTime 转 MongoDateMongoDBHandlerLogmaticFormatterLogmatic 消息LogmaticHandlerFluentdFormatterFluentd 日志SocketHandlerGoogleCloudLoggingFormatterGoogle Cloud Logging 格式类似 JsonFormatter 的微调版SyslogFormatterRFC 5424 / syslog 格式产出可被 lnav 等工具消费的 syslog 风格文件RedactingFormatter包装其他 Formatter 做脱敏任意输出型 HandlerLineFormatter 的常用配置LineFormatter.php默认格式常量SIMPLE_FORMAT [%datetime%] %channel%.%level_name%: %message% %context% %extra%\n占位符可自定义。构造参数与链式方法包括$format/$dateFormat自定义行格式与时间戳格式allowInlineLineBreaks()允许记录内换行默认 falseignoreEmptyContextAndExtra()context/extra 为空时不输出它们让日志行更干净includeStacktraces()附带异常堆栈开启后自动允许内联换行setMaxLevelNameLength()截断级别名如设 3 则 INFO → INFsetBasePath()从堆栈文件路径中隐藏项目基路径缩短日志indentStacktraces()为堆栈设置缩进与主记录区隔。JsonFormatter 的批量模式JsonFormatter.phpformatBatch()支持两种模式——BATCH_MODE_JSON默认多条记录编码为 JSON 数组与BATCH_MODE_NEWLINES每条记录独立成行的 JSON Lines后者更契合 Logstash、Fluentd 等逐行消费的管道。appendNewline控制每条记录末尾是否追加换行。RedactingFormatter 脱敏RedactingFormatter.php这是近期版本新增的安全组件包装另一个 Formatter 以屏蔽敏感数据三层防护委托前掩掉 context/extra 中键名匹配配置如password、token、authorization大小写不敏感、递归的取值委托后从输出中剔除上述密钥的值——这能捕获PsrLogMessageProcessor插值进 message 的密文等掩码覆盖不到的副本对输出运行可配置的正则模式捕获对象归一化后才现形的密文如堆栈里的 token。若某条正则无法应用记录被丢弃而不是泄露明文。此外对构造函数参数带#[SensitiveParameter]属性的对象属性也会脱敏可用$redactSensitiveParameters关闭。作为 Formatter它保证在所有 Processor 之后执行始终看到最终完整记录。内置常量TOKEN_PATTERN{\b(?:[a-z]_)*[a-zA-Z0-9]{30,}\b}用于匹配 30 位长 token。用法use Monolog\Formatter\LineFormatter; use Monolog\Formatter\RedactingFormatter; // 在标准单行格式上叠加脱敏 $formatter new RedactingFormatter( new LineFormatter(), patterns: [RedactingFormatter::TOKEN_PATTERN] ); $streamHandler-setFormatter($formatter);Processors写前加工记录Processor 是在 Handler 格式化前对记录做增补/改写的可调用对象通过Logger::pushProcessor()Logger.php或 Handler 级注册。文档列出的内置 ProcessorProcessor往记录里加什么PsrLogMessageProcessor按 PSR-3 规则把{foo}替换为$context[foo]的值LoadAverageProcessor当前系统负载均值ClosureContextProcessor允许把 Closure 放进 context在记录真正被使用时才调用生成上下文延迟求值IntrospectionProcessor日志调用来源的 file/line/class/methodWebProcessor当前请求 URI、请求方法、客户端 IPMemoryUsageProcessor当前内存占用MemoryPeakUsageProcessor峰值内存占用ProcessIdProcessor进程 IDUidProcessor唯一标识符可用于关联同一次请求的多条日志GitProcessor当前 git 分支与 commitMercurialProcessor当前 hg 分支与 commitTagProcessor预定义的标签数组HostnameProcessor当前主机名内存类 Processor 的基类 MemoryProcessor 提供$realUsage开关默认 true使用memory_get_usage(true)取真实占用与$useFormatting开关是否格式化为易读单位。IntrospectionProcessor 的坑IntrospectionProcessor.php它通过debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)回溯调用来源跳过Monolog\命名空间与call_user_func/call_user_func_array。文档特别提醒如果它被挂在 FingersCrossedHandler 后面的 Handler 上只会被调用一次所有记录都会携带触发点的那组 file/line/class 数据。若需要每条记录都有自己的来源信息应把它注册到 Logger 级别或使用处理器级别的 skip 参数配合。PsrLogMessageProcessor 的插值规则它实现 PSR-3 的消息占位符替换——User {user} logged in配合[user alice]会输出User alice logged in。这是保证 Monolog 与 PSR-3 兼容日志体系互操作的关键 Processor。Third Party Packages生态扩展内置组件之外社区维护的第三方 Handler、Formatter、Processor 收录在项目的 wiki 的 Third-Party Packages 页面。如果你发布了自己的组件也可以在那里登记。自行实现时通常只需实现 HandlerInterface、FormatterInterface、ProcessorInterface 三个接口之一或继承 HandlerWrapper、AbstractProcessingHandler 等便捷基类——绝大多数情况只需实现write(LogRecord $record)一个方法即可被 Logger 栈接纳。组合实战构造一条生产级日志流水线综合以上组件一个典型的生产配置可以是文件记录全部级别LineFormatter IntrospectionProcessor UidProcessorRotatingFileHandler 按天轮转告警路径用 FingersCrossedHandler 包住 SlackWebhookHandler出现 Error 才整段推送再外套 DeduplicationHandler60 秒内同一条错误只通知一次开发环境则额外加 BrowserConsoleHandler。所有远程目标外套 WhatFailureGroupHandler确保日志服务故障不影响业务。use Monolog\Logger; use Monolog\Level; use Monolog\Handler\StreamHandler; use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\FingersCrossedHandler; use Monolog\Handler\DeduplicationHandler; use Monolog\Handler\SlackWebhookHandler; use Monolog\Formatter\LineFormatter; use Monolog\Processor\UidProcessor; use Monolog\Processor\IntrospectionProcessor; $log new Logger(app); $log-pushProcessor(new UidProcessor()); $log-pushProcessor(new IntrospectionProcessor()); // 1) 全量文件日志按天轮转 自定义格式 $log-pushHandler(new RotatingFileHandler(__DIR__ . /logs/app.log, 30, Level::Debug)); $log-getHandlers()[0]-setFormatter(new LineFormatter( [%datetime%] %channel%.%level_name%: %message% %context% %extra%\n )); // 2) 告警路径Deduplication 包 FingersCrossed 包 Slack出错才整段推送且去重 $slack new SlackWebhookHandler(https://hooks.slack.com/services/xxx, #alerts, monolog); $log-pushHandler(new DeduplicationHandler( new FingersCrossedHandler($slack, activationStrategy: Level::Error) ));每条记录经过这条流水线时先由 Logger 级 Processor 补充 uid 与调用来源再进入各 Handler文件 Handler 即时格式化写出告警链路平时只缓冲错误出现时一次性推送完整上下文且同窗期内去重。这套组合正是 Monolog只在你需要时给你全部信息设计哲学的完整落地。延伸阅读01-usage.mdLogger 基础用法与日志级别03-utilities.mdErrorHandler、Utils、Level 等工具类04-extending.md编写自定义 Handler、Formatter、Processor 的完整指引sockets.mdSocketHandler 网络日志示例message-structure.mdLogRecord 消息结构说明。【免费下载链接】monologSends your logs to files, sockets, inboxes, databases and various web services项目地址: https://gitcode.com/gh_mirrors/mo/monolog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表