Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Laravel: tracing Queues (+ hook refactoring) #250

Merged
merged 51 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d76534d
Laravel: start of QueueWatcher.
ChrisLightfootWild Apr 2, 2024
a9864a3
Laravel: PR feedback.
ChrisLightfootWild Apr 3, 2024
71d999c
Laravel: moved additional attributes from event into span.
ChrisLightfootWild Apr 3, 2024
1aba286
Merge remote-tracking branch 'upstream/main' into laravel/queues
ChrisLightfootWild Apr 4, 2024
493e2d2
Laravel: moved propagation to root of message.
ChrisLightfootWild Apr 6, 2024
4498319
Laravel: experimenting with hooking \Illuminate\Contracts\Queue\Queue.
ChrisLightfootWild Apr 7, 2024
29330a1
Laravel: added queue context propagation via hook.
ChrisLightfootWild Apr 7, 2024
0ad58be
Update src/Instrumentation/Laravel/src/Hooks/Queue.php
ChrisLightfootWild Apr 8, 2024
1a596ff
Laravel: fix post-hook return. Thanks @brettmc
ChrisLightfootWild Apr 8, 2024
69117c8
Laravel: message processing on the consumer side.
ChrisLightfootWild Apr 8, 2024
17ad05f
Laravel: refining queue hooks.
ChrisLightfootWild Apr 9, 2024
b2ed01b
Laravel: removed the QueueWatcher in favour of hook.
ChrisLightfootWild Apr 10, 2024
550ddfb
Laravel: hook restructuring.
ChrisLightfootWild Apr 10, 2024
a220d7b
Merge remote-tracking branch 'upstream/main' into laravel/queues
ChrisLightfootWild Apr 10, 2024
f9d11fe
Laravel: moved ServeCommand hook.
ChrisLightfootWild Apr 10, 2024
29af9cb
Laravel: hook SyncQueue.
ChrisLightfootWild Apr 13, 2024
0a564e1
Laravel: moved main Application hook.
ChrisLightfootWild Apr 14, 2024
db1749c
Laravel: queue cleanup.
ChrisLightfootWild Apr 14, 2024
c734b78
Laravel: test queue tracing.
ChrisLightfootWild Apr 14, 2024
59f173c
Laravel: SyncQueue hook linting.
ChrisLightfootWild Apr 14, 2024
58667a2
Laravel: rebuilt docker image and newer otel-instrumentation fixed this.
ChrisLightfootWild Apr 14, 2024
9141441
Laravel: update QueueTest.
ChrisLightfootWild Apr 14, 2024
8a06b74
Laravel: hook Queue bulk & later.
ChrisLightfootWild Apr 14, 2024
57edcd9
Laravel: added estimate delivery_timestamp when passed to Queue->late…
ChrisLightfootWild Apr 14, 2024
e939785
Laravel: fixing psalm errors.
ChrisLightfootWild Apr 15, 2024
8654e59
Laravel: fixing phpstan errors.
ChrisLightfootWild Apr 15, 2024
7f6f028
Laravel: refactoring QueueTest.
ChrisLightfootWild Apr 15, 2024
1cf42cf
Laravel: added Queue->later tests.
ChrisLightfootWild Apr 15, 2024
a5a7262
Laravel: fix Queue->later tests.
ChrisLightfootWild Apr 16, 2024
3de767d
Laravel: added beanstalk message system detection.
ChrisLightfootWild Apr 16, 2024
58f5847
Laravel: added bulk message dispatch test.
ChrisLightfootWild Apr 16, 2024
406222c
Laravel: added redis connection test.
ChrisLightfootWild Apr 16, 2024
4c042b9
Laravel: record a span if a job is received from the queue.
ChrisLightfootWild Apr 16, 2024
c7bbe1a
Laravel: PR feedback to address closing incorrect span.
ChrisLightfootWild Apr 17, 2024
4086980
Laravel: PR feedback - change confusing ClockInterface mock.
ChrisLightfootWild Apr 19, 2024
f67e089
Merge remote-tracking branch 'upstream/main' into laravel/queues
ChrisLightfootWild Apr 19, 2024
6f3aa89
Laravel: prefer stable dependencies with lowest package compat.
ChrisLightfootWild Apr 19, 2024
9c1eebe
Laravel: fix dependencies for use with prefer-lowest.
ChrisLightfootWild Apr 19, 2024
52e30ab
Laravel: fix lowest dependencies.
ChrisLightfootWild Apr 21, 2024
5eac03b
Laravel: fix watcher PhanTypeArraySuspicious warnings.
ChrisLightfootWild Apr 21, 2024
1ef619b
Laravel: removed ClockInterface usage to avoid dependency on SDK.
ChrisLightfootWild Apr 21, 2024
84a4ea5
Laravel: more fixes for phan.
ChrisLightfootWild Apr 21, 2024
33b4cfa
Laravel: remove erroneous Context destroy usage.
ChrisLightfootWild Apr 21, 2024
e8a3ca1
Laravel: added interface LaravelHook.
ChrisLightfootWild Apr 21, 2024
75df94b
Laravel: suppress psalm warning.
ChrisLightfootWild Apr 21, 2024
6a83396
Laravel: moved Console/Kernel/Command hooks.
ChrisLightfootWild Apr 21, 2024
47e1ede
Laravel: export-ignore /.phan.
ChrisLightfootWild Apr 21, 2024
f4ea0ce
Laravel: explicitly list support for Laravel versions 6 to 11.
ChrisLightfootWild Apr 22, 2024
92a6325
Laravel: moved Http Kernel hooks.
ChrisLightfootWild Apr 22, 2024
ae2eeda
Laravel: consistent test naming.
ChrisLightfootWild Apr 22, 2024
a3e9dba
Laravel: test dropped empty queue receives.
ChrisLightfootWild Apr 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/Instrumentation/Laravel/.php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@
])
->setRiskyAllowed(true)
->setFinder($finder);

