PHP 语言 OpenTelemetry 接入指南
本文档提供 PHP 应用接入 OpenTelemetry 的完整指南,包括零代码接入和 SDK 手动接入两种方式,支持 Laravel、Symfony 等主流框架。
目录
概述
PHP 语言特性
PHP 的 OpenTelemetry 支持具有以下特点:
- 成熟稳定: API 和 SDK 已达到稳定版本
- 零代码接入: 通过 OpenTelemetry PHP 扩展实现完全零侵入
- 丰富的埋点库: 支持 30+ 主流 PHP 框架和库
- Composer 支持: 通过 Composer 轻松安装和管理
- PSR 标准: 遵循 PSR-3、PSR-7 等 PHP 标准
版本要求
- PHP 版本: 8.0+ (推荐 8.1+)
- OpenTelemetry PHP: v1.0.0+
- 自动埋点: PHP 8.0+(需要 OpenTelemetry PHP 扩展)
- 支持架构: x86_64, arm64
支持的信号
- ✅ Traces: 完全支持
- ✅ Metrics: 完全支持
- ✅ Logs: 完全支持
适用场景
- Web 服务(Laravel、Symfony、Slim)
- 微服务架构
- API 服务
- 数据库应用
- 消息队列应用
零代码接入(推荐)
零代码接入是 PHP OpenTelemetry 最强大的特性,通过 OpenTelemetry PHP 扩展和自动埋点库即可实现自动埋点,无需修改任何源代码。
快速开始(5 分钟)
1. 安装 OpenTelemetry PHP 扩展
方法 1: 使用 PECL 安装(推荐)
# 安装开发环境依赖(Linux)sudo apt-get install gcc make autoconf# 或 macOSbrew install gcc make autoconf# 安装扩展pecl install opentelemetry
方法 2: 使用 Linux 包管理器
CentOS/RHEL (RPM):
yum update -yyum install -y epel-release yum-utilsyum install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpmyum-config-manager --enable remi-php81yum install -y php php-pecl-opentelemetry
Alpine Linux (APK):
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositoriesapk add php php81-pecl-opentelemetry@testing
启用扩展
在 php.ini 中添加:
[opentelemetry]extension=opentelemetry.so
验证安装:
php -m | grep opentelemetryphp --ri opentelemetry
2. 安装 SDK 和自动埋点库
根据您使用的框架和库,安装相应的自动埋点包:
# 基础 SDK 和 OTLP Exportercomposer require open-telemetry/sdkcomposer require open-telemetry/exporter-otlp# 根据您的框架选择(示例:Slim Framework 和 PSR-18 HTTP 客户端)composer require open-telemetry/opentelemetry-auto-slimcomposer require open-telemetry/opentelemetry-auto-psr18# 其他常用自动埋点库composer require open-telemetry/opentelemetry-auto-psr15 # PSR-15composer require open-telemetry/opentelemetry-auto-pdo # PDOcomposer require open-telemetry/opentelemetry-auto-redis # Rediscomposer require open-telemetry/opentelemetry-auto-guzzle # Guzzle
查看所有可用的自动埋点库:
访问 Packagist 搜索 opentelemetry-auto-* 查看完整的自动埋点库列表。
3. 配置环境变量
必需的环境变量:
# 启用自动加载(必需)export OTEL_PHP_AUTOLOAD_ENABLED=true# 服务信息export OTEL_SERVICE_NAME=my-php-serviceexport OTEL_SERVICE_VERSION=1.0.0export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production# OTLP Exporter(内部平台)export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:80export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufexport OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317# Traces 配置export OTEL_TRACES_EXPORTER=otlpexport OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1# 传播器配置export OTEL_PROPAGATORS=baggage,tracecontext
或使用 php.ini 配置:
在 php.ini 文件中添加:
OTEL_PHP_AUTOLOAD_ENABLED="true"OTEL_SERVICE_NAME=my-php-serviceOTEL_TRACES_EXPORTER=otlpOTEL_EXPORTER_OTLP_PROTOCOL=http/protobufOTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:80OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317OTEL_PROPAGATORS=baggage,tracecontext
4. 启动应用
就这么简单!直接运行您的应用即可,无需任何代码修改:
# CLI 脚本php myapp.php# Web 服务器(PHP-FPM、Apache、Nginx)# 正常启动即可,扩展会自动工作
重要提示:
- ✅ 无需创建任何初始化文件
- ✅ 无需修改应用代码
- ✅ 只需安装扩展和 Composer 包,配置环境变量即可
- ✅ 扩展会自动检测已安装的自动埋点库并启用它们
就这么简单!您的应用现在已经自动埋点并开始发送遥测数据了。
工作原理
OpenTelemetry PHP 扩展通过注册观察者函数(observer functions)来实现自动埋点。扩展会在类和方法执行前后执行这些函数,自动创建和管理 Span。
关键点:
- 扩展层:OpenTelemetry PHP 扩展提供底层钩子机制
- 自动埋点库:Composer 包提供具体的埋点逻辑
- 自动加载:通过
OTEL_PHP_AUTOLOAD_ENABLED=true启用自动加载机制 - 零侵入:无需修改应用代码,只需安装和配置
支持的自动埋点库
根据您安装的自动埋点库,系统会自动检测并启用相应的埋点。常用库包括:
Web 框架
- Slim Framework:
open-telemetry/opentelemetry-auto-slim - Laravel:
open-telemetry/opentelemetry-auto-laravel(如可用) - Symfony:
open-telemetry/opentelemetry-auto-symfony(如可用)
HTTP 客户端
- PSR-18:
open-telemetry/opentelemetry-auto-psr18 - PSR-15:
open-telemetry/opentelemetry-auto-psr15 - Guzzle:
open-telemetry/opentelemetry-auto-guzzle
数据库
- PDO:
open-telemetry/opentelemetry-auto-pdo - MySQLi:
open-telemetry/opentelemetry-auto-mysqli(如可用)
缓存
- Redis:
open-telemetry/opentelemetry-auto-redis - Memcached:
open-telemetry/opentelemetry-auto-memcached(如可用)
查看完整列表:
访问 Packagist 搜索 opentelemetry-auto-* 查看所有可用的自动埋点库。
环境变量详解
服务信息:
# 服务名称(必需)export OTEL_SERVICE_NAME=my-php-service# 服务版本export OTEL_SERVICE_VERSION=1.0.0# 资源属性(键值对,逗号分隔)export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=backend
OTLP Exporter 配置:
# 端点地址(内部平台,HTTP 协议)export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:80# 协议类型(http/protobuf 或 grpc)export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf# 请求头(包含 DSN)export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317# 超时时间(毫秒)export OTEL_EXPORTER_OTLP_TIMEOUT=10000# 压缩(gzip,可选)export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
重要提示:
- 内部平台推荐使用 HTTP 协议(端口 80),兼容性更好
- 如果使用 gRPC 协议,端点应为
uptrace-grpc.yodao.cn:4317 - DSN 格式:
http://token@uptrace-grpc.yodao.cn/project?grpc=4317
Traces 配置:
# Exporter 类型export OTEL_TRACES_EXPORTER=otlp# 采样器类型export OTEL_TRACES_SAMPLER=parentbased_traceidratio# 采样率(0.0 到 1.0)export OTEL_TRACES_SAMPLER_ARG=0.1
Metrics 配置:
# Exporter 类型export OTEL_METRICS_EXPORTER=otlp# 导出间隔(毫秒)export OTEL_METRIC_EXPORT_INTERVAL=60000
Logs 配置:
# Exporter 类型export OTEL_LOGS_EXPORTER=otlp
传播器配置:
# 传播器类型(逗号分隔)export OTEL_PROPAGATORS=baggage,tracecontext
不同运行环境的配置
PHP CLI
# 设置环境变量后直接运行export OTEL_PHP_AUTOLOAD_ENABLED=trueexport OTEL_SERVICE_NAME=my-php-serviceexport OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:80export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317php myapp.php
PHP-FPM
在 php-fpm.conf 或 www.conf 中设置环境变量:
[www]env[OTEL_PHP_AUTOLOAD_ENABLED] = trueenv[OTEL_SERVICE_NAME] = my-php-serviceenv[OTEL_EXPORTER_OTLP_ENDPOINT] = http://uptrace-grpc.yodao.cn:80env[OTEL_EXPORTER_OTLP_HEADERS] = uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317
Apache
在 httpd.conf 或虚拟主机配置中:
<VirtualHost *:80>SetEnv OTEL_PHP_AUTOLOAD_ENABLED trueSetEnv OTEL_SERVICE_NAME my-php-serviceSetEnv OTEL_EXPORTER_OTLP_ENDPOINT http://uptrace-grpc.yodao.cn:80SetEnv OTEL_EXPORTER_OTLP_HEADERS "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317"</VirtualHost>
Docker
在 Dockerfile 或 docker-compose.yml 中:
environment:- OTEL_PHP_AUTOLOAD_ENABLED=true- OTEL_SERVICE_NAME=my-php-service- OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:80- OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317
验证零代码接入
启动应用后,访问您的服务端点,然后在 APM 平台查看数据:
- Traces: 查看调用链路
- Metrics: 查看请求量、延迟等指标
- Service Map: 查看服务拓扑图
快速验证
# 启动应用php -d opentelemetry.enabled=1 -S localhost:8000# 在另一个终端发送测试请求curl http://localhost:8000/# 等待几秒后,在 APM 平台查看数据
日志级别配置
可以通过环境变量设置日志级别:
# 设置日志级别export OTEL_LOG_LEVEL=info # none, error, warn, info, debug, verbose, all
SDK 手动接入
SDK 手动接入提供更精细的控制和自定义能力。
安装依赖
使用 Composer 安装:
composer require open-telemetry/apicomposer require open-telemetry/sdkcomposer require open-telemetry/exporter-otlpcomposer require open-telemetry/instrumentation-httpcomposer require open-telemetry/instrumentation-pdo
基础初始化
创建 OpenTelemetry 初始化文件
<?php// otel.phprequire_once __DIR__ . '/vendor/autoload.php';use OpenTelemetry\API\Globals;use OpenTelemetry\SDK\Resource\ResourceInfoFactory;use OpenTelemetry\SDK\Trace\TracerProviderBuilder;use OpenTelemetry\SDK\Trace\Sampler\ParentBased;use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;use OpenTelemetry\Exporter\Otlp\OtlpExporter;use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;use OpenTelemetry\SDK\Common\Attribute\Attributes;function initOpenTelemetry(): void{// 创建 Resource$resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfoFactory::defaultResource())->merge(ResourceInfoFactory::create(['service.name' => $_ENV['OTEL_SERVICE_NAME'] ?? 'my-php-service','service.version' => $_ENV['OTEL_SERVICE_VERSION'] ?? '1.0.0',]));// 创建 OTLP Exporter$exporter = new OtlpExporter(['endpoint' => $_ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] ?? 'uptrace-grpc.yodao.cn:4317','protocol' => $_ENV['OTEL_EXPORTER_OTLP_PROTOCOL'] ?? 'grpc','headers' => ['uptrace-dsn' => $_ENV['OTEL_EXPORTER_OTLP_HEADERS'] ?? '',],]);// 创建采样器$sampler = new ParentBased(new TraceIdRatioBasedSampler((float)($_ENV['OTEL_TRACES_SAMPLER_ARG'] ?? 0.1)));// 创建 TracerProvider$tracerProvider = (new TracerProviderBuilder())->setResource($resource)->addSpanProcessor(new BatchSpanProcessor($exporter))->setSampler($sampler)->build();// 注册全局 TracerProviderGlobals::registerInitializer(function () use ($tracerProvider) {return $tracerProvider;});}// 初始化initOpenTelemetry();
在应用中使用
<?php// index.phprequire_once __DIR__ . '/otel.php';use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;// 获取 Tracer$tracer = Globals::tracerProvider()->getTracer('my-service');// 创建 Span$span = $tracer->spanBuilder('handle_request')->setSpanKind(SpanKind::KIND_SERVER)->startSpan();$scope = $span->activate();try {// 业务逻辑$span->setAttribute('http.method', 'GET');$span->setAttribute('http.url', '/');echo "Hello, World!";$span->setStatus(StatusCode::STATUS_OK);} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}
Traces - 手动埋点
创建和使用 Span
<?phpuse OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;use OpenTelemetry\API\Globals;$tracer = Globals::tracerProvider()->getTracer('my-service');function processOrder(string $orderId): void{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('process_order')->setSpanKind(SpanKind::KIND_INTERNAL)->startSpan();$scope = $span->activate();try {$span->setAttribute('order.id', $orderId);$span->setAttribute('operation', 'process');$span->addEvent('order_validation_started');validateOrder($orderId);$span->addEvent('order_validated');chargePayment($orderId);$span->addEvent('payment_processed');$span->setStatus(StatusCode::STATUS_OK);$span->setAttribute('order.success', true);} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
Context 传播
<?phpuse OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;use OpenTelemetry\API\Context\Propagation\TextMapPropagatorInterface;// 获取当前 Context$propagator = TraceContextPropagator::getInstance();$context = Globals::propagator()->extract([], $_SERVER);// 在 HTTP 请求中传播 Contextfunction makeHttpRequest(string $url): string{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('http_request')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();$scope = $span->activate();try {$span->setAttribute('http.url', $url);$span->setAttribute('http.method', 'GET');// 注入 Context 到 HTTP headers$headers = [];Globals::propagator()->inject($headers);$ch = curl_init($url);curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(fn($k, $v) => "$k: $v",array_keys($headers),$headers));$response = curl_exec($ch);$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);$span->setAttribute('http.status_code', $statusCode);$span->setStatus($statusCode >= 400 ? StatusCode::STATUS_ERROR : StatusCode::STATUS_OK);return $response;} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
Metrics - 指标采集
Counter(计数器)
<?phpuse OpenTelemetry\API\Metrics\MeterProviderInterface;use OpenTelemetry\API\Metrics\CounterInterface;$meterProvider = Globals::meterProvider();$meter = $meterProvider->getMeter('my-service');$requestCounter = $meter->createCounter('http.server.requests','total','Total number of HTTP requests');function handleRequest(string $method, string $route, int $statusCode): void{global $requestCounter;$requestCounter->add(1, ['http.method' => $method,'http.route' => $route,'http.status_code' => (string)$statusCode,]);}
Histogram(直方图)
<?php$requestDuration = $meter->createHistogram('http.server.duration','ms','HTTP request duration');function recordDuration(float $duration, string $method, string $route): void{global $requestDuration;$requestDuration->record($duration, ['http.method' => $method,'http.route' => $route,]);}
UpDownCounter(升降计数器)
<?php$activeConnections = $meter->createUpDownCounter('http.server.active_connections','connections','Number of active HTTP connections');function connectionOpened(): void{global $activeConnections;$activeConnections->add(1);}function connectionClosed(): void{global $activeConnections;$activeConnections->add(-1);}
Logs - 日志集成
集成 Monolog
<?phpuse Monolog\Logger;use Monolog\Handler\StreamHandler;use OpenTelemetry\API\Trace\SpanContextInterface;use OpenTelemetry\API\Globals;// 创建 logger$logger = new Logger('my-service');$logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));// 添加 trace 信息到日志$logger->pushProcessor(function ($record) {$span = Globals::tracerProvider()->getTracer('my-service')->spanBuilder('log')->startSpan()->getContext();if ($span->isValid()) {$record['extra']['trace_id'] = $span->getTraceId();$record['extra']['span_id'] = $span->getSpanId();}return $record;});// 使用示例function example(): void{global $logger;$logger->info('Processing request', ['user_id' => '12345','action' => 'create',]);}
框架集成示例
Laravel 框架集成
使用 Service Provider
创建 app/Providers/OpenTelemetryServiceProvider.php:
<?phpnamespace App\Providers;use Illuminate\Support\ServiceProvider;use OpenTelemetry\API\Globals;use OpenTelemetry\SDK\Trace\TracerProviderBuilder;use OpenTelemetry\Exporter\Otlp\OtlpExporter;use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;class OpenTelemetryServiceProvider extends ServiceProvider{public function register(): void{$exporter = new OtlpExporter(['endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT', 'uptrace-grpc.yodao.cn:4317'),'protocol' => env('OTEL_EXPORTER_OTLP_PROTOCOL', 'grpc'),'headers' => ['uptrace-dsn' => env('OTEL_EXPORTER_OTLP_HEADERS', ''),],]);$tracerProvider = (new TracerProviderBuilder())->addSpanProcessor(new BatchSpanProcessor($exporter))->build();Globals::registerInitializer(function () use ($tracerProvider) {return $tracerProvider;});}}
在 config/app.php 中注册:
'providers' => [// ...App\Providers\OpenTelemetryServiceProvider::class,],
使用 Middleware
创建 app/Http/Middleware/OpenTelemetryMiddleware.php:
<?phpnamespace App\Http\Middleware;use Closure;use Illuminate\Http\Request;use OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;class OpenTelemetryMiddleware{public function handle(Request $request, Closure $next){$tracer = Globals::tracerProvider()->getTracer('laravel');$span = $tracer->spanBuilder('http.request')->setSpanKind(SpanKind::KIND_SERVER)->startSpan();$scope = $span->activate();try {$span->setAttribute('http.method', $request->method());$span->setAttribute('http.url', $request->fullUrl());$span->setAttribute('http.route', $request->route()?->getName());$response = $next($request);$span->setAttribute('http.status_code', $response->getStatusCode());$span->setStatus(StatusCode::STATUS_OK);return $response;} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}}
Symfony 框架集成
使用 Event Subscriber
创建 src/EventSubscriber/OpenTelemetrySubscriber.php:
<?phpnamespace App\EventSubscriber;use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpKernel\KernelEvents;use Symfony\Component\HttpKernel\Event\RequestEvent;use Symfony\Component\HttpKernel\Event\ResponseEvent;use OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;class OpenTelemetrySubscriber implements EventSubscriberInterface{private $spans = [];public static function getSubscribedEvents(): array{return [KernelEvents::REQUEST => ['onKernelRequest', 8],KernelEvents::RESPONSE => ['onKernelResponse', -8],];}public function onKernelRequest(RequestEvent $event): void{if (!$event->isMainRequest()) {return;}$request = $event->getRequest();$tracer = Globals::tracerProvider()->getTracer('symfony');$span = $tracer->spanBuilder('http.request')->setSpanKind(SpanKind::KIND_SERVER)->startSpan();$scope = $span->activate();$span->setAttribute('http.method', $request->getMethod());$span->setAttribute('http.url', $request->getUri());$span->setAttribute('http.route', $request->attributes->get('_route'));$this->spans[spl_object_id($request)] = ['span' => $span, 'scope' => $scope];}public function onKernelResponse(ResponseEvent $event): void{$request = $event->getRequest();$response = $event->getResponse();$key = spl_object_id($request);if (!isset($this->spans[$key])) {return;}$span = $this->spans[$key]['span'];$scope = $this->spans[$key]['scope'];$span->setAttribute('http.status_code', $response->getStatusCode());$span->setStatus($response->getStatusCode() >= 400? StatusCode::STATUS_ERROR: StatusCode::STATUS_OK);$span->end();$scope->detach();unset($this->spans[$key]);}}
Slim 框架集成
<?phpuse Slim\Factory\AppFactory;use OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;require __DIR__ . '/vendor/autoload.php';require __DIR__ . '/otel.php';$app = AppFactory::create();// 添加 OpenTelemetry Middleware$app->add(function ($request, $handler) {$tracer = Globals::tracerProvider()->getTracer('slim');$span = $tracer->spanBuilder('http.request')->setSpanKind(SpanKind::KIND_SERVER)->startSpan();$scope = $span->activate();try {$span->setAttribute('http.method', $request->getMethod());$span->setAttribute('http.url', (string)$request->getUri());$response = $handler->handle($request);$span->setAttribute('http.status_code', $response->getStatusCode());$span->setStatus(StatusCode::STATUS_OK);return $response;} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}});$app->get('/users/{id}', function ($request, $response, $args) {$tracer = Globals::tracerProvider()->getTracer('slim');$span = $tracer->spanBuilder('get_user')->startSpan();$scope = $span->activate();try {$span->setAttribute('user.id', $args['id']);// 业务逻辑$user = getUser($args['id']);$span->setStatus(StatusCode::STATUS_OK);return $response->withJson($user);} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}});$app->run();
数据库集成
PDO 集成
<?phpuse OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;function queryDatabase(string $query, array $params = []): array{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('database.query')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();$scope = $span->activate();try {$pdo = new PDO('mysql:host=localhost;dbname=mydb','user','password');$span->setAttribute('db.system', 'mysql');$span->setAttribute('db.name', 'mydb');$span->setAttribute('db.statement', $query);$stmt = $pdo->prepare($query);$stmt->execute($params);$result = $stmt->fetchAll(PDO::FETCH_ASSOC);$span->setStatus(StatusCode::STATUS_OK);return $result;} catch (\PDOException $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
MySQLi 集成
<?phpfunction queryWithMysqli(string $query): array{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('database.query')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();$scope = $span->activate();try {$mysqli = new mysqli('localhost', 'user', 'password', 'mydb');$span->setAttribute('db.system', 'mysql');$span->setAttribute('db.name', 'mydb');$span->setAttribute('db.statement', $query);$result = $mysqli->query($query);$rows = $result->fetch_all(MYSQLI_ASSOC);$span->setStatus(StatusCode::STATUS_OK);return $rows;} catch (\mysqli_sql_exception $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
MongoDB 集成
<?phpuse MongoDB\Client;use OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;function queryMongoDB(string $collection, array $filter): array{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('mongodb.query')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();$scope = $span->activate();try {$client = new Client('mongodb://localhost:27017');$db = $client->selectDatabase('mydb');$collection = $db->selectCollection($collection);$span->setAttribute('db.system', 'mongodb');$span->setAttribute('db.name', 'mydb');$span->setAttribute('db.collection', $collection);$result = $collection->find($filter)->toArray();$span->setStatus(StatusCode::STATUS_OK);return $result;} catch (\Exception $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
Redis 集成
<?phpuse Redis;use OpenTelemetry\API\Globals;use OpenTelemetry\API\Trace\SpanKind;use OpenTelemetry\API\Trace\StatusCode;function getCache(string $key): ?string{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('redis.get')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();$scope = $span->activate();try {$redis = new Redis();$redis->connect('127.0.0.1', 6379);$span->setAttribute('db.system', 'redis');$span->setAttribute('db.operation', 'GET');$span->setAttribute('db.redis.key', $key);$value = $redis->get($key);$span->setStatus(StatusCode::STATUS_OK);return $value;} catch (\Exception $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;} finally {$span->end();$scope->detach();}}
高级配置
自定义采样策略
<?phpuse OpenTelemetry\SDK\Trace\Sampler\SamplerInterface;use OpenTelemetry\SDK\Trace\SamplingResult;class CustomSampler implements SamplerInterface{private array $alwaysSample = ['/checkout', '/payment'];private array $neverSample = ['/health', '/metrics'];public function shouldSample(ContextInterface $context,string $traceId,string $spanName,int $spanKind,array $attributes,array $links): SamplingResult {// 总是采样foreach ($this->alwaysSample as $pattern) {if (str_contains($spanName, $pattern)) {return new SamplingResult(SamplingResult::RECORD_AND_SAMPLE);}}// 从不采样foreach ($this->neverSample as $pattern) {if (str_contains($spanName, $pattern)) {return new SamplingResult(SamplingResult::DROP);}}// 默认采样率 10%return (new TraceIdRatioBasedSampler(0.1))->shouldSample($context, $traceId, $spanName, $spanKind, $attributes, $links);}public function getDescription(): string{return 'CustomSampler';}}
多 Exporter 配置
<?phpuse OpenTelemetry\Exporter\Otlp\OtlpExporter;use OpenTelemetry\Exporter\Zipkin\ZipkinExporter;use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;// 创建多个 exporter$otlpExporter = new OtlpExporter(['endpoint' => 'uptrace-grpc.yodao.cn:4317','headers' => ['uptrace-dsn' => 'http://token@uptrace-grpc.yodao.cn/project?grpc=4317',],]);$zipkinExporter = new ZipkinExporter(['endpoint' => 'http://localhost:9411/api/v2/spans',]);$consoleExporter = new ConsoleSpanExporter();// 添加多个处理器$tracerProvider = (new TracerProviderBuilder())->addSpanProcessor(new BatchSpanProcessor($otlpExporter))->addSpanProcessor(new BatchSpanProcessor($zipkinExporter))->addSpanProcessor(new BatchSpanProcessor($consoleExporter))->build();
性能优化配置
<?phpuse OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;$exporter = new OtlpExporter(['endpoint' => 'uptrace-grpc.yodao.cn:4317','headers' => ['uptrace-dsn' => 'http://token@uptrace-grpc.yodao.cn/project?grpc=4317',],'compression' => 'gzip','timeout' => 10000,]);$spanProcessor = new BatchSpanProcessor($exporter,null, // Clock512, // maxExportBatchSize5000, // scheduledDelayMillis2048, // maxQueueSize30000 // exportTimeoutMillis);$tracerProvider = (new TracerProviderBuilder())->addSpanProcessor($spanProcessor)->build();
最佳实践
1. Context 传递
<?php// ✅ 正确:在函数调用中传递 contextfunction processOrder(ContextInterface $context, string $orderId): void{$tracer = Globals::tracerProvider()->getTracer('my-service');$span = $tracer->spanBuilder('process_order')->setParent($context)->startSpan();$newContext = $span->storeInContext($context);// 传递 context 给下游函数validateOrder($newContext, $orderId);chargePayment($newContext, $orderId);$span->end();}
2. 错误处理
<?php// ✅ 正确:记录错误并设置状态try {processOrder($orderId);$span->setStatus(StatusCode::STATUS_OK);} catch (\Throwable $e) {$span->recordException($e);$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());throw $e;}
3. 资源管理
<?php// ✅ 正确:使用 try-finally 确保 span 结束function operation(): void{$span = $tracer->spanBuilder('operation')->startSpan();$scope = $span->activate();try {// 业务逻辑} finally {$span->end();$scope->detach();}}
故障排查
常见问题
1. 数据未发送到后端
排查步骤:
- 检查环境变量:
echo $OTEL_SERVICE_NAMEecho $OTEL_EXPORTER_OTLP_ENDPOINT
- 检查 PHP 扩展是否加载:
php -m | grep opentelemetry
- 启用调试日志:
export OTEL_LOG_LEVEL=debug
2. Span 未关联
确保正确传递 context:
$newContext = $span->storeInContext($context);downstreamFunction($newContext);
3. 性能问题
- 降低采样率
- 增加批量大小
- 使用异步导出
调试技巧
启用详细日志
export OTEL_LOG_LEVEL=debug
使用 Console Exporter
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;$tracerProvider = (new TracerProviderBuilder())->addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()))->build();
下一步
- 环境变量配置 - 了解所有配置选项
- 高级配置 - 深入了解采样和性能优化
- Uptrace 平台使用 - 了解如何使用 Uptrace 平台
参考资源
- OpenTelemetry PHP Documentation
- OpenTelemetry PHP Zero-Code
- OpenTelemetry PHP GitHub
- PHP Semantic Conventions