通过OpenTelemetry为应用埋点并上报链路数据至云监控2.0后,云监控2.0即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetry为PHP应用进行自动或手动埋点并上报数据。
背景信息
OpenTelemetry PHP支持自动埋点和手动埋点,对PHP的版本要求如下:
自动埋点:已安装 php、composer、pecl,且 php版本≥8.0
手动埋点:已安装 php、composer、pecl,且 php版本≥7.4
支持自动埋点的框架列表如下,完整信息请参见OpenTelemetry官方文档。
步骤一:获取接入点信息
登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择 。
在服务端应用区域单击PHP卡片,然后选择运行环境类型为手动安装,选择接入协议类型为Opentelemetry。
在参数配置区域单击LicenseKey右侧的点击获取,然后根据需求选择埋点方式、连接方式、上报方式,并输入应用名称、版本号和部署环境。
页面下方将会根据配置参数生成相应的接入代码,代码中包含了Endpoint、LicenseKey等接入点信息。
自动埋点(推荐)
创建一个 Slim 框架的 PHP 应用。
这一步将演示如何构建一个简单的 Web 应用程序。如果您已经有编写好的 PHP 应用程序,这一步可跳过。
应用初始化。
mkdir <service name> && cd <service name> composer init \ --no-interaction \ --stability beta \ --require slim/slim:"^4" \ --require slim/psr7:"^1" composer update
编写应用代码。
在<service name>目录下创建一个index.php文件,并添加以下内容。这段代码模拟了扔骰子游戏,返回1~6之间的一个随机数。
<?php use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Factory\AppFactory; require __DIR__ . '/vendor/autoload.php'; $app = AppFactory::create(); $app->get('/rolldice', function (Request $request, Response $response) { $result = random_int(1,6); $response->getBody()->write(strval($result)); return $response; }); $app->run();
此时应用已经编写完成,执行以下命令运行应用,访问地址为
http://localhost:8080/rolldice
。php -S localhost:8080
构建 OpenTelemetry PHP 插件。
下载构建 OpenTelemetry PHP 插件所需要的工具。
# Linux(apt) sudo apt-get install gcc make autoconf # macOS brew install gcc make autoconf
使用 pecl 构建 OpenTelemetry PHP 插件。
pecl install opentelemetry
启用 OpenTelemetry PHP 插件,在 php.ini 文件中添加以下内容。
注意,如果上一步 PHP 扩展构建成功时输出了
Extension opentelemetry enabled in php.ini
,表明插件已经启用,请跳过这一步。[opentelemetry] extension=opentelemetry.so
验证 OpenTelemetry PHP 插件是否启用成功。
php --ri opentelemetry # 预期输出 opentelemetry opentelemetry support => enabled extension version => 1.0.0 # 版本可能不尽相同
为上一步中的应用程序添加自动埋点所需要的额外依赖。
pecl install grpc # 这一步构建时间较长,可能在控制台输出较多内容 composer config allow-plugins.php-http/discovery false composer require \ open-telemetry/sdk \ open-telemetry/opentelemetry-auto-slim \ open-telemetry/exporter-otlp \ php-http/guzzle7-adapter \ open-telemetry/transport-grpc
运行应用。
执行以下命令运行应用。
HTTP上报
env OTEL_PHP_AUTOLOAD_ENABLED=true \ OTEL_SERVICE_NAME=<service name> \ OTEL_LOGS_EXPORTER=none \ OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=<traces.endpoint> \ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<metrics.endpoint> \ OTEL_EXPORTER_OTLP_HEADERS="x-arms-license-key=<license-key>,x-arms-project=<arms-project>,x-cms-workspace=<workspace>" \ OTEL_PROPAGATORS=baggage,tracecontext \ php -S localhost:8080
gRPC上报
env OTEL_PHP_AUTOLOAD_ENABLED=true \ OTEL_SERVICE_NAME=<service name> \ OTEL_LOGS_EXPORTER=none \ OTEL_EXPORTER_OTLP_PROTOCOL=grpc \ OTEL_EXPORTER_OTLP_ENDPOINT=<endpoint> \ OTEL_EXPORTER_OTLP_HEADERS="x-arms-license-key=<license-key>,x-arms-project=<arms-project>,x-cms-workspace=<workspace>" \ OTEL_PROPAGATORS=baggage,tracecontext \ php -S localhost:8080
执行以下命令访问应用程序,OpenTelemetry PHP 插件会自动为应用生成调用链并上报至云监控2.0。
curl http://localhost:8080/rolldice
手动埋点
创建一个 Slim 框架的 PHP 应用。
这一步将演示如何构建一个简单的 Web 应用程序。如果您已经有编写好的 PHP 应用程序,这一步可跳过。
应用初始化。
mkdir <service name> && cd <service name> composer init \ --no-interaction \ --stability beta \ --require slim/slim:"^4" \ --require slim/psr7:"^1" composer update
编写应用代码。
在<service name>目录下创建一个index.php文件,并添加以下内容。这段代码模拟了扔骰子游戏,返回1~6之间的一个随机数。
<?php use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Factory\AppFactory; require __DIR__ . '/vendor/autoload.php'; $app = AppFactory::create(); $app->get('/rolldice', function (Request $request, Response $response) { $result = random_int(1,6); $response->getBody()->write(strval($result)); return $response; }); $app->run();
此时应用已经编写完成,执行以下命令运行应用,访问地址为
http://localhost:8080/rolldice
。php -S localhost:8080
导入 OpenTelemetry PHP SDK 所需依赖。
下载 PHP HTTP 客户端库,用于链路数据上报。
composer require guzzlehttp/guzzle
下载 OpenTelemetry PHP SDK。
composer require open-telemetry/opentelemetry
编写 OpenTelemetry 初始化工具类。
在应用目录下创建 opentelemetry_util.php 文件,并添加以下代码:
HTTP上报
将步骤一获取到的接入点信息替换到以下代码中。
<?php use OpenTelemetry\API\Common\Instrumentation\Globals; use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator; use OpenTelemetry\Contrib\Otlp\SpanExporter; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory; use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\SDK\Sdk; use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler; use OpenTelemetry\SDK\Trace\Sampler\ParentBased; use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor; use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessorBuilder; use OpenTelemetry\SDK\Trace\TracerProvider; use OpenTelemetry\SemConv\ResourceAttributes; use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory; use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; // OpenTelemetry 初始化配置(需要在PHP应用初始化时就进行OpenTelemetry初始化配置) function initOpenTelemetry() { // 1. 设置 OpenTelemetry 资源信息 $resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([ ResourceAttributes::SERVICE_NAME => '<service name>', // 应用名,必填 ResourceAttributes::HOST_NAME => '${hostName}', // 主机名,选填 'acs.cms.workspace' => '<workspace>' // 工作空间名称 ]))); // 2. 创建将 Span 输出到控制台的 SpanExporter,可选 // $spanExporter = new SpanExporter( // (new StreamTransportFactory())->create('php://stdout', 'application/json') // ); $headers = [ 'x-arms-license-key' => "<license-key>", 'x-arms-project' => "<arms-project>", 'x-cms-workspace' => "<workspace>" ]; // 2. 创建通过 HTTP 上报 Span 的 SpanExporter $transport = (new OtlpHttpTransportFactory())->create('<endpoint>','application/x-protobuf', $headers); $spanExporter = new SpanExporter($transport); // 3. 创建全局的 TraceProvider,用于创建 tracer $tracerProvider = TracerProvider::builder() ->addSpanProcessor( (new BatchSpanProcessorBuilder($spanExporter))->build() ) ->setResource($resource) ->setSampler(new ParentBased(new AlwaysOnSampler())) ->build(); Sdk::builder() ->setTracerProvider($tracerProvider) ->setPropagator(TraceContextPropagator::getInstance()) ->setAutoShutdown(true) // PHP 程序退出后自动关闭 tracerProvider,保证链路数据都被上报 ->buildAndRegisterGlobal(); // 将 tracerProvider 添加到全局 } ?>
gRPC上报
将步骤一获取到的接入点信息替换到以下代码中。
<?php use OpenTelemetry\API\Common\Instrumentation\Globals; use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator; use OpenTelemetry\Contrib\Otlp\SpanExporter; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory; use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\SDK\Sdk; use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler; use OpenTelemetry\SDK\Trace\Sampler\ParentBased; use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor; use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessorBuilder; use OpenTelemetry\SDK\Trace\TracerProvider; use OpenTelemetry\SemConv\ResourceAttributes; use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory; use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\API\Signals; // OpenTelemetry 初始化配置(需要在PHP应用初始化时就进行OpenTelemetry初始化配置) function initOpenTelemetry() { // 1. 设置 OpenTelemetry 资源信息 $resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([ ResourceAttributes::SERVICE_NAME => '<service name>', // 应用名,必填 ResourceAttributes::HOST_NAME => '${hostName}', // 主机名,选填 'acs.cms.workspace' => '<workspace>' // 工作空间名称 ]))); // 2. 创建将 Span 输出到控制台的 SpanExporter,可选 // $spanExporter = new SpanExporter( // (new StreamTransportFactory())->create('php://stdout', 'application/json') // ); $headers = [ 'x-arms-license-key' => "<license-key>", 'x-arms-project' => "<arms-project>", 'x-cms-workspace' => "<workspace>" ]; // 2. 创建通过 gRPC 上报 Span 的 SpanExporter $transport = (new GrpcTransportFactory())->create('<endpoint>' . OtlpUtil::method(Signals::TRACE), 'application/x-protobuf', $headers); $spanExporter = new SpanExporter($transport); // 3. 创建全局的 TraceProvider,用于创建 tracer $tracerProvider = TracerProvider::builder() ->addSpanProcessor( (new BatchSpanProcessorBuilder($spanExporter))->build() ) ->setResource($resource) ->setSampler(new ParentBased(new AlwaysOnSampler())) ->build(); Sdk::builder() ->setTracerProvider($tracerProvider) ->setPropagator(TraceContextPropagator::getInstance()) ->setAutoShutdown(true) // PHP 程序退出后自动关闭 tracerProvider,保证链路数据都被上报 ->buildAndRegisterGlobal(); // 将 tracerProvider 添加到全局 } ?>
修改 PHP 应用代码。
在步骤1创建的 index.php 文件基础之上修改代码,调用 initOpenTelemetry 方法初始化 OpenTelemetry 并创建 Span。完整代码如下:
<?php use OpenTelemetry\API\Common\Instrumentation\Globals; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Trace\TracerProvider; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Factory\AppFactory; require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/opentelemetry_util.php'; // OpenTelemetry 初始化(包含设置应用名、Trace导出方式、Trace上报接入点,并创建全局TraceProvider) initOpenTelemetry(); $app = AppFactory::create(); /** * 1. 接口功能:模拟扔骰子,返回一个1-6之间的随机正整数 * 并演示如何创建Span、设置属性、事件、带有属性的事件 */ $app->get('/rolldice', function (Request $request, Response $response) { // 获取 tracer $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer'); // 创建 Span $span = $tracer->spanBuilder("/rolldice")->startSpan(); // 为 Span 设置属性 $span->setAttribute("http.method", "GET"); // 为 Span 设置事件 $span->addEvent("Init"); // 设置带有属性的事件 $eventAttributes = Attributes::create([ "key1" => "value", "key2" => 3.14159, ]); // 业务代码 $result = random_int(1,6); $response->getBody()->write(strval($result)); $span->addEvent("End"); // 销毁 Span $span->end(); return $response; }); /** * 2. 接口功能:模拟扔两个骰子,返回两个1-6之间的随机正整数 * 并演示如何创建嵌套的Span */ $app->get('/rolltwodices', function (Request $request, Response $response) { // 获取 tracer $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer'); // 创建 Span $parentSpan = $tracer->spanBuilder("/rolltwodices/parent")->startSpan(); $scope = $parentSpan->activate(); $value1 = random_int(1,6); $childSpan = $tracer->spanBuilder("/rolltwodices/parent/child")->startSpan(); // 业务代码 $value2 = random_int(1,6); $result = "dice1: " . $value1 . ", dice2: " . $value2; // 销毁 Span $childSpan->end(); $parentSpan->end(); $scope->detach(); $response->getBody()->write(strval($result)); return $response; }); /** * 3. 接口功能:模拟接口发生异常 * 并演示在代码发生异常使用Span记录状态 */ $app->get('/error', function (Request $request, Response $response) { // 获取 tracer $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer'); // 创建 Span $span3 = $tracer->spanBuilder("/error")->startSpan(); try { // 模拟代码发生异常 throw new \Exception('exception!'); } catch (\Throwable $t) { // 设置Span状态为error $span3->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR, "expcetion in span3!"); // 记录异常栈轨迹 $span3->recordException($t, ['exception.escaped' => true]); } finally { $span3->end(); $response->getBody()->write("error"); return $response; } }); $app->run();
执行以下命令运行应用。
php -S localhost:8080
执行以下命令访问应用程序,OpenTelemetry PHP 插件会自动为应用生成调用链并上报至云监控2.0。
curl http://localhost:8080/rolldice