我以larevel框架做分布式全链路追踪为实例
安装必要的包
composer require jaeger/jaeger-client-php
composer require opentracing/opentracing
创建 Service Provider
php artisan make:provider TracingServiceProvider
<?php
// app/Providers/TracingServiceProvider.php
namespace App\Providers;
use Jaeger\Config;
use OpenTracing\GlobalTracer;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;
class TracingServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('jaeger.tracer', function () {
$config = new Config(
[
'sampler' => [
'type' => 'const',
'param' => true,
],
'logging' => true,
'local_agent' => [
'reporting_host' => env('JAEGER_AGENT_HOST', 'localhost'),
'reporting_port' => env('JAEGER_AGENT_PORT', 6831),
],
],
env('APP_NAME', 'laravel-app')
);
return $config->initializeTracer();
});
// 设置全局 tracer
GlobalTracer::set($this->app->make('jaeger.tracer'));
}
public function boot()
{
//
}
}
注册 Service Provider
// config/app.php
'providers' => [
// ...
App\Providers\TracingServiceProvider::class,
],
创建中间件
php artisan make:middleware TracingMiddleware
<?php
// app/Http/Middleware/TracingMiddleware.php
namespace App\Http\Middleware;
use Closure;
use OpenTracing\GlobalTracer;
use OpenTracing\Tags;
use Illuminate\Http\Request;
class TracingMiddleware
{
public function handle(Request $request, Closure $next)
{
$tracer = GlobalTracer::get();
// 从请求头中提取追踪上下文
$spanContext = $tracer->extract(
\OpenTracing\Formats\HTTP_HEADERS,
$request->headers->all()
);
$scope = $tracer->startActiveSpan(
'http.request',
[
'child_of' => $spanContext,
'tags' => [
Tags\HTTP_METHOD => $request->method(),
Tags\HTTP_URL => $request->fullUrl(),
'http.host' => $request->getHost(),
'http.path' => $request->path(),
'http.query' => $request->getQueryString() ?: '',
'http.user_agent' => $request->userAgent(),
'http.client_ip' => $request->ip(),
]
]
);
// 将 span 存储到请求中,供后续使用
$request->attributes->set('tracing_scope', $scope);
try {
$response = $next($request);
$scope->getSpan()->setTag(
Tags\HTTP_STATUS_CODE,
$response->getStatusCode()
);
return $response;
} catch (\Exception $e) {
$scope->getSpan()->setTag(Tags\ERROR, true);
$scope->getSpan()->log([
'event' => 'error',
'error.kind' => get_class($e),
'message' => $e->getMessage(),
'stack' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function terminate($request, $response)
{
if ($request->attributes->has('tracing_scope')) {
$scope = $request->attributes->get('tracing_scope');
$scope->close();
}
}
}
注册中间件
// app/Http/Kernel.php
protected $middleware = [
// ...
\App\Http\Middleware\TracingMiddleware::class,
];
数据库查询追踪
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OpenTracing\GlobalTracer;
use Illuminate\Support\Facades\DB;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
if (config('tracing.enabled', false)) {
DB::listen(function ($query) {
$tracer = GlobalTracer::get();
$scope = $tracer->startActiveSpan('db.query', [
'tags' => [
'db.system' => 'mysql',
'db.statement' => $query->sql,
'db.query.bindings' => json_encode($query->bindings),
'db.query.time' => $query->time . 'ms',
]
]);
$scope->close();
});
}
}
}
HTTP 客户端追踪(Guzzle)
// app/Services/TracingHttpClient.php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use OpenTracing\GlobalTracer;
use OpenTracing\Formats;
use OpenTracing\Tags;
class TracingHttpClient
{
public static function create(): Client
{
$stack = HandlerStack::create();
$stack->push(self::createTracingMiddleware());
return new Client([
'handler' => $stack,
'timeout' => 30,
]);
}
private static function createTracingMiddleware(): callable
{
return function (callable $handler) {
return function ($request, array $options) use ($handler) {
$tracer = GlobalTracer::get();
$scope = $tracer->startActiveSpan('http.client.request', [
'tags' => [
Tags\HTTP_METHOD => $request->getMethod(),
Tags\HTTP_URL => (string) $request->getUri(),
'http.target' => $request->getRequestTarget(),
]
]);
// 注入追踪头
$tracer->inject(
$scope->getSpan()->getContext(),
Formats\HTTP_HEADERS,
$request->getHeaders()
);
return $handler($request, $options)->then(
function ($response) use ($scope) {
$scope->getSpan()->setTag(
Tags\HTTP_STATUS_CODE,
$response->getStatusCode()
);
$scope->close();
return $response;
},
function ($reason) use ($scope) {
$scope->getSpan()->setTag(Tags\ERROR, true);
$scope->getSpan()->log(['error' => $reason]);
$scope->close();
throw $reason;
}
);
};
};
}
}
队列任务追踪
// app/Jobs/TracedJob.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;
use OpenTracing\GlobalTracer;
use OpenTracing\Tags;
class TracedJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $spanContext;
public function __construct(array $spanContext = null)
{
$this->spanContext = $spanContext;
}
public function handle()
{
$tracer = GlobalTracer::get();
$scope = $tracer->startActiveSpan('job.process', [
'child_of' => $this->spanContext,
'tags' => [
'job.name' => static::class,
'job.queue' => $this->queue,
'job.attempts' => $this->attempts(),
]
]);
try {
$this->processJob();
$scope->close();
} catch (\Exception $e) {
$scope->getSpan()->setTag(Tags\ERROR, true);
$scope->getSpan()->log([
'event' => 'error',
'error.kind' => get_class($e),
'message' => $e->getMessage(),
]);
$scope->close();
throw $e;
}
}
protected function processJob()
{
// 实际的作业处理逻辑
}
// 在分发作业时保存追踪上下文
public static function dispatchWithTracing()
{
$tracer = GlobalTracer::get();
$spanContext = $tracer->getActiveSpan()?->getContext();
return new static($spanContext);
}
}
配置文件
php artisan vendor:publish --provider="App\Providers\TracingServiceProvider"
// config/tracing.php
return [
'enabled' => env('TRACING_ENABLED', true),
'service_name' => env('TRACING_SERVICE_NAME', env('APP_NAME', 'laravel-app')),
'jaeger' => [
'agent_host' => env('JAEGER_AGENT_HOST', 'localhost'),
'agent_port' => env('JAEGER_AGENT_PORT', 6831),
],
'sampler' => [
'type' => env('TRACING_SAMPLER_TYPE', 'const'),
'param' => env('TRACING_SAMPLER_PARAM', true),
],
'tags' => [
'environment' => env('APP_ENV', 'production'),
'version' => env('APP_VERSION', '1.0.0'),
],
];
.env 配置
TRACING_ENABLED=true
TRACING_SERVICE_NAME=my-laravel-app
JAEGER_AGENT_HOST=localhost
JAEGER_AGENT_PORT=6831
TRACING_SAMPLER_TYPE=const
TRACING_SAMPLER_PARAM=true
http请求的使用示例
// 在控制器中使用
namespace App\Http\Controllers;
use App\Services\TracingHttpClient;
use OpenTracing\GlobalTracer;
use OpenTracing\Tags;
class UserController extends Controller
{
public function show($id)
{
$tracer = GlobalTracer::get();
$scope = $tracer->startActiveSpan('user.controller.show', [
'tags' => [
'user.id' => $id,
]
]);
try {
// 调用外部服务
$client = TracingHttpClient::create();
$response = $client->get('https://api.example.com/users/' . $id);
$user = json_decode($response->getBody(), true);
$scope->getSpan()->log(['message' => 'User fetched successfully']);
return response()->json($user);
} catch (\Exception $e) {
$scope->getSpan()->setTag(Tags\ERROR, true);
$scope->getSpan()->log(['error' => $e->getMessage()]);
throw $e;
} finally {
$scope->close();
}
}
}