本文目录导读:

我来详细介绍Laravel链路追踪的接入方案。
常见链路追踪工具
Zipkin(推荐)
Jaeger
SkyWalking
OpenTelemetry
使用 Zipkin 接入示例
安装依赖
composer require openzipkin/zipkin-laravel composer require guzzlehttp/guzzle
配置文件
创建 config/zipkin.php:
<?php
return [
'enabled' => env('ZIPKIN_ENABLED', true),
'endpoint_url' => env('ZIPKIN_ENDPOINT', 'http://localhost:9411/api/v2/spans'),
'app_name' => env('ZIPKIN_APP_NAME', 'laravel-app'),
'sample_rate' => env('ZIPKIN_SAMPLE_RATE', 1.0), // 采样率 0-1
'timeout' => env('ZIPKIN_TIMEOUT', 1),
'tags' => [
'env' => env('APP_ENV', 'production'),
'version' => env('APP_VERSION', '1.0.0'),
],
];
创建 Zipkin 服务提供者
// app/Providers/ZipkinServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Zipkin\Annotation;
use Zipkin\Endpoint;
use Zipkin\Samplers\BinarySampler;
use Zipkin\TracingBuilder;
use Zipkin\Reporters\Http;
use Illuminate\Support\Facades\Log;
use Zipkin\Instrumentation\Http\Client\HttpClient;
use Zipkin\Instrumentation\Http\Server\PSR15Middleware;
class ZipkinServiceProvider extends ServiceProvider
{
protected $tracer;
protected $tracing;
public function register()
{
$this->app->singleton('zipkin.tracer', function ($app) {
return $this->createTracer();
});
}
public function boot()
{
if (!config('zipkin.enabled')) {
return;
}
$this->app['events']->listen('kernel.handled', function ($request, $response) {
$this->addTraceToResponse($request, $response);
});
}
protected function createTracer()
{
$endpoint = Endpoint::create(
config('zipkin.app_name'),
gethostbyname(gethostname()),
null,
config('zipkin.port', 80)
);
$reporter = new Http(
['endpoint_url' => config('zipkin.endpoint_url')],
function () {
return ['timeout' => config('zipkin.timeout')];
}
);
$sampler = BinarySampler::createAsAlwaysSample();
// 或者使用概率采样
// $sampler = new PercentageSampler(config('zipkin.sample_rate'));
$this->tracing = TracingBuilder::create()
->havingLocalEndpoint($endpoint)
->havingSampler($sampler)
->havingReporter($reporter)
->build();
return $this->tracing->getTracer();
}
protected function addTraceToResponse($request, $response)
{
try {
$tracer = $this->app['zipkin.tracer'];
if ($tracer && $request && $response) {
// 为当前请求创建 span
$span = $tracer->nextSpan();
$span->start();
// 添加请求信息
$span->setName($request->method() . ' ' . $request->path());
$span->tag('http.method', $request->method());
$span->tag('http.path', $request->path());
$span->tag('http.status_code', $response->getStatusCode());
// 添加性能指标
if (defined('LARAVEL_START')) {
$duration = (microtime(true) - LARAVEL_START) * 1000;
$span->tag('duration_ms', round($duration, 2));
}
$span->finish();
$tracer->flush();
}
} catch (\Exception $e) {
Log::error('Zipkin trace error: ' . $e->getMessage());
}
}
}
创建中间件
// app/Http/Middleware/ZipkinTracing.php
<?php
namespace App\Http\Middleware;
use Closure;
use Zipkin\Tracer;
use Illuminate\Support\Facades\Log;
class ZipkinTracing
{
protected $tracer;
public function __construct()
{
$this->tracer = app('zipkin.tracer');
}
public function handle($request, Closure $next)
{
if (!config('zipkin.enabled') || !$this->tracer) {
return $next($request);
}
// 创建 span
$span = $this->tracer->nextSpan();
$span->start();
try {
// 注入 trace 上下文到请求头
$span->inject($request->headers->all(), function ($carrier, $key, $value) {
$carrier[$key] = $value;
});
$response = $next($request);
// 记录响应信息
$span->tag('http.status_code', $response->status());
return $response;
} catch (\Exception $e) {
$span->tag('error', $e->getMessage());
throw $e;
} finally {
$span->finish();
$this->tracer->flush();
}
}
}
注册中间件和服务提供者
// config/app.php
'providers' => [
// ...
App\Providers\ZipkinServiceProvider::class,
],
// app/Http/Kernel.php
protected $middleware = [
// ...
\App\Http\Middleware\ZipkinTracing::class,
];
使用 OpenTelemetry(更现代的方案)
安装依赖
composer require open-telemetry/opentelemetry-auto-laravel composer require open-telemetry/exporter-otlp composer require open-telemetry/sdk
配置文件
// config/opentelemetry.php
<?php
return [
'enabled' => env('OTEL_ENABLED', true),
'service_name' => env('OTEL_SERVICE_NAME', 'laravel-app'),
'endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318'),
'sampler' => [
'type' => env('OTEL_TRACES_SAMPLER', 'always_on'),
'ratio' => env('OTEL_TRACES_SAMPLER_ARG', 1.0),
],
'exporter' => [
'type' => env('OTEL_EXPORTER', 'otlp'),
'timeout' => env('OTEL_EXPORTER_TIMEOUT', 10),
],
'instrumentation' => [
'http_client' => env('OTEL_PHP_INSTRUMENTATION_HTTP_CLIENT', true),
'guzzle' => env('OTEL_PHP_INSTRUMENTATION_GUZZLE', true),
'redis' => env('OTEL_PHP_INSTRUMENTATION_REDIS', true),
'eloquent' => env('OTEL_PHP_INSTRUMENTATION_ELOQUENT', true),
],
];
创建 OpenTelemetry 服务提供者
// app/Providers/OpenTelemetryServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\Exporter\Otlp\OtlpHttpExporter;
use OpenTelemetry\API\Globals;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
class OpenTelemetryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('opentelemetry.tracer', function ($app) {
return $this->createTracerProvider();
});
}
public function boot()
{
if (!config('opentelemetry.enabled')) {
return;
}
// 自动检测框架
if (class_exists(Globals::class)) {
Globals::registerInitializer(function ($builder) {
return $builder;
});
}
// 记录启动时间
$this->app['events']->listen('kernel.handled', function ($request, $response) {
$this->addTraceHeaders($response);
});
}
protected function createTracerProvider()
{
$resource = ResourceInfo::create(Attributes::create([
'service.name' => config('opentelemetry.service_name'),
'telemetry.sdk.name' => 'opentelemetry',
'telemetry.sdk.language' => 'php',
]));
$exporter = new OtlpHttpExporter(
config('opentelemetry.endpoint')
);
$sampler = new ParentBased(
new AlwaysOnSampler()
);
$tracerProvider = TracerProvider::builder()
->setResource($resource)
->addSpanProcessor(new SimpleSpanProcessor($exporter))
->setSampler($sampler)
->build();
return $tracerProvider->getTracer('laravel-app');
}
protected function addTraceHeaders($response)
{
try {
$tracer = $this->app['opentelemetry.tracer'];
$span = $tracer->getCurrentSpan();
if ($span && $response) {
$span->end();
}
} catch (\Exception $e) {
\Log::error('OpenTelemetry error: ' . $e->getMessage());
}
}
}
自定义 Span 示例
在控制器中使用
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
// 使用 OpenTelemetry
if (config('opentelemetry.enabled')) {
$tracer = app('opentelemetry.tracer');
$span = $tracer->spanBuilder('users.list')
->setAttribute('user.type', 'admin')
->startSpan();
}
try {
// 业务逻辑
$users = \App\Models\User::all();
// 添加数据库查询 span
if (isset($span)) {
$span->setAttribute('db.statement', 'SELECT * FROM users');
$span->setAttribute('db.system', 'mysql');
$span->setAttribute('db.count', $users->count());
}
return response()->json($users);
} catch (\Exception $e) {
if (isset($span)) {
$span->setAttribute('error', true);
$span->setAttribute('error.message', $e->getMessage());
}
throw $e;
} finally {
if (isset($span)) {
$span->end();
$tracer->forceFlush();
}
}
}
}
在队列任务中使用
// app/Jobs/ProcessPodcast.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
if (config('opentelemetry.enabled')) {
$tracer = app('opentelemetry.tracer');
$span = $tracer->spanBuilder('job.process')
->setAttribute('job.name', static::class)
->setAttribute('job.queue', $this->queue)
->startSpan();
}
try {
// 任务处理逻辑
sleep(2);
} finally {
if (isset($span)) {
$span->end();
}
}
}
}
Docker Compose 部署 Zipkin
version: '3.8'
services:
zipkin:
image: openzipkin/zipkin:latest
container_name: zipkin
ports:
- "9411:9411"
environment:
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
elasticsearch:
image: elasticsearch:7.9.3
container_name: elasticsearch
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
# 或者使用内存存储(简单部署)
# zipkin-inmemory:
# image: openzipkin/zipkin:latest
# container_name: zipkin-inmemory
# ports:
# - "9411:9411"
常用配置建议
.env 配置
# Zipkin 配置 ZIPKIN_ENABLED=true ZIPKIN_ENDPOINT=http://localhost:9411/api/v2/spans ZIPKIN_APP_NAME=laravel-app ZIPKIN_SAMPLE_RATE=0.5 # OpenTelemetry 配置 OTEL_ENABLED=true OTEL_SERVICE_NAME=laravel-app OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_TRACES_SAMPLER=always_on
采样策略
// 动态采样
public function shouldSample($request)
{
// 只采样 10% 的请求
if (mt_rand(1, 100) > 10) {
return false;
}
// 始终采样错误请求
if ($request->is('api/*')) {
return true;
}
return true;
}
常见问题解决
- 性能影响:设置合适的采样率,避免全量采集
- 内存泄漏:确保 Span 正确结束和释放
- 网络超时:设置合理的超时时间
- 数据丢失:使用缓冲策略,异步发送
选择哪种方案取决于你的具体需求、团队熟悉度和基础设施,Zipkin 更适合传统架构,OpenTelemetry 面向云原生场景。