PHP怎么接入链路追踪

wen PHP项目 2

本文目录导读:

PHP怎么接入链路追踪

  1. OpenTelemetry(推荐)
  2. Jaeger + Jaeger PHP Client
  3. SkyWalking PHP Agent
  4. Zipkin PHP
  5. Laravel专用方案
  6. 日志集成
  7. 环境变量配置
  8. 最佳实践
  9. 注意事项

我来详细介绍PHP接入链路追踪的几种主流方案:

OpenTelemetry(推荐)

安装扩展

# 安装PHP扩展
pecl install opentelemetry

基础配置

<?php
require_once 'vendor/autoload.php';
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\Exporter\JaegerExporter;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
// 创建TracerProvider
$resource = ResourceInfoFactory::emptyResource()->merge(
    ResourceInfo::create([
        'service.name' => 'your-service-name',
    ])
);
$exporter = new JaegerExporter(
    'jaeger',
    'http://jaeger:9411/api/v2/spans'
);
$tracerProvider = new TracerProvider(
    new SimpleSpanProcessor($exporter),
    $resource
);
$tracer = $tracerProvider->getTracer('your-app');

自动埋点

<?php
// 使用OpenTelemetry自动埋点
$tracer = \OpenTelemetry\API\Globals::tracer();
// 创建span
$span = $tracer->spanBuilder('database-query')
    ->setAttribute('db.system', 'mysql')
    ->setAttribute('db.statement', 'SELECT * FROM users')
    ->startSpan();
// 执行操作
try {
    // 业务逻辑
    $span->end();
} catch (\Exception $e) {
    $span->recordException($e);
    $span->setStatus(StatusCode::STATUS_ERROR);
    $span->end();
    throw $e;
}

中间件方式(Laravel示例)

<?php
namespace App\Http\Middleware;
use Closure;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
class TracingMiddleware
{
    public function handle($request, Closure $next)
    {
        $tracer = Globals::tracer();
        $span = $tracer->spanBuilder($request->getMethod() . ' ' . $request->getPathInfo())
            ->setSpanKind(SpanKind::KIND_SERVER)
            ->setAttribute('http.method', $request->getMethod())
            ->setAttribute('http.url', $request->fullUrl())
            ->startSpan();
        $response = $next($request);
        $span->setAttribute('http.status_code', $response->getStatusCode());
        $span->end();
        return $response;
    }
}

Jaeger + Jaeger PHP Client

<?php
require_once 'vendor/autoload.php';
use Jaeger\Config;
use OpenTracing\GlobalTracer;
use OpenTracing\Reference;
// 初始化Jaeger
$config = new Config(
    [
        'sampler' => [
            'type' => 'const',
            'param' => true,
        ],
        'logging' => true,
    ],
    'your-service-name'
);
$config->initializeTracer();
$tracer = GlobalTracer::get();
// 创建span
$span = $tracer->startActiveSpan('business-operation', [
    'tags' => [
        'db.type' => 'mysql',
        'customer.id' => '123',
    ]
]);
try {
    // 子操作
    $childSpan = $tracer->startActiveSpan('database-query');
    // 执行操作
    $childSpan->close();
    $span->close();
} catch (\Exception $e) {
    $span->setTag('error', true);
    $span->log(['error' => $e->getMessage()]);
    $span->close();
} finally {
    $tracer->flush();
}

SkyWalking PHP Agent

<?php
// 安装SkyWalking Agent
// composer require skywalking/agent
use Skywalking\Agent;
use Skywalking\Util\Config;
// 初始化
$config = new Config([
    'grpc' => '127.0.0.1:11800',
    'service_name' => 'your-service-name',
    'instance_name' => 'instance-1'
]);
Agent::init($config);
// 创建Span
$span = Agent::startSpan('business-operation', [
    'peer' => 'localhost:8080',
    'tags' => [
        'db.type' => 'mysql',
        'customer.id' => '123',
    ]
]);
try {
    // 业务逻辑
    $span->stop();
} catch (\Exception $e) {
    $span->setError($e);
    $span->stop();
}

Zipkin PHP

