orgenus/tracing-laravel

让Laravel的分布式追踪变得简单

1.0.4 2023-02-16 21:44 UTC

This package is not auto-updated.

Last update: 2024-09-28 00:28:00 UTC


README

简介

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

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

简单来说,分布式追踪是一种知识工具。拥有它的一个最重要的好处是,开发者可以通过简单地跟踪其创建的轨迹来了解系统。

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

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

每个 跨度 具有以下内容

  • 操作名称
  • 开始时间戳
  • 完成时间戳
  • 一组零个或多个键值标签,用于查找和记录其他信息
  • 一组零个或多个与时间戳配对的日志
  • 相关跨度的引用(例如,父跨度)

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

image

追踪器接口(通过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

由于PHP缺乏稳定的仪器支持,Jaeger“非官方”支持。

然而,您仍然可以使用 Zipkin 驱动程序通过 与 Zipkin 兼容的 HTTP 端点 将 spans 发送到 Jaeger 收集器。实际上,这是推荐使用此库的方式,因为 Jaeger 的 UI 比Zipkin 的 UI 要方便得多。

但是也有一些缺点

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

一旦 Jaeger 的工具成熟,我们将考虑提高 Jaeger 的支持。

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

使用方法

您将通过此包提供的 Trace 门面进行跟踪。

创建跨度

开始新的跟踪就像调用带有表示 span 所代表逻辑操作名称的 startSpan 方法一样简单

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

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

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

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

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

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

自定义跨度

覆盖 span 名称

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

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

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

检索跨度

您可以检索当前 span,这同时也是您最近创建的 span

$span = Trace::getCurrentSpan()

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

在调用 flush 之后,根 span 会重置。

$span = Trace::getRootSpan()

控制跨度

您可以通过调用该 span 的 finish 来完成 span。通过从开始时间戳减去这个时间来推导 span 持续时间

$span->finish()

您可以在 span 开始和完成之间记录额外的数据。例如,annotate 创建一个带时间戳的事件来解释延迟

$span->annotate('Order Validated')

刷新跨度

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

Trace::flush()

大多数时候,您不需要明确调用 flush。由于 PHP 被设计为在每次请求后死亡,我们已经在应用程序关闭时为您处理完成根 span 和调用 flush。

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

日志记录

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

// 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 属性中注册中间件类。

该中间件在根 span 上添加以下 标签

  • 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 选项。

您可以在控制器中覆盖默认的 span 名称(即 VERB /path/for/route

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

控制台命令

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

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

容器跨度将包含以下标签

  • type (命令行)
  • argv

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

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

队列作业

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

Vinelab\Tracing\Contracts\ShouldBeTraced 接口添加到您的作业类中,以便让您的队列作业可被跟踪。

容器跨度将包含以下标签

  • type (队列)
  • connection_name(例如 sync, redis 等)
  • 队列名称
  • 作业输入

正如其名,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 中排除。

重要:此包不会自动处理队列闭包和队列事件监听器的跟踪。您仍然可以通过打开和关闭跨度手动跟踪它们。可能在包的将来版本中考虑对这些功能的改进。

上下文传播

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

我们已经看到了从 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 的 extend 方法注册它。您应从您的 AppServiceProvider 或任何其他应用程序使用的服务提供者的 boot 方法中调用 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项目中不使用外观,您可以通过以下方式从容器中解析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();
    }
}