通过OpenTelemetry上报PHP应用

通过OpenTelemetry为应用埋点并上报链路数据至云监控2.0后,云监控2.0即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetryPHP应用进行自动或手动埋点并上报数据。

背景信息

OpenTelemetry PHP支持自动埋点和手动埋点,对PHP的版本要求如下:

  • 自动埋点:已安装 php、composer、pecl,且 php版本≥8.0

  • 手动埋点:已安装 php、composer、pecl,且 php版本≥7.4

支持自动埋点的框架列表如下,完整信息请参见OpenTelemetry官方文档

展开查看支持监控的PHP框架

  • CodeIgniter Framework

  • PHP HTTP Async Client

  • PHP IO

  • Laravel

  • MongoDB

  • PDO

  • PSR-15 middleware

  • PSR-18 HTTP clients

  • PSR-3

  • Slim

  • Symfony

  • WordPress

  • Yii

步骤一:获取接入点信息

  1. 登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择接入中心 > 接入中心

  2. 服务端应用区域单击PHP卡片,然后选择运行环境类型手动安装,选择接入协议类型Opentelemetry

  3. 参数配置区域单击LicenseKey右侧的点击获取,然后根据需求选择埋点方式连接方式上报方式,并输入应用名称版本号部署环境

    页面下方将会根据配置参数生成相应的接入代码,代码中包含了Endpoint、LicenseKey等接入点信息。

    image

自动埋点(推荐)

  1. 创建一个 Slim 框架的 PHP 应用。

    这一步将演示如何构建一个简单的 Web 应用程序。如果您已经有编写好的 PHP 应用程序,这一步可跳过。

    1. 应用初始化。

      mkdir <service name> && cd <service name>
        
      composer init \
        --no-interaction \
        --stability beta \
        --require slim/slim:"^4" \
        --require slim/psr7:"^1"
      composer update
    2. 编写应用代码。

      在<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();
    3. 此时应用已经编写完成,执行以下命令运行应用,访问地址为http://localhost:8080/rolldice

      php -S localhost:8080
  2. 构建 OpenTelemetry PHP 插件。

    1. 下载构建 OpenTelemetry PHP 插件所需要的工具。

      # Linux(apt)
      sudo apt-get install gcc make autoconf
      
      # macOS
      brew install gcc make autoconf
    2. 使用 pecl 构建 OpenTelemetry PHP 插件。

      pecl install opentelemetry
    3. 启用 OpenTelemetry PHP 插件,在 php.ini 文件中添加以下内容。

      注意,如果上一步 PHP 扩展构建成功时输出了 Extension opentelemetry enabled in php.ini,表明插件已经启用,请跳过这一步。

      [opentelemetry]
      extension=opentelemetry.so
    4. 验证 OpenTelemetry PHP 插件是否启用成功。

      php --ri opentelemetry
      
      # 预期输出
      opentelemetry
      opentelemetry support => enabled
      extension version => 1.0.0 # 版本可能不尽相同
  3. 为上一步中的应用程序添加自动埋点所需要的额外依赖。

    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
  4. 运行应用。

    1. 执行以下命令运行应用。

      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
    2. 执行以下命令访问应用程序,OpenTelemetry PHP 插件会自动为应用生成调用链并上报至云监控2.0。

      curl http://localhost:8080/rolldice

手动埋点

  1. 创建一个 Slim 框架的 PHP 应用。

    这一步将演示如何构建一个简单的 Web 应用程序。如果您已经有编写好的 PHP 应用程序,这一步可跳过。

    1. 应用初始化。

      mkdir <service name> && cd <service name>
        
      composer init \
        --no-interaction \
        --stability beta \
        --require slim/slim:"^4" \
        --require slim/psr7:"^1"
      composer update
    2. 编写应用代码。

      在<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();
    3. 此时应用已经编写完成,执行以下命令运行应用,访问地址为http://localhost:8080/rolldice

      php -S localhost:8080
  2. 导入 OpenTelemetry PHP SDK 所需依赖。

    • 下载 PHP HTTP 客户端库,用于链路数据上报。

      composer require guzzlehttp/guzzle
    • 下载 OpenTelemetry PHP SDK。

      composer require open-telemetry/opentelemetry
  3. 编写 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 添加到全局
    }
    ?>
  4. 修改 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();
  5. 执行以下命令运行应用。

    php -S localhost:8080

    执行以下命令访问应用程序,OpenTelemetry PHP 插件会自动为应用生成调用链并上报至云监控2.0。

    curl http://localhost:8080/rolldice

查看监控数据

  1. 登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择应用中心 > 运维监控 > 应用监控

  2. 应用列表页面单击目标应用名称,然后查看对应的监控详情。更多信息,请参见应用监控