<?php
use Zipkin\Annotation;
use Zipkin\Endpoint;
use Zipkin\Samplers\Percentage;
use Zipkin\TracingBuilder;
use Zipkin\Reporters\Http;
// 创建Reporter
$endpoint = Endpoint::create('your-service-name');
$reporter = new Http([
    'base_url' => 'http://zipkin:9411/api/v2/spans'
]);
// 采样器
$sampler = Percentage::create(0.1);
// 创建Tracing
$tracing = TracingBuilder::create()
    ->havingLocalEndpoint($endpoint)
    ->havingReporter($reporter)
    ->havingSampler($sampler)
    ->build();
$tracer = $tracing->getTracer();
// 创建Span
$span = $tracer->newTrace()
    ->setName('business-operation')
    ->start();
$span->tag('db.type', 'mysql');
$span->annotate('db.statement', 'SELECT * FROM users');
// 子Span
$child = $tracer->newChild($span->getContext())
    ->setName('database-query')
    ->start();
// 完成操作
$child->finish();
$span->finish();
// 发送到Zipkin
$tracing->flush();

Laravel专用方案

使用 Laravel OpenTelemetry

<?php
// config/opentelemetry.php
return [
    'service_name' => 'laravel-app',
    'exporter' => 'zipkin',
    'zipkin' => [
        'endpoint' => 'http://localhost:9411/api/v2/spans',
    ],
    'sampler' => [
        'type' => 'always_on',
    ],
];
// ServiceProvider
class TelemetryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(Tracer::class, function ($app) {
            return TracerProvider::create()
                ->setResource(ResourceInfo::create([
                    'service.name' => config('opentelemetry.service_name')
                ]))
                ->setExporter(ExporterFactory::create(config('opentelemetry.exporter')))
                ->getTracer();
        });
    }
}

日志集成

<?php
// 在日志中包含Trace ID
class TraceIdProcessor
{
    public function __invoke(array $record): array
    {
        $tracer = \OpenTelemetry\API\Globals::tracer();
        $span = $tracer->getCurrentSpan();
        if ($span) {
            $context = $span->getContext();
            $record['extra']['trace_id'] = $context->getTraceId();
            $record['extra']['span_id'] = $context->getSpanId();
        }
        return $record;
    }
}
// 配置到Monolog
$logger->pushProcessor(new TraceIdProcessor());

环境变量配置

# .env 配置
OTEL_SERVICE_NAME=your-service-name
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_TRACES_EXPORTER=otlp
OTEL_PROPAGATORS=jaeger,b3,baggage,tracecontext

最佳实践

统一封装

<?php
class TracingHelper
{
    public static function trace(string $operation, callable $callback, array $attributes = [])
    {
        $tracer = \OpenTelemetry\API\Globals::tracer();
        $span = $tracer->spanBuilder($operation)
            ->setAttributes($attributes)
            ->startSpan();
        try {
            $result = $callback();
            $span->setStatus(StatusCode::STATUS_OK);
            return $result;
        } catch (\Exception $e) {
            $span->recordException($e);
            $span->setStatus(StatusCode::STATUS_ERROR);
            throw $e;
        } finally {
            $span->end();
        }
    }
}
// 使用
$result = TracingHelper::trace('user-registration', function() {
    // 业务逻辑
}, ['user.type' => 'vip']);

性能监控

<?php
// 监控数据库查询
DB::listen(function ($query) {
    $tracer = \OpenTelemetry\API\Globals::tracer();
    $span = $tracer->spanBuilder('query')
        ->setAttribute('db.system', 'mysql')
        ->setAttribute('db.statement', $query->sql)
        ->setAttribute('db.bindings', json_encode($query->bindings))
        ->startSpan();
    $span->end();
});
// 监控Redis操作
Redis::macro('trace', function ($command, $params) {
    $tracer = \OpenTelemetry\API\Globals::tracer();
    $span = $tracer->spanBuilder('redis-' . $command)
        ->setAttribute('redis.command', $command)
        ->setAttribute('redis.params', json_encode($params))
        ->startSpan();
    try {
        $result = $this->command($command, $params);
        $span->end();
        return $result;
    } catch (\Exception $e) {
        $span->recordException($e);
        $span->end();
        throw $e;
    }
});

注意事项

  1. 性能影响:合理设置采样率
  2. 安全:确保追踪数据不包含敏感信息
  3. 异步场景:处理协程、消息队列的链路传递
  4. 错误处理:确保发生异常时也能正确记录
  5. 资源清理:及时关闭Span释放资源

选择合适的方案,建议优先使用OpenTelemetry,它是目前最通用的标准。

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