# PHP 零代码插桩

LLMS index: [llms.txt](/llms.txt)

---

## 前置条件 {#requirements}

使用 PHP 进行自动插桩需要满足以下条件：

- PHP 8.0 或更高版本
- [OpenTelemetry PHP 扩展](https://github.com/open-telemetry/opentelemetry-php-instrumentation)
- [Composer 自动加载](https://getcomposer.org/doc/01-basic-usage.md#autoloading)
- [OpenTelemetry SDK](https://packagist.org/packages/open-telemetry/sdk)
- 一个或多个[插桩库](/ecosystem/registry/?component=instrumentation&language=php)
- [配置方法](#configuration)

## 安装 OpenTelemetry 扩展 {#install-the-opentelemetry-extension}

<div class="alert alert-warning" role="alert"><div class="h4 alert-heading" role="heading">重要</div>

仅安装 OpenTelemetry 扩展本身不会生成链路数据。</div>


该扩展可以通过 pecl、[pickle](https://github.com/FriendsOfPHP/pickle)、
[PIE](https://github.com/php/pie) 或
[php-extension-installer](https://github.com/mlocati/docker-php-extension-installer)
（Docker 专用）进行安装。一些 Linux 软件包管理器也提供了预构建版本。

### Linux 软件包 {#linux-packages}

以下渠道提供 RPM 和 APK 包：

- [Remi 仓库](https://blog.remirepo.net/pages/PECL-extensions-RPM-status) - RPM 包
- [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=*pecl-opentelemetry) - APK
  包（目前在 [_testing_ 分支](https://wiki.alpinelinux.org/wiki/Repositories#Testing)）

   <ul class="nav nav-tabs" id="tabs-1" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-01-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-01-00" role="tab"
          data-td-tp-persist="rpm" aria-controls="tabs-01-00" aria-selected="true">
        RPM
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-01-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-01-01" role="tab"
          data-td-tp-persist="apk" aria-controls="tabs-01-01" aria-selected="false">
        APK
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-1-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-01-00" role="tabpanel" aria-labelled-by="tabs-01-00-tab" tabindex="1">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># 以下示例基于 CentOS 7。PHP 版本可通过启用 remi-&lt;version&gt; 来切换，例如：</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &#34;yum config-manager --enable remi-php83&#34;</span>
</span></span><span class="line"><span class="cl">yum update -y
</span></span><span class="line"><span class="cl">yum install -y epel-release yum-utils
</span></span><span class="line"><span class="cl">yum install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm
</span></span><span class="line"><span class="cl">yum-config-manager --enable remi-php81
</span></span><span class="line"><span class="cl">yum install -y php php-pecl-opentelemetry
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">php --ri opentelemetry
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-01-01" role="tabpanel" aria-labelled-by="tabs-01-01-tab" tabindex="1">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="c1"># 截至撰写时，默认 PHP 版本为 8.1。如默认版本变更，可自行替换 php81；</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 也可以使用 &#34;apk add php&lt;version&gt;&#34; 选择其他版本，例如 &#34;apk add php83&#34;。</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing&#34;</span> &gt;&gt; /etc/apk/repositories
</span></span><span class="line"><span class="cl">apk add php php81-pecl-opentelemetry@testing
</span></span><span class="line"><span class="cl">php --ri opentelemetry
</span></span></code></pre></div>
    </div>
</div>


### PECL

1. 设置开发环境。源码安装需要配置好开发环境并安装依赖项：

      <ul class="nav nav-tabs" id="tabs-2" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-02-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-02-00" role="tab"
          data-td-tp-persist="linux (apt)" aria-controls="tabs-02-00" aria-selected="true">
        Linux (apt)
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-02-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-02-01" role="tab"
          data-td-tp-persist="macos (homebrew)" aria-controls="tabs-02-01" aria-selected="false">
        macOS (homebrew)
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-2-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-02-00" role="tabpanel" aria-labelled-by="tabs-02-00-tab" tabindex="2">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo apt-get install gcc make autoconf
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-02-01" role="tabpanel" aria-labelled-by="tabs-02-01-tab" tabindex="2">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">brew install gcc make autoconf
</span></span></code></pre></div>
    </div>
</div>


2. 构建并安装扩展。在准备好开发环境后，使用以下命令安装扩展：

       <ul class="nav nav-tabs" id="tabs-3" role="tablist">
  <li class="nav-item">
      <button class="nav-link active"
          id="tabs-03-00-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-00" role="tab"
          data-td-tp-persist="pecl" aria-controls="tabs-03-00" aria-selected="true">
        pecl
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-03-01-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-01" role="tab"
          data-td-tp-persist="pickle" aria-controls="tabs-03-01" aria-selected="false">
        pickle
      </button>
    </li><li class="nav-item">
      <button class="nav-link"
          id="tabs-03-02-tab" data-bs-toggle="tab" data-bs-target="#tabs-03-02" role="tab"
          data-td-tp-persist="php-extension-installer (docker)" aria-controls="tabs-03-02" aria-selected="false">
        php-extension-installer (docker)
      </button>
    </li>
</ul>

<div class="tab-content" id="tabs-3-content">
    <div class="tab-body tab-pane fade show active"
        id="tabs-03-00" role="tabpanel" aria-labelled-by="tabs-03-00-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pecl install opentelemetry
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-03-01" role="tabpanel" aria-labelled-by="tabs-03-01-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">php pickle.phar install opentelemetry
</span></span></code></pre></div>
    </div>
    <div class="tab-body tab-pane fade"
        id="tabs-03-02" role="tabpanel" aria-labelled-by="tabs-03-02-tab" tabindex="3">
        <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">install-php-extensions opentelemetry
</span></span></code></pre></div>
    </div>
</div>


3. 将扩展添加到 `php.ini` 文件中：

   ```ini
   [opentelemetry]
   extension=opentelemetry.so
   ```

4. 验证扩展是否安装成功并已启用：

   ```sh
   php -m | grep opentelemetry
   ```

## 安装 SDK 和插桩库 {#install-sdk-and-instrumentation-libraries}

在安装完扩展后，你需要安装 OpenTelemetry SDK 和一个或多个插桩库。

PHP 常用库的自动插桩已被支持。完整列表请查阅
[packagist 网站上的插桩库](https://packagist.org/search/?query=open-telemetry&tags=instrumentation)。

假设你的应用使用 Slim Framework 和 PSR-18 HTTP 客户端，并希望使用 OTLP 协议导出链路数据。

你可以安装以下软件包：

```shell
composer require \
    open-telemetry/sdk \
    open-telemetry/exporter-otlp \
    open-telemetry/opentelemetry-auto-slim \
    open-telemetry/opentelemetry-auto-psr18
```

## 配置 {#configuration}

配合 OpenTelemetry SDK 使用时，你可以通过环境变量或 `php.ini` 文件来配置自动插桩行为。

### 环境配置 {#environment-configuration}

```sh
OTEL_PHP_AUTOLOAD_ENABLED=true \
OTEL_SERVICE_NAME=your-service-name \
OTEL_TRACES_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \
OTEL_PROPAGATORS=baggage,tracecontext \
php myapp.php
```

### php.ini 配置 {#phpini-configuration}

将以下内容追加到 `php.ini` 或其他会被 PHP 处理的 ini 文件中：

```ini
OTEL_PHP_AUTOLOAD_ENABLED="true"
OTEL_SERVICE_NAME=your-service-name
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
OTEL_PROPAGATORS=baggage,tracecontext
```

## 运行你的应用 {#run-your-application}

在完成上述安装和配置后，像往常一样运行你的应用即可。

你看到的链路数据取决于你安装的插桩库以及程序运行时的代码路径。以
Slim Framework 和 PSR-18 插桩库为例，你应能看到如下 Span：

- 表示 HTTP 事务的根 Span
- 表示执行操作的 Span
- 每个 PSR-18 客户端发出的 HTTP 请求的 Span

请注意，PSR-18 客户端的插桩会自动为出站 HTTP
请求添加[分布式追踪](/docs/concepts/context-propagation/#propagation)相关的标头。

## 工作原理 {#how-it-works}

<div class="alert alert-primary" role="alert"><div class="h4 alert-heading" role="heading">可选</div>

如果你只想快速开始，并且已有合适的插桩库可用，可以跳过本节。</div>


该扩展支持注册观察函数，用于观察某些类和方法，并在方法执行前后运行这些函数。

如果你的框架或应用没有可用的插桩库，也可以自定义编写。以下是一个示例，
包括待插桩代码，以及如何使用 OpenTelemetry 扩展对其进行追踪：

```php
<?php

use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;

require 'vendor/autoload.php';

/* 待插桩类 */
class DemoClass
{
    public function run(): void
    {
        echo 'Hello, world';
    }
}

/* 自动插桩逻辑 */
OpenTelemetry\Instrumentation\hook(
    class: DemoClass::class,
    function: 'run',
    pre: static function (DemoClass $demo, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
        static $instrumentation;
        $instrumentation ??= new CachedInstrumentation('example');
        $span = $instrumentation->tracer()->spanBuilder('democlass-run')->startSpan();
        Context::storage()->attach($span->storeInContext(Context::getCurrent()));
    },
    post: static function (DemoClass $demo, array $params, $returnValue, ?Throwable $exception) {
        $scope = Context::storage()->scope();
        $scope->detach();
        $span = Span::fromContext($scope->context());
        if ($exception) {
            $span->recordException($exception);
            $span->setStatus(StatusCode::STATUS_ERROR);
        }
        $span->end();
    }
);

/* 运行已插桩代码，生成链路*/
$demo = new DemoClass();
$demo->run();
```

上述示例定义了 `DemoClass`，并为其 `run` 方法注册了 `pre` 和 `post` 钩子。
钩子会在每次方法执行前后运行，`pre` 函数启动 Span，`post` 函数结束 Span。

如果 `DemoClass::run()` 抛出异常，`post` 函数会记录异常，但不会干扰异常传播机制。

## 后续步骤 {#next-steps}

完成应用或服务的自动插桩后，
你可能还希望引入[手动插桩](/docs/languages/php/instrumentation)以收集自定义遥测数据。

更多示例可参考
[opentelemetry-php-contrib/examples](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/examples)。
