vinelab/tracing-laravel

简化Laravel分布式追踪

v2.3.2 2023-11-09 08:01 UTC

README

简介

分布式追踪是跟踪对应用程序请求产生活动的过程。使用此功能,您可以:

  • 追踪请求在复杂系统中的路径
  • 发现该路径上组件(服务)的延迟
  • 知道路径中的哪个组件正在创建瓶颈
  • 检查组件之间发送的有效载荷
  • 为每个单独的组件构建执行图等

简单来说,分布式追踪是一种知识工具。在项目中拥有它的一大优点是,开发者可以通过简单地跟踪它产生的痕迹来了解系统。

看看Uber如何使用分布式追踪来理解其产品中的大量微服务和交互。

分布式追踪由多个跨度组成,这些跨度表示在服务或这些服务的资源中花费的时间。

每个跨度具有以下内容:

  • 操作名称
  • 开始时间戳
  • 结束时间戳
  • 一组零个或多个键值对标签,以启用查找并记录附加信息
  • 一组零个或多个与时间戳配对的日志
  • 与相关跨度(例如父跨度)的引用

跨度通常以时间轴的形式显示,每个跨度都可以展开以查看更多详细信息

image

Tracer接口(通过Trace外观)创建跨度并了解如何在进程边界之间注入(序列化)和提取(反序列化)它们。

有关分布式追踪背后的语义的更多详细信息,请参阅OpenTracing规范

需求

此包需要PHP >= 7.1Laravel 5.5或更高版本。我们还提供有限的Lumen支持(基本的追踪和HTTP中间件)。

安装

首先,通过Composer安装该包

composer require vinelab/tracing-laravel

安装后,您可以使用vendor:publish命令发布包配置。此命令会将tracing.php配置文件发布到您的配置目录

php artisan vendor:publish --provider="Vinelab\Tracing\TracingServiceProvider"

您应该在您的.env文件中配置驱动程序和服务名称

TRACING_DRIVER=zipkin
TRACING_SERVICE_NAME=orders

您还应该添加以下部分中描述的相应驱动程序的凭据。

有关Lumen,请参阅专用部分中的安装说明。

驱动器先决条件

Zipkin

使用以下环境变量来配置Zipkin

ZIPKIN_HOST=localhost
ZIPKIN_PORT=9411

如果无法通过给定的主机名访问收集器,您可能会在每次请求中看到有关该情况的调试消息。如果希望在生产中忽略这些消息,只需编辑您的日志配置以省略debug级别的消息。

Jaeger

Jaeger不支持,因为PHP缺乏稳定的工具。

然而,您仍然可以使用与Zipkin兼容的HTTP端点,通过Jaeger驱动程序将跨度发送到Jaeger收集器,具体请参阅Zipkin兼容性。事实上,这是推荐使用此库的方法,因为Jaeger的UI比Zipkin的UI要方便得多。

但也有一些缺点:

  • 由于Zipkin只支持标签和时间注释,您将无法利用一些Jaeger特定的功能,如上下文化日志。
  • HTTP是您唯一的传输选项(没有UDP选项)。

一旦Jaeger的自动化成熟,我们将考虑改进Jaeger支持。

该包还包括一个丢弃创建的跨度的null驱动程序。

使用方法

您将通过此包提供的Trace外观与跟踪一起工作。

创建跨度

启动新的跟踪就像调用具有代表跨度的逻辑操作名称的startSpan方法一样简单。

$span = Trace::startSpan('Create Order');

通常,您需要继续现有的跟踪,这就是为什么startSpan也接受额外的跨域上下文参数。 SpanContext可以通过各种渠道传播,包括HTTP请求、AMQP消息、数组,甚至另一个跨度。

$spanContext = Trace::extract($request, Formats::ILLUMINATE_HTTP);

$rootSpan = Trace::startSpan('Create Order', $spanContext);

$childSpan = Trace::startSpan('Validate Order', $rootSpan->getContext())

可能性是无限的。有关详细信息,请参阅上下文传播部分。

自定义跨度

重写跨度名称

$span->setName('Create Order');

添加标签,这些标签可以用作查找键(用于在UI上搜索跨度)或附加详细信息

$span->tag('shipping_method', $shipping_method);

检索跨度

您可以检索当前跨度,这通常是您最近创建的跨度

$span = Trace::getCurrentSpan()

在服务中处理请求时创建的第一个跨度称为根跨度(不要与跟踪的全局根跨度混淆)

在调用flush之后,根跨度将被重置。

$span = Trace::getRootSpan()

控制跨度

您可以通过调用该跨度的finish来结束跨度。跨度持续时间通过从开始时间戳减去这个值来计算。

$span->finish()

您可以在跨度开始和结束之间记录其他数据。例如,annotate创建一个时间戳事件来解释延迟。

$span->annotate('Order Validated')

刷新跨度

