PHP 怎么PHP 外部服务追踪

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 外部服务追踪

  1. 🎯 主要追踪维度
  2. 📊 推荐的监控工具
  3. 🎯 最佳实践

我来帮你详细解释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: 监控和可视化

🎯 最佳实践

  1. 统一追踪ID: 使用唯一ID关联整个请求链路
  2. 异步日志: 使用消息队列避免阻塞主流程
  3. 级别分级: 设置不同追踪级别(DEBUG/INFO/WARNING)
  4. 采样策略: 高频服务使用采样追踪
  5. 性能考虑: 追踪本身不要成为性能瓶颈
// 采样追踪示例
class SampledTracer {
    private $sampleRate;
    public function shouldTrace() {
        // 1%采样率
        return rand(1, 100) <= $this->sampleRate;
    }
}

这些方法可以根据你的具体需求选择和组合使用,建议从简单的日志追踪开始,逐步引入更完善的分布式追踪方案。

抱歉,评论功能暂时关闭!