本文目录导读:

在 PHP 项目中实现日志链路 ID(Trace ID / Request ID)传递,主要目的是追踪一次完整的请求链路,尤其是在微服务架构或异步任务中,以下是几种从简单到复杂的实现方案:
最基础方案:单请求内(使用 $_SERVER)
适用于单体应用,只需在单个 HTTP 请求内保持同一个 ID。
生成/获取 Trace ID
<?php
class TraceContext
{
private static string $traceId = '';
public static function getTraceId(): string
{
if (self::$traceId === '') {
self::$traceId = self::generateTraceId();
}
return self::$traceId;
}
public static function setTraceId(string $traceId): void
{
self::$traceId = $traceId;
}
private static function generateTraceId(): string
{
// 优先从请求头获取(方便链路追踪)
$headerTraceId = $_SERVER['HTTP_X_TRACE_ID'] ?? '';
if ($headerTraceId && preg_match('/^[a-f0-9]{32}$/', $headerTraceId)) {
return $headerTraceId;
}
// 生成唯一 ID(使用 openssl 更安全)
return bin2hex(random_bytes(16)); // 32位十六进制
// 或者:return uniqid('', true); // 但不太可靠
// 或者:return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', ...); // UUID
}
}
在入口文件中初始化(如 index.php)
<?php // index.php require_once 'TraceContext.php'; // 在业务逻辑开始前设置 TraceContext::setTraceId(TraceContext::getTraceId()); // 后续所有日志都带上 trace_id
自定义日志函数
<?php
function log_info(string $message, array $context = []): void
{
$traceId = TraceContext::getTraceId();
$logData = [
'time' => date('Y-m-d H:i:s'),
'trace_id' => $traceId,
'level' => 'INFO',
'message' => $message,
'context' => $context,
];
// 输出 JSON 日志,方便日志系统解析
echo json_encode($logData, JSON_UNESCAPED_UNICODE) . PHP_EOL;
// 或者写入日志文件
// file_put_contents('/var/log/app.log', json_encode($logData) . PHP_EOL, FILE_APPEND);
}
使用 Monolog(主要方案)
大多数 PHP 框架(Laravel、Symfony)使用 Monolog,可以通过 Processor 添加 Trace ID。
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class TraceIdProcessor
{
public function __invoke(array $record): array
{
$record['extra']['trace_id'] = TraceContext::getTraceId();
$record['extra']['span_id'] = uniqid('span_', true); // 可选的 Span ID
return $record;
}
}
// 初始化 Logger
$logger = new Logger('app');
$logger->pushProcessor(new TraceIdProcessor());
$logger->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::DEBUG));
// 业务中使用
$logger->info('用户下单', ['order_id' => 12345]);
// 输出: {"message":"用户下单","context":{"order_id":12345},"extra":{"trace_id":"abc123..."}}
完整链路传递(跨服务 / 跨进程)
适用于微服务架构,需要将 Trace ID 通过 HTTP 头、消息队列、RPC 等传递。
客户端传递(调用其他服务时)
<?php
class HttpClient
{
public static function get(string $url, array $headers = []): string
{
$traceId = TraceContext::getTraceId();
$headers['X-Trace-Id'] = $traceId;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, self::buildHeaders($headers));
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
服务端接收(从请求头提取)
<?php
// framework/entry.php 或 middleware
$traceId = $_SERVER['HTTP_X_TRACE_ID'] ?? '';
if (preg_match('/^[a-f0-9]{32}$/', $traceId)) {
TraceContext::setTraceId($traceId);
} else {
TraceContext::setTraceId(TraceContext::generateTraceId());
}
MQ 消息传递(如 RabbitMQ、Kafka)
<?php
// 发送消息时
$message = new AMQPMessage($body, [
'headers' => ['X-Trace-Id' => TraceContext::getTraceId()]
]);
// 消费消息时
$traceId = $message->get('application_headers')->getNativeData()['X-Trace-Id'] ?? '';
TraceContext::setTraceId($traceId);
框架集成示例
Laravel(中间件方式)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class TraceIdMiddleware
{
public function handle(Request $request, Closure $next)
{
$traceId = $request->header('X-Trace-Id', '');
if (!preg_match('/^[a-f0-9]{32}$/', $traceId)) {
$traceId = bin2hex(random_bytes(16));
}
// 存入容器,全局可用
app()->instance('trace_id', $traceId);
$response = $next($request);
// 响应头也带上,方便前端排查
$response->headers->set('X-Trace-Id', $traceId);
return $response;
}
}
ThinkPHP / 其他框架
类似,在入口文件或中间件中处理。
跨语言/跨平台传递(标准)
推荐使用 OpenTelemetry 或 Zipkin 的标准:
| 标准 | Header 名称 | 示例值 |
|---|---|---|
| W3C Trace-Context | traceparent |
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 |
| OpenTelemetry | tracestate |
vendor1=value1,vendor2=value2 |
| 自定义 | X-Trace-Id |
abc123... |
| 阿里云 | eagleeye-traceid |
1a2b3c... |
W3C 标准示例
<?php
// 生成 W3C traceparent
function generateTraceParent(): string
{
$traceId = bin2hex(random_bytes(16)); // 32位
$spanId = bin2hex(random_bytes(8)); // 16位
return "00-{$traceId}-{$spanId}-01";
}
// 解析
function parseTraceParent(string $header): ?array
{
if (preg_match('/^00-([a-f0-9]{32})-([a-f0-9]{16})-[01]$/', $header, $matches)) {
return [
'trace_id' => $matches[1],
'span_id' => $matches[2],
];
}
return null;
}
最佳实践建议
- 使用 32 位十六进制(128bit)作为 Trace ID,避免碰撞
- 遵循 W3C 标准,便于与其他语言(Java/Go/Node.js)互通
- 在响应头中返回 Trace ID,方便客户端反馈问题
- 日志统一 JSON 格式,便于日志系统(ELK、Loki)检索
- 异步任务传递:在使用 Job/Queue 时,将 Trace ID 作为参数传递
- 不要过度设计:单一服务、小项目直接用
$_SERVER['REQUEST_TIME_FLOAT']或uniqid()即可
完整示例(单文件简单实现)
<?php
class Trace
{
private static string $id = '';
public static function id(): string
{
if (self::$id === '') {
self::$id = $_SERVER['HTTP_X_TRACE_ID'] ?? '';
if (!preg_match('/^[a-f0-9]{32}$/', self::$id)) {
self::$id = bin2hex(random_bytes(16));
}
}
return self::$id;
}
public static function header(): string
{
return "X-Trace-Id: " . self::id();
}
}
// 日志函数
function log_error(string $msg, array $ctx = []): void
{
$log = [
'timestamp' => date(DATE_ATOM),
'trace_id' => Trace::id(),
'level' => 'ERROR',
'message' => $msg,
'context' => $ctx,
];
error_log(json_encode($log, JSON_UNESCAPED_UNICODE));
}
// HTTP 调用
function http_get(string $url): string
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [Trace::header()]);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
// 使用
Trace::id();
log_error('订单创建失败', ['order' => 10086]);
- 单体应用:简单生成一个 ID 存入全局即可
- 多服务应用:必须通过 HTTP 头/MQ 传递,保证链路一致
- 标准选择:优先 W3C Trace-Context,或各云厂商标准
- 落地关键:日志系统支持按 trace_id 聚合查询(如 ELK、Loki)
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播