本文目录导读:

我来帮你详细解释PHP外部服务追踪的实现方法,这是一个非常重要的开发实践,用于监控和调试与第三方API、数据库、微服务等的交互。
🎯 主要追踪维度
请求追踪
<?php
class RequestTracer {
private $startTime;
private $apiCalls = [];
private $requestId;
public function __construct() {
$this->requestId = uniqid('req_', true);
$this->startTime = microtime(true);
}
public function startApiCall($service, $url) {
return [
'service' => $service,
'url' => $url,
'start_time' => microtime(true),
'request_id' => $this->requestId
];
}
public function endApiCall($callId, $response) {
$endTime = microtime(true);
$duration = $endTime - $callId['start_time'];
$this->apiCalls[] = [
'service' => $callId['service'],
'url' => $callId['url'],
'duration' => $duration,
'status' => $response['status'] ?? 'unknown',
'timestamp' => date('Y-m-d H:i:s')
];
return $duration;
}
public function getSummary() {
return [
'request_id' => $this->requestId,
'total_duration' => microtime(true) - $this->startTime,
'api_calls' => $this->apiCalls,
'api_call_count' => count($this->apiCalls)
];
}
}
使用中间件追踪
<?php
class ServiceTrackingMiddleware {
private $tracer;
private $logger;
private $config;
public function __construct($config = []) {
$this->tracer = new RequestTracer();
$this->config = $config;
$this->logger = new Logger($config['log_path'] ?? '/var/log/tracking');
}
public function trackExternalCall($serviceName, $url, $method = 'GET', $data = null) {
$callId = $this->tracer->startApiCall($serviceName, $url);
try {
// 执行外部调用
$response = $this->executeExternalCall($url, $method, $data);
$duration = $this->tracer->endApiCall($callId, $response);
// 记录日志
$this->logApiCall($serviceName, $url, $duration, $response);
// 检查性能阈值
$this->checkPerformanceThreshold($serviceName, $duration);
return $response;
} catch (Exception $e) {
// 记录错误
$this->logError($callId, $e);
throw $e;
}
}
private function executeExternalCall($url, $method, $data) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->config['timeout'] ?? 30,
CURLOPT_CONNECTTIMEOUT => $this->config['connect_timeout'] ?? 5
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode,
'data' => $response,
'success' => $httpCode >= 200 && $httpCode < 300
];
}
private function logApiCall($service, $url, $duration, $response) {
$logData = [
'timestamp' => date('c'),
'service' => $service,
'url' => $url,
'duration' => $duration,
'status' => $response['status'],
'success' => $response['success']
];
$this->logger->info('external_api_call', $logData);
}
private function checkPerformanceThreshold($service, $duration) {
$threshold = $this->config['thresholds'][$service] ?? 1.0;
if ($duration > $threshold) {
$this->logger->warning('slow_external_call', [
'service' => $service,
'duration' => $duration,
'threshold' => $threshold
]);
}
}
private function logError($callId, $exception) {
$this->logger->error('external_call_error', [
'request_id' => $callId['request_id'],
'service' => $callId['service'],
'url' => $callId['url'],
'error' => $exception->getMessage()
]);
}
}
// 日志类
class Logger {
private $logPath;
public function __construct($logPath) {
$this->logPath = $logPath;
if (!is_dir($logPath)) {
mkdir($logPath, 0755, true);
}
}
public function info($type, $data) {
$this->writeLog('INFO', $type, $data);
}
public function warning($type, $data) {
$this->writeLog('WARNING', $type, $data);
}
public function error($type, $data) {
$this->writeLog('ERROR', $type, $data);
}
private function writeLog($level, $type, $data) {
$logEntry = [
'level' => $level,
'type' => $type,
'data' => $data,
'memory_usage' => memory_get_usage(true)
];
$logFile = $this->logPath . '/' . date('Y-m-d') . '.log';
file_put_contents(
$logFile,
json_encode($logEntry) . PHP_EOL,
FILE_APPEND | LOCK_EX
);
}
}
使用OpenTracing标准
<?php
// 需要安装 jaeger/opentracing
use OpenTracing\Formats;
use OpenTracing\GlobalTracer;
class OpenTracingServiceTracker {
private $tracer;
private $spans = [];
public function __construct() {
// 初始化Jaeger tracer
$config = Jaeger\Config::getInstance();
$config->gen128bit();
$this->tracer = $config->initTrace('php-service', '0.0.0.0:6831');
GlobalTracer::set($this->tracer);
}
public function startExternalCall($service, $url) {
$spanContext = GlobalTracer::get()->getActiveSpan()->getContext();
$span = GlobalTracer::get()->startSpan('external.call', [
'child_of' => $spanContext,
'tags' => [
'service.name' => $service,
'http.url' => $url,
'span.kind' => 'client'
]
]);
// 注入上下文到HTTP头
$headers = [];
GlobalTracer::get()->inject(
$span->getContext(),
Formats\HttpHeaders,
$headers
);
return [
'span' => $span,
'headers' => $headers
];
}
public function endExternalCall($callData, $status = 200) {
$span = $callData['span'];
$span->setTag('http.status_code', $status);
if ($status >= 400) {
$span->setTag('error', true);
}
$span->finish();
}
}
数据库追踪
<?php
class DatabaseQueryTracker {
private $queries = [];
private $pdo;
private $logger;
public function __construct($dsn, $username, $password, $config = []) {
$this->pdo = new PDO($dsn, $username, $password);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->logger = new Logger($config['log_path'] ?? '/var/log/db_tracking');
}
public function query($sql, $params = []) {
$startTime = microtime(true);
$startMemory = memory_get_usage();
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$duration = microtime(true) - $startTime;
$memoryUsed = memory_get_usage() - $startMemory;
// 记录查询
$this->queries[] = [
'sql' => $sql,
'params' => $params,
'duration' => $duration,
'memory' => $memoryUsed,
'timestamp' => date('c')
];
// 慢查询日志
if ($duration > 0.1) { // 超过100ms
$this->logger->warning('slow_query', [
'sql' => $sql,
'duration' => $duration,
'params' => $params
]);
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$this->logger->error('query_error', [
'sql' => $sql,
'error' => $e->getMessage()
]);
throw $e;
}
}
public function getQueryStats() {
$totalTime = array_sum(array_column($this->queries, 'duration'));
$totalMemory = array_sum(array_column($this->queries, 'memory'));
return [
'total_queries' => count($this->queries),
'total_time' => $totalTime,
'average_time' => count($this->queries) > 0 ? $totalTime / count($this->queries) : 0,
'total_memory' => $totalMemory,
'slow_queries' => count(array_filter($this->queries, fn($q) => $q['duration'] > 0.1))
];
}
}
使用追踪中间件(Laravel示例)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
class ExternalServiceTracking
{
public function handle($request, Closure $next)
{
// 请求开始
$startTime = microtime(true);
// 处理请求
$response = $next($request);
// 请求结束
$duration = microtime(true) - $startTime;
// 记录外部服务调用
if ($this->hasExternalCalls($request)) {
Log::channel('external')->info('external_service_call', [
'url' => $request->fullUrl(),
'duration' => $duration,
'method' => $request->method(),
'response_status' => $response->status(),
'external_calls' => $this->getExternalCallsData()
]);
}
// 添加追踪头信息
$response->headers->set('X-Tracking-Id', uniqid());
$response->headers->set('X-Duration', $duration);
return $response;
}
private function hasExternalCalls($request)
{
// 判断请求是否涉及外部服务调用
return true; // 简化示例
}
private function getExternalCallsData()
{
// 获取外部调用数据
return [];
}
}
数据可视化
<?php
class TrackingDashboard
{
public function getMetrics($service, $timeRange = '24h')
{
// 从日志中提取指标
$logs = $this->fetchLogs($service, $timeRange);
return [
'total_requests' => count($logs),
'average_response_time' => $this->calculateAvgTime($logs),
'error_rate' => $this->calculateErrorRate($logs),
'p95_response_time' => $this->calculatePercentile($logs, 95),
'slow_requests' => $this->getSlowRequests($logs)
];
}
private function fetchLogs($service, $timeRange)
{
// 从日志存储中获取数据
return [];
}
private function calculateAvgTime($logs)
{
if (empty($logs)) return 0;
return array_sum(array_column($logs, 'duration')) / count($logs);
}
private function calculateErrorRate($logs)
{
if (empty($logs)) return 0;
$errors = count(array_filter($logs, fn($log) => $log['status'] >= 400));
return ($errors / count($logs)) * 100;
}
private function calculatePercentile($logs, $percentile)
{
$durations = array_column($logs, 'duration');
sort($durations);
$index = ceil($percentile / 100 * count($durations));
return $durations[$index - 1] ?? 0;
}
private function getSlowRequests($logs, $threshold = 1.0)
{
return array_filter($logs, fn($log) => $log['duration'] > $threshold);
}
}
📊 推荐的监控工具
APM工具
- New Relic: 提供PHP代理自动检测
- Datadog: 支持分布式追踪
- Dynatrace: 自动发现服务依赖
开源方案
- Jaeger: Uber开源的分布式追踪系统
- Zipkin: Twitter开源的追踪系统
- Prometheus + Grafana: 监控和可视化
🎯 最佳实践
- 统一追踪ID: 使用唯一ID关联整个请求链路
- 异步日志: 使用消息队列避免阻塞主流程
- 级别分级: 设置不同追踪级别(DEBUG/INFO/WARNING)
- 采样策略: 高频服务使用采样追踪
- 性能考虑: 追踪本身不要成为性能瓶颈
// 采样追踪示例
class SampledTracer {
private $sampleRate;
public function shouldTrace() {
// 1%采样率
return rand(1, 100) <= $this->sampleRate;
}
}
这些方法可以根据你的具体需求选择和组合使用,建议从简单的日志追踪开始,逐步引入更完善的分布式追踪方案。