6 changes: 3 additions & 3 deletions src/Instrumentation/Laravel/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
"readme": "./README.md",
"license": "Apache-2.0",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": "^8.0",
"ext-json": "*",
"ext-opentelemetry": "*",
"laravel/framework": ">=6.0",
"open-telemetry/api": "^1.0",
"open-telemetry/sem-conv": "^1.23"
"open-telemetry/sem-conv": "^1.24"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3",
"friendsofphp/php-cs-fixer": "^3.50",
"guzzlehttp/guzzle": "*",
"laravel/tinker": "*",
"nunomaduro/collision": "*",
"open-telemetry/sdk": "^1.0",
"orchestra/testbench": ">=4.0",
Expand Down
29 changes: 29 additions & 0 deletions src/Instrumentation/Laravel/src/Hooks/HookInstance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks;

use OpenTelemetry\API\Instrumentation\CachedInstrumentation;

trait HookInstance
{
private static ?self $instance = null;

protected function __construct(
protected CachedInstrumentation $instrumentation,
) {
}

abstract public function instrument(): void;

public static function hook(CachedInstrumentation $instrumentation): self
{
if (self::$instance === null) {
self::$instance = new self($instrumentation);
self::$instance->instrument();
}

return self::$instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Contracts\Queue;

use DateInterval;
use DateTime;
use DateTimeInterface;
use Illuminate\Contracts\Queue\Queue as QueueContract;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\HookInstance;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Queue\AttributesBuilder;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\PostHookHandler;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Common\Time\ClockInterface;
use OpenTelemetry\SemConv\TraceAttributes;
use OpenTelemetry\SemConv\TraceAttributeValues;
use Throwable;

class Queue
{
use AttributesBuilder;
use HookInstance;
use PostHookHandler;

public function instrument(): void
{
$this->hookBulk();
$this->hookLater();
$this->hookPushRaw();
}

protected function hookBulk(): bool
{
return hook(
QueueContract::class,
'bulk',
pre: function (QueueContract $queue, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
$attributes = array_merge([
TraceAttributes::CODE_FUNCTION => $function,
TraceAttributes::CODE_NAMESPACE => $class,
TraceAttributes::CODE_FILEPATH => $filename,
TraceAttributes::CODE_LINENO => $lineno,
TraceAttributes::MESSAGING_BATCH_MESSAGE_COUNT => count($params[0] ?? []),
], $this->contextualMessageSystemAttributes($queue, []));

/** @psalm-suppress ArgumentTypeCoercion */
$span = $this->instrumentation
->tracer()
->spanBuilder(vsprintf('%s %s', [
method_exists($queue, 'getQueue') ? $queue->getQueue($params[2] ?? null) : $queue->getConnectionName(),
TraceAttributeValues::MESSAGING_OPERATION_PUBLISH,
]))
->setSpanKind(SpanKind::KIND_PRODUCER)
->setAttributes($attributes)
->startSpan();

Context::storage()->attach($span->storeInContext(Context::getCurrent()));

return $params;
},
post: function (QueueContract $queue, array $params, $returnValue, ?Throwable $exception) {
$this->endSpan($exception);
},
);
}

protected function hookLater(): bool
{
return hook(
QueueContract::class,
'later',
pre: function (QueueContract $queue, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
$clock = ClockFactory::getDefault();
brettmc marked this conversation as resolved.
Show resolved Hide resolved
$now = fn (): DateTimeInterface => new DateTime('@' . ($clock->now() / ClockInterface::NANOS_PER_SECOND));

$estimateDeliveryTimestamp = match (true) {
is_int($params[0]) => $now()->add(new DateInterval("PT{$params[0]}S"))->getTimestamp(),
$params[0] instanceof DateInterval => $now()->add($params[0])->getTimestamp(),
$params[0] instanceof DateTimeInterface => ($params[0])->getTimestamp(),
default => $params[0],
};

$attributes = [
TraceAttributes::CODE_FUNCTION => $function,
TraceAttributes::CODE_NAMESPACE => $class,
TraceAttributes::CODE_FILEPATH => $filename,
TraceAttributes::CODE_LINENO => $lineno,
'messaging.message.delivery_timestamp' => $estimateDeliveryTimestamp,
];

/** @psalm-suppress ArgumentTypeCoercion */
$span = $this->instrumentation
->tracer()
->spanBuilder(vsprintf('%s %s', [
method_exists($queue, 'getQueue') ? $queue->getQueue($params[2] ?? null) : $queue->getConnectionName(),
'create',
]))
->setSpanKind(SpanKind::KIND_PRODUCER)
->setAttributes($attributes)
->startSpan();

Context::storage()->attach($span->storeInContext(Context::getCurrent()));

return $params;
},
post: function (QueueContract $queue, array $params, $returnValue, ?Throwable $exception) {
$this->endSpan($exception);
},
);
}

protected function hookPushRaw(): bool
{
return hook(
QueueContract::class,
'pushRaw',
pre: function (QueueContract $queue, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
$attributes = $this->buildMessageAttributes($queue, ...$params);

$parent = Context::getCurrent();
/** @psalm-suppress ArgumentTypeCoercion */
$span = $this->instrumentation
->tracer()
->spanBuilder(vsprintf('%s %s', [
$attributes[TraceAttributes::MESSAGING_DESTINATION_NAME],
TraceAttributeValues::MESSAGING_OPERATION_CREATE,
]))
->setSpanKind(SpanKind::KIND_PRODUCER)
->setAttributes($attributes)
->startSpan();

Context::storage()->attach($span->storeInContext($parent));

return $params;
},
post: function (QueueContract $queue, array $params, $returnValue, ?Throwable $exception) {
$this->endSpan($exception);
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Foundation;

use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Foundation\Application as FoundationalApplication;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\HookInstance;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\CacheWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\ClientRequestWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\ExceptionWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\LogWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\QueryWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\RequestWatcher;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\Watcher;
use function OpenTelemetry\Instrumentation\hook;
use Throwable;

class Application
{
use HookInstance;

public function instrument(): void
{
hook(
FoundationalApplication::class,
'__construct',
post: function (FoundationalApplication $application, array $params, mixed $returnValue, ?Throwable $exception) {
$this->registerWatchers($application, new CacheWatcher());
$this->registerWatchers($application, new ClientRequestWatcher($this->instrumentation));
$this->registerWatchers($application, new ExceptionWatcher());
$this->registerWatchers($application, new LogWatcher());
$this->registerWatchers($application, new QueryWatcher($this->instrumentation));
$this->registerWatchers($application, new RequestWatcher());
},
);
}

private function registerWatchers(ApplicationContract $app, Watcher $watcher): void
{
$watcher->register($app);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Foundation\Console;

use Illuminate\Foundation\Console\ServeCommand as FoundationServeCommand;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\HookInstance;
use function OpenTelemetry\Instrumentation\hook;

/**
* Instrument Laravel's local PHP development server.
*/
class ServeCommand
{
use HookInstance;

public function instrument(): void
{
hook(
FoundationServeCommand::class,
'handle',
pre: static function (FoundationServeCommand $serveCommand, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
if (!property_exists(FoundationServeCommand::class, 'passthroughVariables')) {
return;
}

foreach ($_ENV as $key => $value) {
if (str_starts_with($key, 'OTEL_') && !in_array($key, FoundationServeCommand::$passthroughVariables)) {
FoundationServeCommand::$passthroughVariables[] = $key;
}
}
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Queue;

use Illuminate\Contracts\Queue\Queue as QueueContract;
use Illuminate\Queue\BeanstalkdQueue;
use Illuminate\Queue\RedisQueue;
use Illuminate\Queue\SqsQueue;
use OpenTelemetry\SemConv\TraceAttributes;
use OpenTelemetry\SemConv\TraceAttributeValues;

trait AttributesBuilder
{
private function buildMessageAttributes(
QueueContract $queue,
string $rawPayload,
string $queueName = null,
array $options = [],
mixed ...$params,
): array {
$payload = json_decode($rawPayload, true) ?? [];

return array_merge([
TraceAttributes::MESSAGING_DESTINATION_NAME => '(anonymous)',
TraceAttributes::MESSAGING_MESSAGE_ID => $payload['uuid'] ?? $payload['id'] ?? null,
TraceAttributes::MESSAGING_MESSAGE_ENVELOPE_SIZE => strlen($rawPayload),
'messaging.message.job_name' => $payload['displayName'] ?? $payload['job'] ?? null,
'messaging.message.attempts' => $payload['attempts'] ?? 0,
'messaging.message.max_exceptions' => $payload['maxExceptions'] ?? null,
'messaging.message.max_tries' => $payload['maxTries'] ?? null,
'messaging.message.retry_until' => $payload['retryUntil'] ?? null,
'messaging.message.timeout' => $payload['timeout'] ?? null,
], $this->contextualMessageSystemAttributes($queue, $payload, $queueName, $options, ...$params));
}

private function contextualMessageSystemAttributes(
QueueContract $queue,
array $payload,
string $queueName = null,
array $options = [],
mixed ...$params,
): array {
return match (true) {
$queue instanceof BeanstalkdQueue => $this->beanstalkContextualAttributes($queue, $payload, $queueName, $options, ...$params),
$queue instanceof RedisQueue => $this->redisContextualAttributes($queue, $payload, $queueName, $options, ...$params),
$queue instanceof SqsQueue => $this->awsSqsContextualAttributes($queue, $payload, $queueName, $options, ...$params),
default => [],
};
}

private function beanstalkContextualAttributes(BeanstalkdQueue $queue, array $payload, string $queueName = null, array $options = [], mixed ...$params): array
{
return [
TraceAttributes::MESSAGING_SYSTEM => 'beanstalk',
TraceAttributes::MESSAGING_DESTINATION_NAME => $queue->getQueue($queueName),
];
}

private function redisContextualAttributes(RedisQueue $queue, array $payload, string $queueName = null, array $options = [], mixed ...$params): array
{
return [
TraceAttributes::MESSAGING_SYSTEM => 'redis',
TraceAttributes::MESSAGING_DESTINATION_NAME => $queue->getQueue($queueName),
];
}

private function awsSqsContextualAttributes(SqsQueue $queue, array $payload, string $queueName = null, array $options = [], mixed ...$params): array
{
return [
TraceAttributes::MESSAGING_SYSTEM => TraceAttributeValues::MESSAGING_SYSTEM_AWS_SQS,
TraceAttributes::MESSAGING_DESTINATION_NAME => $queue->getQueue($queueName),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Queue;

use Illuminate\Queue\Queue as AbstractQueue;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\HookInstance;
use function OpenTelemetry\Instrumentation\hook;
use Throwable;

class Queue
{
use AttributesBuilder;
use HookInstance;

public function instrument(): void
{
$this->hookAbstractQueueCreatePayloadArray();
}

protected function hookAbstractQueueCreatePayloadArray(): bool
{
return hook(
AbstractQueue::class,
'createPayloadArray',
post: function (AbstractQueue $queue, array $params, array $payload, ?Throwable $exception): array {
TraceContextPropagator::getInstance()->inject($payload);

return $payload;
},
);
}
}
Loading
Loading