Laravel分布式全链路追踪实战

我以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();
        }
    }
}