本文目录导读:

- 方案一:使用 Nginx + Lua(OpenResty)【推荐】
- 方案二:PHP 扩展(C 扩展)拦截
- 方案三:PHP 框架中间件(最常用)
- 方案四:AOP 切面编程(面向切面)
- 方案五:自建日志系统 + 日志分析【间接埋点】
- 总结推荐
在 PHP 中实现“零代码埋点”通常是指不修改业务逻辑代码,通过外部手段(如框架中间件、代理层、扩展)来自动采集数据,以下是几种主流的实现方案:
使用 Nginx + Lua(OpenResty)【推荐】
这是最彻底的“零代码”方案,在 Web 服务器层拦截请求,完全不触碰 PHP 代码。
原理:Nginx 在处理请求时,通过 log_by_lua 或 header_filter_by_lua 钩子,在请求结束或响应发出时自动记录日志。
# nginx.conf 配置示例
http {
lua_shared_dict my_cache 10m;
server {
listen 80;
location /api/ {
# 业务请求转给 PHP-FPM
fastcgi_pass php_fpm;
# 响应完成后,自动记录埋点数据(零 PHP 代码)
log_by_lua_block {
local cjson = require "cjson"
local track = {}
track.uri = ngx.var.request_uri
track.method = ngx.var.request_method
track.status = ngx.status
track.latency = (ngx.now() - ngx.req.start_time()) * 1000
track.client_ip = ngx.var.remote_addr
track.user_agent = ngx.var.http_user_agent
-- 发送到 Kafka 或日志系统
local producer = require "resty.kafka.producer"
local broker_list = {{host = "kafka-server", port = 9092}}
local bp = producer:new(broker_list)
bp:send("php_track_topic", cjson.encode(track))
}
}
}
}
优点:完全不影响 PHP 代码,性能极高,支持高并发。
缺点:需要熟悉 OpenResty/Lua,且无法获取 PHP 内部变量(如数据库查询耗时)。
PHP 扩展(C 扩展)拦截
开发一个 PHP 扩展,利用 PHP 的 zend_execute_internal 钩子或 auto_prepend_file 机制,在脚本执行前后自动执行埋点。
实现方式(简化):
// PHP_MSHOT 扩展伪代码
void php_track_request_start(INTERNAL_FUNCTION_PARAMETERS) {
// 记录开始时间
track_begin_time = hrtime();
}
void php_track_request_end(INTERNAL_FUNCTION_PARAMETERS) {
// 计算耗时
// 通过 dlopen/curl 发送到采集服务器
// 不修改任何业务代码
}
注册钩子(在 php.ini 中):
; 自动加载扩展,无需修改代码 extension=track_ext.so ; 每次请求开始和结束时自动执行 auto_prepend_file=/path/to/prepend.php auto_append_file=/path/to/append.php
优点:能获取 PHP 内部信息。
缺点:需要 C 语言功底,维护成本高。
PHP 框架中间件(最常用)
如果你的项目使用 Laravel、ThinkPHP 等现代框架,可以通过全局中间件实现,业务代码零侵入(但需要配置中间件文件)。
Laravel 示例:
<?php
// app/Http/Middleware/AutoTrack.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Redis;
class AutoTrack
{
public function handle($request, Closure $next)
{
$start = microtime(true);
$response = $next($request); // 业务代码执行
// 业务完成后自动记录,无需在控制器写任何埋点代码
$latency = (microtime(true) - $start) * 1000;
$data = [
'uri' => $request->getRequestUri(),
'method' => $request->method(),
'status' => $response->status(),
'latency' => round($latency, 2),
'timestamp' => date('Y-m-d H:i:s'),
];
// 异步发送到日志系统
Redis::pipeline(function ($pipe) use ($data) {
$pipe->rPush('track_queue', json_encode($data));
$pipe->expire('track_queue', 86400);
});
return $response;
}
}
注册中间件(app/Http/Kernel.php):
protected $middleware = [
\App\Http\Middleware\AutoTrack::class, // 全局注册,所有请求自动生效
];
优点:框架生态成熟,易维护,不影响业务逻辑。
缺点:需要找到项目的中间件核心,理解框架路由机制。
AOP 切面编程(面向切面)
使用 AOP 库(如 Go!AOP 或 Ray.Aop),通过配置文件定义切点,自动拦截方法调用。
<?php
// aspect_track.php
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;
class TrackAspect implements Aspect
{
/**
* @Around("execution(public App\Controller\**\*(*))")
*/
public function aroundTracker(MethodInvocation $invocation)
{
$start = microtime(true);
$result = $invocation->proceed(); // 执行业务代码
$latency = microtime(true) - $start;
file_put_contents('/tmp/track.log',
$invocation->getMethod()->getName() . ':' . $latency . "\n",
FILE_APPEND);
return $result;
}
}
注册切面(config.php):
return [
'aspect' => [
TrackAspect::class
],
'include_paths' => [
__DIR__ . '/../app/Controller', // 监控这些目录
]
];
优点:精确到方法级别,适合复杂业务监控。
缺点:需要引入新的库,对性能有一定影响。
自建日志系统 + 日志分析【间接埋点】
如果只是要获取请求日志(不需要业务数据),可以完全不写埋点代码。
-
Nginx 访问日志:直接开启
$request_time和$upstream_response_time。log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '$request_time $upstream_response_time'; access_log /var/log/nginx/access.log main; -
PHP-FPM slowlog:记录慢请求,无需改代码。
; php-fpm.conf slowlog = log/$pool.log.slow request_slowlog_timeout = 5s ; 超过5秒自动记录堆栈
-
监控系统:配合
telegraf+Prometheus,每秒主动抓取 PHP-FPM 状态页。# 开启 fpm 状态页 pm.status_path = /status
总结推荐
| 场景 | 推荐方案 | 侵入性 |
|---|---|---|
| 团队会 Nginx/Lua | 方案一(OpenResty) | 零侵入,性能最好 |
| 用 Laravel/Tp 框架 | 方案三(中间件) | 仅配置中间件 |
| 没有框架的裸 PHP | 方案二(扩展)+ 方案五(Nginx日志) | 零侵入但需 C 语言 |
| 精确到方法级别 | 方案四(AOP) | 需引入库 |
最终建议:如果条件允许,优先选择 OpenResty + Lua,这是最干净的“零代码”方案;如果团队 PHP 为主且用框架,用全局中间件是最实际的解决方案。