被问性能后,我封装了这个 PHP 错误上报工具

2025-12-12 0 807

最近我把自己常用的一套错误上报逻辑封装成了一个 Composer 包,叫 hejunjie/lazylog
功能很简单也很实用:安全地写本地日志 + 把异常信息上报到远端(支持同步/异步) 。本文讲讲为什么我要做这个库、实现思路、在不同运行环境下如何选择(以及我推荐的优化方案)。


起因:为啥要做这个工具

先讲个背景。之前我写了一个 Go 项目 —— oh-shit-logger,目标是把不同语言、不同项目里的错误集中收集到一个地方。Go 做服务天然快、部署也简单: GitHub Actions自动打包,我只要把包丢主机上一键启动就好了。

但上线后有朋友问:

这是个很合理的问题。网络 I/O 的确有成本,但异常本身在多数系统里不是那种持续不断、高频率的事件(如果异常多到经常并发,那系统可能已经在出问题了)。

与其空谈“会不会慢”,我更愿意把常用做法封装一下,直接给出一个实战好用的方案——于是 hejunjie/lazylog 诞生了。


思路概览:伪异步 + 可回退的同步

lazylog 的核心思路很简单:

  • 本地写日志:线程安全、支持按行数/大小自动切分,长期运行不会把单个日志文件撑爆。
  • 远程上报:提供两种方式:
    • 异步上报(伪异步) :通过 proc_open()exec() fork 出一个 PHP CLI 子进程来发送 HTTP POST,不阻塞主进程。适用于 PHP-FPM、一次性 CLI 脚本等短生命周期环境。
    • 同步上报:直接在当前进程做一个带超时的 HTTP POST,适合常驻内存框架(Webman、Swoole、RoadRunner 等)或需要保证上报结果的场景。

我把这些行为都封装在一个很小的包里:composer require hejunjie/lazylog,在任何项目里都能快速复用。


异步实现细节:为什么是“伪异步”?

PHP 没有内置线程(除非用扩展),但我们可以通过子进程实现“非阻塞式”的上报:

  • proc_open():启动子进程并可拿到 stdin/stdout/stderr,控制能力强;但会创建管道资源,需要注意关闭管道以免资源泄露。
  • exec():简单粗暴,把命令交给 shell 去做 fork,父进程可立即返回(命令后面加 &)。语义上更轻量,但控制能力弱。

两者的本质都是 fork 一个新进程去跑 PHP CLI,然后子进程读取临时文件(或者接收传参)、发 POST、删临时文件、退出。主进程不会等子进程走完就返回给用户,所以对用户体验几乎零影响。

优点:实现简单、跨平台、即插即用;适合错误信息本身不高频的场景。
缺点:在“极高并发”场景下(比如每秒上千条错误)会比较吃资源,子进程启动和网络请求仍然有成本。


常驻内存框架(Webman/Swoole)该怎么办?

这是个重要的实践问题:在常驻内存框架中,我更推荐用同步上报或队列,而不是频繁 fork 子进程。

原因很直观:

  • 常驻框架的 Worker 是长期存在的,fork 子进程会带来额外的资源管理问题(僵尸进程、内存增长、文件描述符等)。
  • 同步上报虽然会阻塞当前 Worker,但只影响当前 Worker,不会像在传统短生命周期中影响整个请求模型。对于大多数低频异常而言,这个阻塞代价是可以接受的。
  • 更稳妥的做法是:把异常先格式化成数组,投递到队列,由专门的队列 worker 来异步上报。这样既避免了直接 fork,又能在不影响主流程的情况下批量/可靠地上报。

我在包里同时提供了 reportSync()(同步上报)和 reportAsync()(伪异步上报),并提供 Logger::formatThrowable() 帮你把异常转成纯数据结构,方便推队列或序列化。


实际使用示例

本地写日志

Logger::write(\'/var/logs\', \'error/app.log\', \'Task Failed\', [\'msg\' => \'something wrong\']);

短生命周期场景(异步上报)

try {
  // ...
} catch (Throwable $e) {
  Logger::reportAsync($e, \'https://your-collector/collect\', \'my-project\');
}

常驻框架(推荐同步或队列)

try {
  // ...
} catch (Throwable $e) {
  // 同步上报(简单、直接)
  Logger::reportSync($e, \'https://your-collector/collect\', \'my-project\');

  // 或者:转成数组,投递队列,由 Worker 负责上报(推荐)
  $payload = Logger::formatThrowable($e, \'my-project\');
  Queue::push(\'error_report\', $payload);
}

性能那些事儿

有人担心“网络 I/O 会把 PHP 卡死”。我的观点是:

  • 错误本身通常是低频事件。如果你的系统错误频率高到持续占用大量带宽/请求,那说明系统正常运行已经有更严重的问题了。
  • 对于多数业务,一次 fork 一个子进程并做一次 HTTP POST 的开销在可接受范围,用户体验影响极小。
  • 在对性能要求极苛刻或错误量非常大的场景,正确做法是把上报变成队列 + 批量发送或将上报移动到专门的后端处理链路,而不是在业务路径里频繁 fork。

总之:衡量利弊后选择适合你业务的方式lazylog 提供了两端(sync/async)以及格式化功能,方便你按需设计。


最后

我把它做成 composer 包的原因很直接:我希望 快速把 PHP 项目的错误上报到我自己的 Go 服务(oh-shit-logger) ,而不是每个项目都重复造轮子。把常用逻辑抽出来,项目里 composer require hejunjie/lazylog 就能统一上报方式——既省事又稳妥。

如果你想快速了解这个项目:Zread 解析文档

  • 如果你是在 PHP-FPM / CLI 的短生命周期环境:reportAsync() 很方便,能保证主流程不被阻塞。
  • 如果你是在 Webman/Swoole 等常驻内存框架:优先考虑 reportSync() 或推队列再上报。
  • 如果你面临的是极高并发的错误量:把上报放队列,批量发送,或交由专门的采集基础设施处理。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 被问性能后,我封装了这个 PHP 错误上报工具 https://www.zuozi.net/35942.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务