分布式追踪与 APM
OxPHP 支持 W3C Trace Context 传播、OpenTelemetry(OTel)导出,以及内置的应用性能监控(APM)。传入的 traceparent 头会被解析并延续,trace ID 可在 PHP 中通过 $_SERVER 获取,访问日志包含追踪字段,span 可导出到 Jaeger、Grafana Tempo、Zipkin 或任何兼容 OTLP 的后端。
APM 插件在 OTel 基础之上增加了三层追踪能力:
- 自动埋点 —— 在引擎层挂钩 PHP 内部函数(PDO、mysqli、cURL、Redis、Memcached、文件 I/O);每次调用都会生成一个 span,无需改动任何代码
- 基于注解的追踪 —— 用
#[OxPHP\Apm\Trace]注解任意 PHP 函数或方法即可自动创建 span - PHP SDK —— 提供 10 个
oxphp_apm_*()函数,用于手动创建 span、设置属性、记录事件与错误
工作原理
- 传入请求 —— OxPHP 依据 W3C Trace Context 规范读取
traceparent和tracestate头 - 新 span —— 为本跳生成一个新的 span ID。传入的 span ID 成为其父 span
- 传播到 PHP —— trace ID 被注入到
$_SERVER['OXPHP_TRACE_ID']、$_SERVER['OXPHP_SPAN_ID']和$_SERVER['OXPHP_PARENT_SPAN_ID'] - 访问日志 —— 结构化 JSON 日志包含
trace_id和span_id字段,用于日志关联 - 响应头 —— 更新后的
traceparent头(带有 OxPHP 的 span ID)会被加入响应,以便下游服务延续该追踪 - OTel 导出(可选)—— 当启用 OTel 插件时,每个请求都会成为一个 span,通过 OTLP 携带 HTTP 语义约定属性导出
如果不存在 traceparent 头,OxPHP 会生成新的 trace ID 和 span ID,并开启一条全新的追踪。
配置
W3C Trace Context(内置)
| 变量 | 默认值 | 说明 |
|---|---|---|
TRACE_CONTEXT |
false |
启用 W3C Trace Context 传播。设为 true 或 1 |
OpenTelemetry 插件
OTel 插件是一个编译期特性(plugin-otel)。启用后,它会自动开启 trace-context 传播(效果等同于设置 TRACE_CONTEXT=true)。
| 变量 | 默认值 | 说明 |
|---|---|---|
OTEL_ENABLED |
false |
启用 OpenTelemetry 插件。布尔值 —— 参见 布尔值 |
OTEL_EXPORTER_OTLP_PROTOCOL |
grpc |
导出协议:grpc 或 http/protobuf |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4317(gRPC)或 http://localhost:4318(HTTP) |
OTLP collector 端点。https:// URL 会在两种传输方式上通过 TLS 导出,并针对系统信任库进行校验(运行时镜像必须自带 CA 证书包,例如 ca-certificates —— 官方镜像已安装它);尚不支持自定义 CA 证书包和 mTLS |
OTEL_EXPORTER_OTLP_TIMEOUT |
10000 |
导出超时,单位毫秒 |
OTEL_EXPORTER_OTLP_HEADERS |
(未设置) | 认证头:key=value,key2=value2 |
OTEL_SERVICE_NAME |
oxphp |
导出 span 中的服务名 |
OTEL_SERVICE_VERSION |
(未设置) | 服务版本属性 |
OTEL_RESOURCE_ATTRIBUTES |
(未设置) | 额外的资源属性:env=prod,region=us-east-1 |
OTEL_TRACES_SAMPLER |
parentbased_traceidratio |
采样策略:always_on、always_off、traceidratio、parentbased_always_on、parentbased_always_off、parentbased_traceidratio |
OTEL_TRACES_SAMPLER_ARG |
1.0 |
基于比例的采样器的采样比率(0.0–1.0) |
超出范围或无效的 OTEL_TRACES_SAMPLER_ARG 值会被钳制到 [0.0, 1.0],并以 warn 级别记录日志。无法识别的 OTEL_TRACES_SAMPLER 值会回退到 parentbased_traceidratio 并记录日志。
APM 插件
APM 插件是一个编译期特性(plugin-apm),依赖 OTel 插件。它增加了自动埋点、#[OxPHP\Apm\Trace] 装饰器以及 PHP 追踪 SDK。
| 变量 | 默认值 | 说明 |
|---|---|---|
OTEL_APM_ENABLED |
false |
启用 APM:自动埋点、错误捕获、PHP SDK。需要 OTEL_ENABLED=true。布尔值 —— 参见 布尔值 |
OTEL_APM_SLOW_QUERY_MS |
100 |
慢查询阈值,单位毫秒。超过此值的查询会在其 span 上被标记 oxphp.db.slow=true |
OTEL_APM_DB_CAPTURE_PARAMS_ENABLED |
false |
在 db.params span 属性中记录绑定参数。**值按原样记录——与 db.statement 不同,它们不会被脱敏,因此可能把 PII(邮箱、令牌等)放进你的追踪数据。只在可以接受这一点的场合启用。**布尔值 —— 参见 布尔值 |
OTEL_APM_STACKTRACE_MAX_BYTES |
8192 |
exception.stacktrace 属性的最大字节数。超过上限时会从尾部截断调用栈(保留根帧),并加上 …(truncated) 标记。0 表示禁用截断 |
OTEL_APM_MESSAGE_MAX_BYTES |
4096 |
exception.message 属性的最大字节数(默认值与 New Relic 的单属性值长度上限一致)。超过上限时会从尾部截断消息,并加上 …(truncated) 标记。0 表示禁用截断 |
PHP 中的 trace context
当 TRACE_CONTEXT=true 时,你的 PHP 脚本中可以使用三个 $_SERVER 变量:
| 变量 | 说明 | 示例 |
|---|---|---|
OXPHP_TRACE_ID |
W3C trace ID(32 个十六进制字符) | 4bf92f3577b34da6a3ce929d0e0e4736 |
OXPHP_SPAN_ID |
本次请求的 OxPHP span ID(16 个十六进制字符) | 00f067aa0ba902b7 |
OXPHP_PARENT_SPAN_ID |
传入的父 span ID(16 个十六进制字符,若为新追踪则为空) | a3ce929d0e0e4736 |
利用它们把 trace context 传播到下游服务:
<?php
$traceId = $_SERVER['OXPHP_TRACE_ID'] ?? '';
$spanId = $_SERVER['OXPHP_SPAN_ID'] ?? '';
if ($traceId) {
// Build a traceparent header for downstream calls
$traceparent = "00-{$traceId}-{$spanId}-01";
$response = file_get_contents('https://api.example.com/data', false,
stream_context_create([
'http' => [
'header' => "traceparent: {$traceparent}\r\n",
],
])
);
}配合 Guzzle
<?php
$traceId = $_SERVER['OXPHP_TRACE_ID'] ?? '';
$spanId = $_SERVER['OXPHP_SPAN_ID'] ?? '';
$client = new \GuzzleHttp\Client();
$response = $client->get('https://api.example.com/users', [
'headers' => [
'traceparent' => "00-{$traceId}-{$spanId}-01",
],
]);访问日志关联
启用 trace context 后,结构化 JSON 访问日志会包含 trace_id 和 span_id 字段:
{
"timestamp": "2026-03-23T10:15:30.123Z",
"level": "INFO",
"fields": {
"request_id": "4bf92f3577b34da6-00f067aa",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"method": "GET",
"path": "/api/users",
"status": 200,
"duration_us": 1523,
"remote_ip": "10.0.0.1",
"message": "request completed"
}
}随后你就可以在日志聚合系统(Loki、Elasticsearch、Splunk、CloudWatch)中按 trace ID 搜索日志,找到某条分布式追踪对应的每一条日志记录。
响应头
OxPHP 会为每个响应加上 traceparent 头,携带 OxPHP 自己的 span ID:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01如果传入请求包含 tracestate 头,它也会一并转发到响应中。
OpenTelemetry 集成
启用 OTel 插件后,每个 HTTP 请求都会成为一个 span,通过 OTLP 导出到你的追踪后端。
Span 属性
导出的 span 包含标准的 HTTP 语义约定属性:
| 属性 | 说明 |
|---|---|
http.request.method |
HTTP 方法(GET、POST 等) |
url.path |
请求路径 |
http.response.status_code |
响应状态码 |
client.address |
客户端 IP 地址 |
server.address |
服务器监听地址 |
oxphp.request_id |
OxPHP 请求 ID |
http.request.body.size |
请求体大小,单位字节(若非零) |
http.response.body.size |
响应体大小,单位字节(若非零) |
5xx 响应会被标记为错误 span。
Span 事件
子 span 还会携带 span 事件 —— 带时间戳的注解,作为
OpenTelemetry 事件导出,并被 Jaeger、Grafana Tempo 及其他
OTLP 后端原生渲染。每个事件上的 oxphp.event.kind 属性用于标识其类型:
oxphp.event.kind |
来源 | 事件属性 |
|---|---|---|
exception |
抛出异常的 #[OxPHP\Apm\Trace] 函数、oxphp_apm_error(),或落在请求根 span 上的未处理异常/致命错误(参见根 span 异常自动捕获) |
exception.type、exception.message、exception.stacktrace |
custom |
oxphp_apm_event() |
用户提供 |
mark |
性能分析器 #[Mark] 注解 |
用户提供 |
slow |
性能分析器 #[SlowThreshold] 触发 |
threshold_ms、elapsed_ms |
memory_spike |
性能分析器 #[MemoryThreshold] 触发 |
threshold_kb、delta_bytes |
对于由 APM 埋点生成的事件,oxphp.event.kind 属性还可能额外携带 sql、http 或 alloc。
根 span 异常自动捕获
当请求因未处理的异常或致命错误失败并返回 5xx 时,OxPHP 会自动把一个 exception 事件附加到该请求的根 span 上——不需要 #[OxPHP\Apm\Trace] 属性,也不需要调用 oxphp_apm_error()。一次 500 在追踪里因此变得自我描述,而按异常事件给错误分组的后端(例如 New Relic Errors Inbox)无需任何应用代码就会亮起来。
该事件携带标准的 exception.type、exception.message 和 exception.stacktrace,外加两个 OxPHP 扩展属性 exception.file 和 exception.line,指向抛出点(或致命错误的位置)。对于无类致命错误——trigger_error(…, E_USER_ERROR)、内存耗尽中止、执行超时——exception.type 是一个合成名称(PHP 错误常量,例如 E_USER_ERROR),并且没有调用栈。调用未定义的函数在 PHP 8 上不是无类致命错误:它抛出一个普通的 Error,像任何其他异常一样携带完整调用栈。消息和调用栈遵循与其他 exception 事件相同的 OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES 上限。
这对原生 PHP、没有自定义异常处理器的应用以及工作进程模式的处理器都有效。
**边界:吞掉异常的框架(传统请求路径)。**在 Traditional / Framework / SPA 模式下,安装了 set_exception_handler() 并渲染自己错误页面的应用(Laravel、Symfony、WordPress……)从引擎的角度看已经处理了异常。它从未以未捕获的方式向上传播,所以 OxPHP 只看到 500 状态码,无法找回那个 Throwable。这类请求不会触发自动捕获;请在框架的错误上报器里用 oxphp_apm_error($e) 显式记录该异常。
**工作进程模式不经过 set_exception_handler()。**工作进程运行时会直接捕获逃出你 oxphp_worker() 闭包的异常,而不调用引擎的用户异常处理器。所以对工作进程处理器来说,只要异常离开了闭包,自动捕获就会触发——即使代码注册了自己的 set_exception_handler();那个处理器只作用于闭包自己捕获的异常,不作用于逃出闭包的异常。
流式响应。对于流式响应(SSE,或任何 oxphp_stream_flush() 循环),HTTP 状态在响应头上线的那一刻就固定了,请求也在那一刻被视为已完成。在状态提交之后抛出的致命错误——在流式响应上,或在调用过 finish_request() 的响应上——只会被记录到日志;它不会被加到根 span 上(追踪里仍有该 span,只是没有 exception 事件)。这是一条成文的边界,对已提交的 5xx 和已提交的 2xx 同样成立。
配合 OTel 的请求 ID
当 OTel 插件处于激活状态时,请求 ID 会从 trace context 派生:取 trace ID 的前 16 个字符与 span ID 的前 8 个字符,用短横线分隔。它会出现在日志、X-Request-ID 响应头以及 PHP 的 oxphp_request_id() 中。
APM:自动埋点
启用 APM 插件后,OxPHP 会在引擎层自动挂钩 34 个 PHP 内部函数。每次调用被挂钩的函数都会在当前请求的根 span 下创建一个子 span —— 无需改动任何代码。
被挂钩的函数
| 类别 | 函数 |
|---|---|
| PDO | PDO::__construct、PDO::query、PDO::exec、PDO::prepare、PDOStatement::execute |
| mysqli | mysqli::__construct、mysqli::query、mysqli::prepare、mysqli_stmt::prepare、mysqli_stmt::execute |
| cURL | curl_init、curl_setopt、curl_exec、curl_multi_exec |
| Redis | Redis::connect、Redis::get、Redis::set、Redis::del、Redis::mget、Redis::mset、Redis::hget、Redis::hset、Redis::lpush、Redis::rpush |
| Memcached | Memcached::get、Memcached::set、Memcached::delete、Memcached::getMulti、Memcached::setMulti |
| 文件 I/O | fopen、fread、fwrite、file_get_contents、file_put_contents |
只有对实际已加载的扩展才会安装挂钩。如果你的构建不包含 Redis 扩展,Redis 挂钩会被静默跳过。
数据库 span 属性
数据库挂钩(PDO、mysqli)会用 OpenTelemetry 语义约定属性装饰其 span:
| 属性 | 来源 | 示例 |
|---|---|---|
db.statement |
查询文本,字面值被替换为 ?,因此 PII(邮箱、令牌)永远不会到达追踪后端 |
SELECT * FROM users WHERE email = ? |
db.operation |
开头的 SQL 关键字 | SELECT |
db.system |
从 PDO DSN / mysqli 构造函数解析 | mysql、postgresql、sqlite |
server.address、server.port |
连接的主机/端口(unix socket 或 SQLite 文件时省略) | db.internal、5432 |
db.name |
数据库名,SQLite 则为文件路径 | shop |
oxphp.db.slow |
当调用的墙钟时间达到或超过 OTEL_APM_SLOW_QUERY_MS 时为 true |
true |
db.params |
绑定参数,按**原样(不脱敏)**记录——因此可能包含 PII;仅当 OTEL_APM_DB_CAPTURE_PARAMS_ENABLED=true 时才记录 |
[1, active] |
db.statement 从每次 query / exec / prepare 调用自己的参数中读取,因此出现在对应的 span 上。在 PDOStatement::execute 的 span 上它也存在,读取的是语句对象自身的 queryString 属性,因此绝不可能是另一条语句的 SQL。mysqli_stmt::execute 的 span 没有 db.statement(mysqli 没有暴露这样的属性),但 SQL 在它之前的 mysqli::prepare span 上。每个 execute span 都携带计时——连同慢查询标记——对 PDO 还携带 db.params。缓存(Redis、Memcached)、HTTP 客户端(cURL)和文件 I/O 挂钩只产生一个纯计时 span。
挂钩安装
挂钩安装采用两阶段设计,以保证 PHP ZTS 下的线程安全:
- 阶段 1(MINIT) —— 在模块初始化期间,OxPHP 会针对已加载的扩展校验每个目标函数,并将原始处理器指针捕获到一个只读的已批准列表中
- 阶段 2(RINIT) —— 在每个工作线程处理首个请求时,已批准的挂钩会被安装到该线程的函数表中
这确保了每个 ZTS 工作线程都拥有一致的函数表修改和线程本地状态。
APM:基于注解的追踪
#[OxPHP\Apm\Trace] 注解会自动围绕被装饰的函数和方法创建 span。与自动埋点挂钩(针对内部 C 函数)不同,它作用于用户定义的 PHP 代码。
<?php
use OxPHP\Apm\Trace;
#[Trace]
function processOrder(int $orderId): void
{
// A span named "processOrder" is created on entry and closed on exit.
// If an exception is thrown, the span is marked as error and an
// "exception" span event records exception.type, exception.message
// and exception.stacktrace.
}
class PaymentService
{
#[Trace]
public function charge(float $amount): bool
{
// Span named "PaymentService::charge"
return true;
}
}#[Trace] 注解同时作用于函数和方法。无需任何注册调用 —— APM 插件会在初始化期间自动注册该装饰器。
如果被装饰的函数抛出异常,span 的状态会被设为错误,并记录一个 exception 事件,携带完整的 OpenTelemetry 语义约定数据:exception.type(类名)、exception.message(消息)以及 exception.stacktrace(来自 getTraceAsString() 的调用栈)。消息会被截断到 OTEL_APM_MESSAGE_MAX_BYTES 字节(默认 4096),调用栈会被截断到 OTEL_APM_STACKTRACE_MAX_BYTES 字节(默认 8192);0 可禁用任一上限。帧内的参数捕获遵循 PHP 自身的 zend.exception_ignore_args 设置。
APM:PHP 追踪 SDK
APM 插件注册了 10 个 oxphp_apm_*() 函数,用于手动管理 span。所有函数在 APM 被禁用时都是安全的空操作,因此你的代码无需修改即可在任何环境中运行。
创建 span
<?php
// Start a span and get its local ID
$spanId = oxphp_apm_start('cache.warm', ['cache.size' => '1024']);
// ... do work ...
// Close the span
oxphp_apm_end($spanId);添加属性和事件
<?php
$spanId = oxphp_apm_start('order.process');
// Add attributes to the current span (or a specific one)
oxphp_apm_attribute('order.id', $orderId);
oxphp_apm_attribute('order.total', $total, $spanId);
// Record an event on the span
oxphp_apm_event('payment.authorized', [
'provider' => 'stripe',
'amount' => (string) $amount,
]);
oxphp_apm_end($spanId);记录错误
<?php
$spanId = oxphp_apm_start('external.api');
try {
$result = callExternalApi();
} catch (\Throwable $e) {
// Mark the span as error
oxphp_apm_error($e, $spanId);
throw $e;
} finally {
oxphp_apm_end($spanId);
}传播 trace context
<?php
// Get the current trace ID and span ID
$traceId = oxphp_apm_trace_id();
$currentSpanId = oxphp_apm_span_id();
// Or get a ready-to-use traceparent header value
$traceparent = oxphp_apm_header();
// "00-{trace_id}-{span_id}-01"
// Propagate to downstream services
$response = file_get_contents('https://api.example.com/data', false,
stream_context_create([
'http' => [
'header' => "traceparent: {$traceparent}\r\n",
],
])
);函数参考
| 函数 | 返回值 | 说明 |
|---|---|---|
oxphp_apm_trace(name, callback, ?attributes) |
void |
在一个 span 内执行回调(保留供将来使用) |
oxphp_apm_start(name, ?attributes) |
int |
开启一个 span 并返回其本地 ID。APM 被禁用时返回 0 |
oxphp_apm_end(span_id) |
void |
关闭给定本地 ID 的 span |
oxphp_apm_attribute(key, value, ?span_id) |
void |
在当前或指定的 span 上设置一个属性 |
oxphp_apm_event(name, ?attributes, ?span_id) |
void |
在当前或指定的 span 上记录一个带时间戳的事件 |
oxphp_apm_error(exception, ?span_id) |
void |
将当前或指定的 span 标记为错误并记录一个 exception 事件。Throwable 对象会贡献 exception.type、exception.message 和 exception.stacktrace;而单纯的字符串参数会被记录为 exception.message,并归入通用的 exception.type(值为 Error),以便在按类型分组的后端中该事件仍然可见 |
oxphp_apm_status(code, ?description, ?span_id) |
void |
设置 span 状态:0 = Unset,1 = Ok,2 = Error |
oxphp_apm_trace_id() |
string |
当前 trace ID(32 个十六进制字符)。APM 被禁用时为空 |
oxphp_apm_span_id() |
string |
当前 span ID(16 个十六进制字符)。没有活动 span 时为空 |
oxphp_apm_header() |
string |
当前 span 上下文的 W3C traceparent 头值 |
完整的函数签名参考,请参见 PHP 函数。
Docker 示例
开箱即用的 compose.yaml 变体:
在没有外部后端的情况下启用 W3C trace 传播:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- TRACE_CONTEXT=true
- INTERNAL_ADDR=0.0.0.0:9090以 Jaeger 作为追踪后端的完整可观测性栈:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_SERVICE_VERSION=1.0.0
- OTEL_RESOURCE_ATTRIBUTES=env=production
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPCservices:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317
- OTEL_SERVICE_NAME=my-app
tempo:
image: grafana/tempo:latest
ports:
- "4317:4317"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"对数据库查询、HTTP 调用、缓存操作和文件 I/O 进行自动埋点的完整可观测性:
services:
app:
image: ghcr.io/oxphp/oxphp:0.11.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_APM_ENABLED=true
- OTEL_APM_SLOW_QUERY_MS=50
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
environment:
- COLLECTOR_OTLP_ENABLED=true必须在构建时启用 plugin-apm Cargo 特性。官方 OxPHP 镜像默认包含它。
可观测性栈
OxPHP 提供三大协同工作的可观测性支柱:
| 支柱 | 特性 | 关联方式 |
|---|---|---|
| 指标(Metrics) | 位于 /metrics 的 Prometheus 计数器和直方图 |
聚合的性能数据 |
| 日志(Logging) | 通过 ACCESS_LOG 输出的结构化 JSON 访问日志 |
每请求级别的细节,可按 trace_id 搜索 |
| 追踪(Tracing) | W3C Trace Context + OTLP 导出 | 端到端的分布式请求流 |
三者共享同一个 trace_id 和 request_id,因此你可以从 Grafana 仪表盘的告警一路下钻到 Tempo 追踪,再到某个请求对应的 Loki 日志行。
排查故障
响应中没有出现追踪头
TRACE_CONTEXT 未启用。
修复: 设置 TRACE_CONTEXT=true,或用 OTEL_ENABLED=true 启用 OTel 插件(它会自动启用 trace context)。
$_SERVER 追踪变量为空
trace context 被禁用,或者这些变量是在 OxPHP 之外被检查的。
检查: OXPHP_TRACE_ID、OXPHP_SPAN_ID 和 OXPHP_PARENT_SPAN_ID 变量仅在 TRACE_CONTEXT=true 且请求由 OxPHP 处理时才存在。测试方法:
<?php
echo $_SERVER['OXPHP_TRACE_ID'] ?? 'trace context not enabled';Jaeger/Tempo 中没有出现 span
检查: 确认从 OxPHP 容器可以访问 OTLP 端点:
docker compose exec app curl -v http://jaeger:4317检查: 确认插件已启用:
curl -s http://localhost:9090/config | jq '.plugins'修复: 确保 OTEL_ENABLED=true,且 OTEL_EXPORTER_OTLP_ENDPOINT 指向正确的 collector 地址。
生产环境采样量过高
在高流量下导出每一个 span 代价高昂。
修复: 降低采样比率:
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of traces基于父级的采样意味着:如果传入请求携带的是已采样的追踪,那么无论比率如何它都会被采样。在 OxPHP 处开启的新追踪则按配置的比率采样。如果 OTEL_TRACES_SAMPLER 被设为无法识别的值,OxPHP 会记录一条警告并回退到 parentbased_traceidratio。