刷新是指将所有挂起的跨度发送到传输过程。它还会重置跟踪器的状态,包括活动跨度和UUID。

Trace::flush()

然而,您通常不需要显式调用flush。由于PHP设计为在每个请求后死亡,我们已为您处理完成根跨度并在应用程序关闭时调用刷新。

只有当您在一个循环中连续处理请求时(例如,AMQP通道),您才必须手动调用flush

日志记录

每个根跨度都与一个唯一的标识符相关联,该标识符可以用于查找其跟踪。建议您将其包括在报告错误时的上下文中,以便在不同部分的监控堆栈之间架起桥梁。

// Illuminate\Foundation\Exceptions\Handler

/**
  * Get the default context variables for logging.
  *
  * @return array
  */
protected function context()
{
    return array_filter([
        'userId' => Auth::id(),
        'uuid' => Trace::getUUID(),
    ]);
}

自定义驱动程序也可能支持记录结构化数据(在Zipkin中不可用),这可以用于将跟踪与日志外观集成。

use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Event::listen(MessageLogged::class, function (MessageLogged $e) {
        Tracer::getCurrentSpan()->log((array) $e);
    });
}

中间件

此包包括一个\Vinelab\Tracing\Middleware\TraceRequests中间件,用于处理从传入的HTTP请求继续跟踪。

您应在您的app/Http/Kernel.php类的$middleware属性中注册中间件类。

中间件在根跨度上添加以下标签

  • type(http)
  • request_method
  • request_path
  • request_uri
  • request_headers
  • request_ip
  • request_input
  • response_status
  • response_headers
  • response_content

仅包括白名单内容类型请求和响应体。请参阅您的config/tracing.php中的logging.content_types选项。

您可以在控制器中覆盖默认的跨度名称(默认为VERB /path/for/route)。

Trace::getRootSpan()->setName('Create Order')

控制台命令

Lumen不支持此功能,但您仍然可以使用跟踪实例手动创建命令的跟踪。

通过将Vinelab\Tracing\Contracts\ShouldBeTraced接口添加到您的类中,让控制台命令可追踪。

容器跨度将包含以下标签

  • type (cli)
  • argv

跨度将根据控制台命令命名。您可以在命令本身中覆盖跨度的默认名称

Trace::getRootSpan()->setName('Mark Orders Expired')

队列任务

Lumen不支持此功能,但您仍然可以使用tracer实例手动为作业创建跟踪。

通过将Vinelab\Tracing\Contracts\ShouldBeTraced接口添加到您的作业类中,让队列作业可追踪。

容器跨度将包含以下标签

  • type (queue)
  • connection_name(例如,sync、redis等)
  • queue_name
  • job_input

如名称所示,job_input允许您以JSON格式查看作业的构造函数参数。可以将对象序列化为此JSON字符串,可以通过实现以下接口之一来控制序列化:ArrayableJsonableJsonSerializable__toString方法。默认的回退行为是打印出对象的所有公共属性。

必须将构造函数参数保存为具有相同名称的类属性(请参阅下面的ProcessPodcast示例)。

跨度将根据队列作业类命名。您可以在作业本身中覆盖跨度的默认名称

app('tracing.queue.span')->setName('Process Podcast')

注意,队列跨度不一定是跟踪的根跨度。通常,您希望队列从作业分发时的位置继续跟踪。您可以通过简单地向作业的构造函数提供SpanContext来实现这一点

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $podcast;

    protected $spanContext;

    public function __construct(Podcast $podcast, SpanContext $spanContext)
    {
        $this->podcast = $podcast;
        $this->spanContext = $spanContext;
    }

    public function handle(AudioProcessor $processor)
    {
        // Process uploaded podcast...
    }
}

上面的作业可以按以下方式分发

ProcessPodcast::dispatch($podcast, Trace::getRootSpan()->getContext());

其余的将由系统自动处理。请注意,SpanContext将不包括在记录的job_input中。

注意:此包不会自动处理队列闭包和队列事件监听器的跟踪。您仍然可以通过打开和关闭跨度来手动跟踪它们。未来版本可能考虑增强对这些功能的支持。

上下文传播

如我们之前讨论的,tracer了解如何在不同的应用程序(服务)之间注入和提取跟踪上下文。

我们已经看到了从HTTP请求提取跟踪的例子

$spanContext = Trace::extract($request, Formats::ILLUMINATE_HTTP);

当然,您不需要手动执行此操作,因为这个包已经包含了一个中间件来为您处理此操作,但跟踪不一定来自HTTP请求。

第二个参数是一个格式描述符,它告诉我们如何从给定的载体中反序列化跟踪头。默认情况下,以下格式得到支持

use Vinelab\Tracing\Propagation\Formats;

