目录
- 前言
- 一、回到OpenTelemetry原理看问题
- [二、不加载OpenTelemetry SDK实现Trace与Logs示例](#二、不加载OpenTelemetry SDK实现Trace与Logs示例)
前言
前面两篇我们分别介绍了OpenTelemetry原理以及借助PHP OpenTelemetry SDK实现的分布式链路追踪Trace和日志Logs,按理说我们已经可以起飞了✈️。
基于OpenTelemetry的分布式链路追踪Trace实现(PHP篇)
基于OpenTelemetry的日志Logs实现(PHP篇)
但是实际情况可能各有不同,我们知道sdk对php版本的要求至少是v7.4+,0代码自动插桩的话甚至需要v8,有些PHP项目使用的PHP版本相对老旧,而且由于历史的原因升级是一件很麻烦的事。那怎么办呢?不能升级PHP版本就无法使用SDK且无法实现自己项目的OpenTelemetry 布式链路追踪Trace和日志Logs?答案当然是:❌。

一、回到OpenTelemetry原理看问题
我们之前说到,OpenTelemetry 其实就是个协议规范,原理可以回头参看《基于OpenTelemetry的分布式链路追踪Trace实现(PHP篇)》第三章OpenTelemetry的架构原理,也就是说如果遵循规范其实上报就不是问题。
可观测链路 OpenTelemetry 版支持接收应用的链路追踪、指标和日志数据,提供直接上报 和通过 OpenTelemetry Collector 转发 两种上报方式。
Application : 一般的应用程序,同时使用了 OpenTelemetry 的 Library (实现了 API 的 SDK)。
OTel Libraty :也称为 SDK,负责在客户端 程序里采集 观测数据,包括 metrics,traces,logs,对观测数据进行处理,之后观测数据按照 exporter 的不同方式 ,通过 OTLP 方式 发送到 Collector 或者直接发送到 Backend 中。
OTel Collector :负责根据 OpenTelemetry 的协议收集数据 的组件,以及将观测数据导出到外部系统 。这里的协议指的是 OTLP (OpenTelemetry Protocol)。不同的提供商要想能让观测数据持久化到自己的产品里,需要按照 OpenTelemetry 的标准 exporter 的协议接受数据和存储数据。同时社区已经提供了常见开源软件的输出能力,如 Prometheus,Jaeger,Kafka,zipkin 等。
Backend : 负责持久化观测数据 ,Collector 本身不会去负责持久化观测数据 ,需要外部系统提供,在 Collector 的 exporter 部分,需要将 OTLP 的数据格式转换成 Backend 能识别的数据格式。目前社区的已经集成的厂商非常多,除了上述的开源的,常见的厂商包括 AWS,阿里,Azure,Datadog,Dynatrace,Google,Splunk,VMWare 等都实现了 Collector 的 exporter 能力。

otle collector是一种供应商无关的接收、处理和导出遥测数据的方式。如果不是直接发送到后端存储而是通过otel-collector处理转发,则我们需要将otel-collector部署运行起来。
为什么不直接将数据发送到可观测性后端?而是通过otel-collector处理转发?
对于大多数开发语言都有现成的sdk库可以使用,并将可观测数据导出到可观测性后端,例如jaeger,prometheus,对于小规模集群来说,这是一种快速获得价值体现的方式,但是也不利于统一管理。
所以一般来说,我们建议使用otel-collector,因为它允许快速卸载数据,并且otel-collector可以进行很多操作,例如重试、批处理、加密甚至敏感数据过滤。


OpenTelemetry Collector 对追踪数据的处理流程遵循模块化管道设计,主要分为以下四个阶段,并由配置文件中定义的组件顺序决定执行逻辑:
1、数据接收(Receivers)
多协议适配
Collector 通过 OTLP 接收器(Receiver)支持 HTTP 和 gRPC 两种传输协议 ,自动解析请求头中的 Content-Type(如 application/x-protobuf 或application/json) 完成数据解码。例如,HTTP 接收器监听特定端口(如 4318)接收 OTLP/HTTP 格式的追踪数据。
上下文重建
接收器解析请求体中的二进制 Protobuf 数据后,将其转换为包含 TraceID、SpanID、操作名称等上下文信息的标准化内部数据结构。
2、数据处理(Processors)
数据经过接收器后进入处理管道,典型的处理步骤包括:
批处理优化
将多个 Span 合并为批次,减少对后端存储系统的写入压力。
数据过滤
根据配置规则丢弃低价值 Span(如健康检查请求)或敏感字段脱敏。
元数据增强
添加环境变量信息(如 Kubernetes 节点标签)或统一添加自定义业务标签。
错误重试
在网络波动导致导出失败时,通过内存队列暂存数据并重试发送。
3、数据导出(Exporters)
处理后的数据根据配置通过不同导出器发送到目标系统:
后端存储适配
支持 Jaeger、Zipkin、Prometheus 等主流监控系统的专用导出器,也支持通过通用 OTLP 导出器转发到其他 Collector 。
协议转换
自动将 OpenTelemetry 原生格式转换为目标系统兼容的格式(如 Jaeger 的 Thrift 结构)。
我们可以看到Collector 一共遵循两种传输协议:HTTP 和 gRPC,提交的数据可以有两种格式protobuf 和json,可以通过请求头中的Content-Type设定(如 application/x-protobuf 或application/json)。我们还注意到数据接收Receivers的时候还在组织构TraceID、SpanID等以使数据造符合标准化内部数据结构。之前使用SDK的时候这块都是无感知透明化的,也就是SDK已经处理得明明白白了,你只管提交到后端储存就可以啦。
比如我们再次感受下之前通过SDK上报的示例。
php
putenv('OTEL_SERVICE_NAME=demo1');
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
putenv('OTEL_TRACES_EXPORTER=console');//导出协议 console otlp...
putenv('OTEL_METRICS_EXPORTER=none');
putenv('OTEL_LOGS_EXPORTER=console');
#putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc');//上报协议 Traces 支持 grpc、http/protobuf、http/json。 Metrics 支持 grpc、http/protobuf。
#putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318');//上报端点 http:4318,grpc:4317
#putenv('OTEL_EXPORTER_OTLP_HEADERS=');
#putenv('OTEL_PROPAGATORS=b3,baggage,tracecontext');
use OpenTelemetry\API\Globals;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require_once __DIR__ . '/vendor/autoload.php';
$tracer = Globals::tracerProvider()->getTracer('demo-tracer-name');
$app = AppFactory::create();
$app->get('/rolldice', function (Request $request, Response $response) use ($tracer) {
$span = $tracer->spanBuilder('manual-span')->startSpan();
try {
$span->setAttribute('user_id', 12345);
$result = random_int(1, 6);
$response->getBody()->write(strval($result));
$span->addEvent('rolled dice', ['result' => $result])->end();
return $response;
} catch (Exception $e) {
$span->setStatus($e->getCode(), $e->getMessage());
$span->end();
$response->getBody()->write('exception');
return $response;
}
});
$app->run();
现在我们来看这样的一条HTTP上报路径。
bash
[应用代码] --生成Span数据--> [SDK Exporter] --OTLP/HTTP序列化-->
[Collector HTTP接收器] --数据处理管道--> [后端存储]
在这个流程里我们计划采用HTTP的application/json格式上报。如果不使用SDK,那么就意味着我们需要自己完成后端存储节点之前的数据规范处理所有工作(我们甚至是否可以大胆地认为就是 [后端存储] 节点之前的所有工作,包含Collector内的动作)。撇开次要因素不提,主要就是一个构造 OTLP/HTTP JSON trace 数据的过程。那么首先我们得先要知道OTLP协议规范的必备数据格式是什么,下文将以例子直接展示。
二、不加载OpenTelemetry SDK实现Trace与Logs示例
通过上面的原理分析,事情就变得简单有趣了,直接上示例代码。
php
/**
* 上报trace 参考案例
*- 可以通过 HTTP 接口(如 OTLP/HTTP)手动上报追踪数据到 OpenTelemetry Collector。
*- 需要自己构造符合 OTLP JSON 规范的数据包并发送。
*- 推荐优先使用 SDK,接口方式适合特殊场景或兼容老环境。
*
*/
// 需要 PHP 7.1+,并安装 guzzlehttp/guzzle
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
$traceId = bin2hex(random_bytes(16)); // 16字节=32位hex
$spanId = bin2hex(random_bytes(8)); // 8字节=16位hex
$now = (int)(microtime(true) * 1e9); // 纳秒
// 构造 OTLP/HTTP JSON trace 数据
$traceData = [
"resourceSpans" => [
[
"resource" => [
"attributes" => [
[
"key" => "service.name",
"value" => ["stringValue" => "php-manual-otel"]
]
]
],
"scopeSpans" => [
[
"scope" => [
"name" => "manual-php",
"version" => "1.0.0"
],
"spans" => [
[
"traceId" => $traceId,
"spanId" => $spanId,
"name" => "manual-span",
"kind" => 1, // SPAN_KIND_INTERNAL
"startTimeUnixNano" => $now,
"endTimeUnixNano" => $now + 1000000, // +1ms
"attributes" => [
[
"key" => "http.method",
"value" => ["stringValue" => "GET"]
]
],
"status" => [
"code" => 1 // STATUS_CODE_UNSET
]
]
]
]
]
]
]
];
try {
$response = $client->post('http://collector:4318/v1/traces', [
'headers' => [
'Content-Type' => 'application/json',
'xxxx-token' => 'xxx'
],
'body' => json_encode($traceData),
]);
echo "Status: " . $response->getStatusCode() . "\n";
echo "Response: " . $response->getBody() ."| traceId={$traceId}". "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
php
/**
* 上报logs 参考案例
*- 可以通过 HTTP 接口(如 OTLP/HTTP)手动上报追踪数据到 OpenTelemetry Collector。
*- 需要自己构造符合 OTLP JSON 规范的数据包并发送。
*- 推荐优先使用 SDK,接口方式适合特殊场景或兼容老环境。
*
*/
// 需要 PHP 7.1+,并安装 guzzlehttp/guzzle
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
$now = (int)(microtime(true) * 1e9); // 纳秒
// 构造 OTLP/HTTP JSON log 数据
$logData = [
"resourceLogs" => [
[
"resource" => [
"attributes" => [
[
"key" => "service.name",
"value" => ["stringValue" => "php-manual-otel-logs"]
]
]
],
"scopeLogs" => [
[
"scope" => [
"name" => "manual-php",
"version" => "1.0.0"
],
"logRecords" => [
[
"timeUnixNano" => $now,
"severityNumber" => 9, // INFO
"severityText" => "INFO",
"body" => ["stringValue" => "This is a manual log from PHP"],
"attributes" => [
[
"key" => "http.method",
"value" => ["stringValue" => "GET"]
]
]
]
]
]
]
]
]
];
try {
$response = $client->post('http://collector:4318/v1/logs', [
'headers' => [
'Content-Type' => 'application/json',
'xxxx-token' => 'xxx'
],
'body' => json_encode($logData),
]);
echo "Status: " . $response->getStatusCode() . "\n";
echo "Response: " . $response->getBody() . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
至此,我们不加载OpenTelemetry SDK实现Trace与Logs就完成啦。当然这只是个简单的例子抛砖引玉供参考,在实际的应用中不加载SDK终归要自己处理很多业务之外繁琐的事情,甚至与可能丢失Collector 很多优化的环节。正如示例所说,接口方式适合特殊场景或兼容老环境的临时解决方案,能升级版本解决的尽量使用SDK,推荐优先使用 SDK。