Yii框架不内置APM能力,需通过PSR-15中间件、事件钩子或OpenTelemetry扩展实现可观测性;Yii 2需用beforeAction/afterAction补全追踪生命周期,禁用日志重复采集;不可直接套用Laravel Telescope因架构差异;轻量采样可行但无法支持分布式链路追踪。

Yii 框架本身不内置 APM(Application Performance Monitoring)能力,但可以通过标准 PSR-15 中间件、事件钩子和兼容 OpenTracing / OpenTelemetry 的扩展实现可观测性。直接装个“监控插件”就能出图表的想法,在 Yii 里行不通——得选对集成点,否则埋点失效、指标错乱、甚至拖慢请求。
Yii 2.0.x 如何接入 OpenTelemetry PHP SDK
OpenTelemetry 是当前最主流的跨语言可观测性标准,PHP SDK 已支持自动 HTTP 请求追踪和手动 Span 注入。Yii 2 没有原生中间件栈,必须靠 beforeAction 和 afterAction 钩子补全生命周期覆盖。
- 先用 Composer 安装:
composer require open-telemetry/sdk(注意:需 PHP 8.0+;若用 PHP 7.4,只能退到opentelemetry-php-contrib的旧版) - 在
config/main.php的bootstrap数组里注册自定义组件,例如otelTracer,并在其init()中初始化全局 TracerProvider - 在基控制器(如
Controller)的beforeAction()里调用startSpan(),绑定到$this->traceId;在afterAction()中endSpan()并记录响应码、耗时、异常信息 - 务必禁用
yii\log\Target对 trace/span 日志的重复采集,否则会污染 span 层级关系
为什么不能直接用 Laravel 的 Scout + Telescope 方案
Telescope 依赖 Laravel 的服务容器绑定机制和 Illuminate\Support\Facades 全局门面,Yii 的 Yii::$app 虽然也提供 DI 容器,但生命周期管理、事件分发粒度、日志通道抽象都不同。硬套会导致:
-
Telescope::startRecording()在 Yii 的Application::bootstrap()阶段无法触发,因为没有等价的bootstrappers注册入口 - 数据库查询日志被
yii\db\Command::execute()拦截,但 Telescope 的QueryWatcher期望监听PDO::prepare(),hook 点错位 - 所有
LogTarget输出都会被 Telescope 当作原始日志吞掉,但 Yii 默认把 SQL、缓存、路由匹配全打到applicationchannel,无法按 level 或 category 过滤
用 Yii 自带事件做轻量级性能采样是否可行
可行,但仅适合定位单点瓶颈,不适合生产级 APM。关键是利用好 yii\base\Application::EVENT_BEFORE_REQUEST 和 EVENT_AFTER_REQUEST,再配合 microtime(true) 手动计时。
- 在
config/main.php的on配置块中注册:yii\base\Application::EVENT_BEFORE_REQUEST => function ($event) { Yii::$app->params['req_start'] = microtime(true); } - 同理在
EVENT_AFTER_REQUEST中计算差值,并写入Yii::info(),但注意:不要在这里调用Yii::getLogger()->flush(),否则每请求刷一次磁盘,QPS 直接腰斩 - 如果要统计 SQL 耗时,别 hook
yii\db\Connection::open()—— 它只在首次连接触发;应监听yii\db\Command::EVENT_BEFORE_EXECUTE和EVENT_AFTER_EXECUTE - 这种方案无法关联 DB 查询与 HTTP 请求 ID,排查分布式调用链时完全失效
真正卡住落地的不是技术选型,而是 span context 在 Yii 的 Request/Response 生命周期外如何透传——比如队列任务、定时命令、WebSocket 推送,这些场景下 OpenTelemetry 的 Context::getCurrent() 很可能为空。得在每个异步入口手动 Context::storage()->attach($parentContext),漏一处,整条链就断了。



