$spanContext = Trace::extract($carrier, Formats::TEXT_MAP);
$spanContext = Trace::extract($carrier, Formats::PSR_REQUEST);
$spanContext = Trace::extract($carrier, Formats::ILLUMINATE_HTTP);
$spanContext = Trace::extract($carrier, Formats::AMQP);
$spanContext = Trace::extract($carrier, Formats::GOOGLE_PUBSUB);

您也可以使用registerExtractionFormat方法添加自己的格式。

Trace::registerExtractionFormat("pubsub", new PubSubExtractor());

注入格式必须实现Vinelab\Tracing\Contracts\Extractor。请参考默认的Zipkin实现。

interface Extractor
{
    public function extract($carrier): ?SpanContext;
}

当然,您也可以将来自当前跨度的现有跟踪上下文注入到给定的载体中,以便另一个服务可以继续跟踪

$message = Trace::inject($message, Formats::AMQP);

$channel->basic_publish($message, $this->exchangeName, $routingKey);

默认情况下,以下格式得到支持

use Vinelab\Tracing\Propagation\Formats;

$carrier = Trace::inject($carrier, Formats::TEXT_MAP);
$carrier = Trace::inject($carrier, Formats::PSR_REQUEST);
$carrier = Trace::inject($carrier, Formats::ILLUMINATE_HTTP);
$carrier = Trace::inject($carrier, Formats::AMQP);
$carrier = Trace::inject($carrier, Formats::GOOGLE_PUBSUB);
$carrier = Trace::inject($carrier, Formats::VINELAB_HTTP);

您也可以使用registerInjectionFormat方法添加自己的格式。

注入格式必须实现Vinelab\Tracing\Contracts\Injector。请参考默认的Zipkin实现。

interface Injector
{
    public function inject(SpanContext $spanContext, &$carrier): void;
}

如果您需要显式传递跨度上下文,也可以使用injectContext方法

$carrier = Trace::injectContext($carrier, Formats::TEXT_MAP, $span->getContext());

重要:如果您需要快速完成任务,不需要创建自定义传播格式。您始终可以使用默认的TEXT_MAP格式从关联数组中注入或提取跟踪头。

自定义驱动器

编写新驱动器

新的驱动程序必须遵守Vinelab\Tracing\Contracts\Tracer协议。请参考默认的ZipkinTracer实现。

use Vinelab\Tracing\Contracts\Extractor;
use Vinelab\Tracing\Contracts\Injector;
use Vinelab\Tracing\Contracts\Span;
use Vinelab\Tracing\Contracts\SpanContext;

public function startSpan(string $name, SpanContext $spanContext = null, ?int $timestamp = null): Span;
public function getRootSpan(): ?Span;
public function getCurrentSpan(): ?Span;
public function getUUID(): ?string;
public function extract($carrier, string $format): ?SpanContext;
public function inject($carrier, string $format);
public function injectContext($carrier, string $format, SpanContext $spanContext);
public function registerExtractionFormat(string $format, Extractor $extractor): array;
public function registerInjectionFormat(string $format, Injector $injector): array;
public function flush(): void;

注册新驱动器

一旦你编写了自定义驱动程序,你可以使用TracingDriverManager的扩展方法来注册它。你应该从你的AppServiceProviderboot方法或其他应用程序使用的任何服务提供者中调用extend方法。例如,如果你编写了一个JaegerTracer,你可以这样注册:

use Vinelab\Tracing\TracingDriverManager;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    resolve(TracingDriverManager::class)->extend('jaeger', function () {
        return new JaegerTracer;
    });
}

一旦你的驱动程序被注册,你可以在环境变量中指定它作为你的追踪驱动程序。

TRACING_DRIVER=jaeger

与Lumen一起使用

你需要在bootstrap/app.php文件中手动注册服务提供者。

$app->register(Vinelab\Tracing\TracingServiceProvider::class);

你同样也应该在同一个文件中注册中间件。

$app->middleware([
    Vinelab\Tracing\Middleware\TraceRequests::class,
]);

将以下行添加到public/index.php文件的末尾:

$tracer = app(Vinelab\Tracing\Contracts\Tracer::class);

optional($tracer->getRootSpan())->finish();
$tracer->flush();

最后,如果你需要自定义默认设置,你也可以从这个仓库复制config/tracing.php

如果你在Lumen项目中不使用外观(facades),你可以这样从容器中解析tracer实例:

use Vinelab\Tracing\Contracts\Tracer;

app(Tracer::class)->startSpan('Create Order')

注意,由于Lumen目前不支持控制台命令和作业的自动追踪,因为它没有派发一些事件和终止回调。但是,你仍然可以在需要的地方手动创建追踪。

集成

Lucid架构

此包包括可选的Vinelab\Tracing\Integration\Concerns\TracesLucidArchitecture特质,以启用对Lucid项目的追踪。

class TracingServiceProvider extends ServiceProvider
{
    use TracesLucidArchitecture;

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->traceLucidArchitecture();
    }
}