From 257ef72ec4a24f0faeec8f12db7636846e7a4ca6 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 21:21:00 +0100 Subject: [PATCH 1/9] wip --- composer.json | 3 +- .../Features/HttpClientIntegration.php | 19 +++++++ src/Sentry/Laravel/ServiceProvider.php | 3 +- .../Features/HttpClientIntegrationTest.php | 50 +++++++++++++++---- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 src/Sentry/Laravel/Features/HttpClientIntegration.php diff --git a/composer.json b/composer.json index 2651e869..2cf50001 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,8 @@ "friendsofphp/php-cs-fixer": "^3.11", "mockery/mockery": "^1.3", "phpstan/phpstan": "^1.10", - "laravel/folio": "^1.0" + "laravel/folio": "^1.0", + "guzzlehttp/guzzle": "^7.2" }, "autoload-dev": { "psr-4": { diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php new file mode 100644 index 00000000..60dc285c --- /dev/null +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -0,0 +1,19 @@ +globalMiddleware(GuzzleTracingMiddleware::trace()); + } +} diff --git a/src/Sentry/Laravel/ServiceProvider.php b/src/Sentry/Laravel/ServiceProvider.php index 9d441fab..f239e815 100644 --- a/src/Sentry/Laravel/ServiceProvider.php +++ b/src/Sentry/Laravel/ServiceProvider.php @@ -57,8 +57,9 @@ class ServiceProvider extends BaseServiceProvider Features\CacheIntegration::class, Features\QueueIntegration::class, Features\ConsoleIntegration::class, - Features\FolioPackageIntegration::class, Features\Storage\Integration::class, + Features\HttpClientIntegration::class, + Features\FolioPackageIntegration::class, Features\LivewirePackageIntegration::class, ]; diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index 17828ca5..6d5c5cb4 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -5,23 +5,18 @@ use GuzzleHttp\Psr7\Request as PsrRequest; use GuzzleHttp\Psr7\Response as PsrResponse; use Illuminate\Http\Client\Events\ResponseReceived; +use Illuminate\Http\Client\Factory; use Illuminate\Http\Client\Request; use Illuminate\Http\Client\Response; +use Illuminate\Support\Facades\Http; use Sentry\Laravel\Tests\TestCase; class HttpClientIntegrationTest extends TestCase { - protected function setUp(): void - { - if (!class_exists(ResponseReceived::class)) { - $this->markTestSkipped('The Laravel HTTP client events are only available in Laravel 8.0+'); - } - - parent::setUp(); - } - public function testHttpClientBreadcrumbIsRecordedForResponseReceivedEvent(): void { + $this->skipIfEventClassNotAvailable(); + $this->dispatchLaravelEvent(new ResponseReceived( new Request(new PsrRequest('GET', 'https://example.com', [], 'request')), new Response(new PsrResponse(200, [], 'response')) @@ -40,6 +35,8 @@ public function testHttpClientBreadcrumbIsRecordedForResponseReceivedEvent(): vo public function testHttpClientBreadcrumbDoesntConsumeBodyStream(): void { + $this->skipIfEventClassNotAvailable(); + $this->dispatchLaravelEvent(new ResponseReceived( $request = new Request(new PsrRequest('GET', 'https://example.com', [], 'request')), $response = new Response(new PsrResponse(200, [], 'response')) @@ -50,4 +47,39 @@ public function testHttpClientBreadcrumbDoesntConsumeBodyStream(): void $this->assertEquals('request', $request->toPsrRequest()->getBody()->getContents()); $this->assertEquals('response', $response->toPsrResponse()->getBody()->getContents()); } + + private function skipIfEventClassNotAvailable(): void + { + if (class_exists(ResponseReceived::class)) { + return; + } + + $this->markTestSkipped('The Laravel HTTP client events are only available in Laravel 8.0+'); + } + + public function testHttpClientMiddelwareIsRegistered(): void + { + $this->skipIfGlobalMiddlewareIsNotAvailable(); + + $transaction = $this->startTransaction(); + + $client = Http::fake(); + + $client->get('https://example.com'); + + /** @var \Sentry\Tracing\Span $span */ + $span = last($transaction->getSpanRecorder()->getSpans()); + + $this->assertEquals('http.client', $span->getOp()); + $this->assertEquals('GET https://example.com', $span->getDescription()); + } + + private function skipIfGlobalMiddlewareIsNotAvailable(): void + { + if (method_exists(Factory::class, 'globalMiddleware')) { + return; + } + + $this->markTestSkipped('The Laravel HTTP client global middleware is only available in Laravel 10.31+'); + } } From 5a6ac93f3e1eda4ccdcd89a5071870c07a119ecb Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 21:31:36 +0100 Subject: [PATCH 2/9] Fix comments --- src/Sentry/Laravel/Features/HttpClientIntegration.php | 1 + test/Sentry/Features/HttpClientIntegrationTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index 60dc285c..1bcd5678 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -9,6 +9,7 @@ class HttpClientIntegration extends Feature { public function isApplicable(): bool { + // The `globalMiddleware` method was added in Laravel 10.14 return class_exists(Factory::class) && method_exists(Factory::class, 'globalMiddleware'); } diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index 6d5c5cb4..af4af4ce 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -80,6 +80,6 @@ private function skipIfGlobalMiddlewareIsNotAvailable(): void return; } - $this->markTestSkipped('The Laravel HTTP client global middleware is only available in Laravel 10.31+'); + $this->markTestSkipped('The Laravel HTTP client global middleware is only available in Laravel 10.14+'); } } From 1a72303e76c63c6c88b7480ef30cc3bb98fa8c64 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 21:51:50 +0100 Subject: [PATCH 3/9] Refactor HTTP client events to feature class --- .../Concerns/TracksPushedScopesAndSpans.php | 84 +++++++++++++++++++ .../Features/HttpClientIntegration.php | 68 +++++++++++++-- .../Laravel/Features/QueueIntegration.php | 75 +---------------- src/Sentry/Laravel/Tracing/EventHandler.php | 77 +---------------- .../Features/HttpClientIntegrationTest.php | 55 +++++++----- 5 files changed, 183 insertions(+), 176 deletions(-) create mode 100644 src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php diff --git a/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php b/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php new file mode 100644 index 00000000..edd11da4 --- /dev/null +++ b/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php @@ -0,0 +1,84 @@ + + */ + private $parentSpanStack = []; + + /** + * Hold the stack of current spans that need to be finished still. + * + * @var array + */ + private $currentSpanStack = []; + + protected function pushSpan(Span $span): void + { + $hub = SentrySdk::getCurrentHub(); + + $this->parentSpanStack[] = $hub->getSpan(); + + $hub->setSpan($span); + + $this->currentSpanStack[] = $span; + } + + protected function pushScope(): void + { + SentrySdk::getCurrentHub()->pushScope(); + + ++$this->pushedScopeCount; + + // When a job starts, we want to make sure the scope is cleared of breadcrumbs + // as well as setting a new propagation context. + SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) { + $scope->clearBreadcrumbs(); + $scope->setPropagationContext(PropagationContext::fromDefaults()); + }); + } + + protected function maybePopSpan(): ?Span + { + if (count($this->currentSpanStack) === 0) { + return null; + } + + $parent = array_pop($this->parentSpanStack); + + SentrySdk::getCurrentHub()->setSpan($parent); + + return array_pop($this->currentSpanStack); + } + + protected function maybePopScope(): void + { + Integration::flushEvents(); + + if ($this->pushedScopeCount === 0) { + return; + } + + SentrySdk::getCurrentHub()->popScope(); + + --$this->pushedScopeCount; + } +} diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index 1bcd5678..e6779410 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -2,19 +2,75 @@ namespace Sentry\Laravel\Features; -use Illuminate\Http\Client\Factory; -use Sentry\Tracing\GuzzleTracingMiddleware; +use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Http\Client\Events\ConnectionFailed; +use Illuminate\Http\Client\Events\RequestSending; +use Illuminate\Http\Client\Events\ResponseReceived; +use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans; +use Sentry\Laravel\Util\WorksWithUris; +use Sentry\SentrySdk; +use Sentry\Tracing\SpanContext; +use Sentry\Tracing\SpanStatus; class HttpClientIntegration extends Feature { + use TracksPushedScopesAndSpans, WorksWithUris; + public function isApplicable(): bool { - // The `globalMiddleware` method was added in Laravel 10.14 - return class_exists(Factory::class) && method_exists(Factory::class, 'globalMiddleware'); + return $this->isTracingFeatureEnabled('http_client_requests'); } - public function onBoot(Factory $httpClient): void + public function onBoot(Dispatcher $events): void { - $httpClient->globalMiddleware(GuzzleTracingMiddleware::trace()); + $events->listen(RequestSending::class, [$this, 'handleRequestSendingHandler']); + $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandler']); + $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandler']); + } + + public function handleRequestSendingHandler(RequestSending $event): void + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + + // If there is no tracing span active there is no need to handle the event + if ($parentSpan === null) { + return; + } + + $context = new SpanContext; + + $fullUri = $this->getFullUri($event->request->url()); + $partialUri = $this->getPartialUri($fullUri); + + $context->setOp('http.client'); + $context->setDescription($event->request->method() . ' ' . $partialUri); + $context->setData([ + 'url' => $partialUri, + 'http.request.method' => $event->request->method(), + 'http.query' => $fullUri->getQuery(), + 'http.fragment' => $fullUri->getFragment(), + ]); + + $this->pushSpan($parentSpan->startChild($context)); + } + + public function handleResponseReceivedHandler(ResponseReceived $event): void + { + $span = $this->maybePopSpan(); + + if ($span !== null) { + $span->finish(); + $span->setHttpStatus($event->response->status()); + } + } + + public function handleConnectionFailedHandler(ConnectionFailed $event): void + { + $span = $this->maybePopSpan(); + + if ($span !== null) { + $span->finish(); + $span->setStatus(SpanStatus::internalError()); + } } } diff --git a/src/Sentry/Laravel/Features/QueueIntegration.php b/src/Sentry/Laravel/Features/QueueIntegration.php index 520f882a..f655e35f 100644 --- a/src/Sentry/Laravel/Features/QueueIntegration.php +++ b/src/Sentry/Laravel/Features/QueueIntegration.php @@ -9,6 +9,7 @@ use Illuminate\Queue\Events\WorkerStopping; use Illuminate\Queue\Queue; use Sentry\Breadcrumb; +use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans; use Sentry\Laravel\Integration; use Sentry\SentrySdk; use Sentry\State\Scope; @@ -25,30 +26,11 @@ class QueueIntegration extends Feature { + use TracksPushedScopesAndSpans; + private const QUEUE_PAYLOAD_BAGGAGE_DATA = 'sentry_baggage_data'; private const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data'; - /** - * Hold the number of times the scope was pushed. - * - * @var int - */ - private $pushedScopeCount = 0; - - /** - * Hold the stack of parent spans that need to be put back on the scope. - * - * @var array - */ - private $parentSpanStack = []; - - /** - * Hold the stack of current spans that need to be finished still. - * - * @var array - */ - private $currentSpanStack = []; - public function isApplicable(): bool { if (!$this->container()->bound('queue')) { @@ -181,57 +163,6 @@ public function handleJobExceptionOccurredQueueEvent(JobExceptionOccurred $event Integration::flushEvents(); } - private function pushSpan(Span $span): void - { - $hub = SentrySdk::getCurrentHub(); - - $this->parentSpanStack[] = $hub->getSpan(); - - $hub->setSpan($span); - - $this->currentSpanStack[] = $span; - } - - private function pushScope(): void - { - SentrySdk::getCurrentHub()->pushScope(); - - ++$this->pushedScopeCount; - - // When a job starts, we want to make sure the scope is cleared of breadcrumbs - // as well as setting a new propagation context. - SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) { - $scope->clearBreadcrumbs(); - $scope->setPropagationContext(PropagationContext::fromDefaults()); - }); - } - - private function maybePopSpan(): ?Span - { - if (count($this->currentSpanStack) === 0) { - return null; - } - - $parent = array_pop($this->parentSpanStack); - - SentrySdk::getCurrentHub()->setSpan($parent); - - return array_pop($this->currentSpanStack); - } - - private function maybePopScope(): void - { - Integration::flushEvents(); - - if ($this->pushedScopeCount === 0) { - return; - } - - SentrySdk::getCurrentHub()->popScope(); - - --$this->pushedScopeCount; - } - private function finishJobWithStatus(SpanStatus $status): void { $span = $this->maybePopSpan(); diff --git a/src/Sentry/Laravel/Tracing/EventHandler.php b/src/Sentry/Laravel/Tracing/EventHandler.php index 15314c8e..b184ca82 100644 --- a/src/Sentry/Laravel/Tracing/EventHandler.php +++ b/src/Sentry/Laravel/Tracing/EventHandler.php @@ -5,12 +5,10 @@ use Exception; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Events as DatabaseEvents; -use Illuminate\Http\Client\Events as HttpClientEvents; use Illuminate\Routing\Events as RoutingEvents; use RuntimeException; use Sentry\Laravel\Features\Concerns\ResolvesEventOrigin; use Sentry\Laravel\Integration; -use Sentry\Laravel\Util\WorksWithUris; use Sentry\SentrySdk; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; @@ -19,7 +17,7 @@ class EventHandler { - use WorksWithUris, ResolvesEventOrigin; + use ResolvesEventOrigin; /** * Map event handlers to events. @@ -31,9 +29,6 @@ class EventHandler DatabaseEvents\QueryExecuted::class => 'queryExecuted', RoutingEvents\ResponsePrepared::class => 'responsePrepared', RoutingEvents\PreparingResponse::class => 'responsePreparing', - HttpClientEvents\RequestSending::class => 'httpClientRequestSending', - HttpClientEvents\ResponseReceived::class => 'httpClientResponseReceived', - HttpClientEvents\ConnectionFailed::class => 'httpClientConnectionFailed', DatabaseEvents\TransactionBeginning::class => 'transactionBeginning', DatabaseEvents\TransactionCommitted::class => 'transactionCommitted', DatabaseEvents\TransactionRolledBack::class => 'transactionRolledBack', @@ -67,13 +62,6 @@ class EventHandler */ private $traceQueueJobsAsTransactions; - /** - * Indicates if we should trace HTTP client requests. - * - * @var bool - */ - private $traceHttpClientRequests; - /** * Hold the stack of parent spans that need to be put back on the scope. * @@ -96,8 +84,6 @@ public function __construct(array $config) $this->traceSqlQueries = ($config['sql_queries'] ?? true) === true; $this->traceSqlQueryOrigins = ($config['sql_origin'] ?? true) === true; - $this->traceHttpClientRequests = ($config['http_client_requests'] ?? true) === true; - $this->traceQueueJobs = ($config['queue_jobs'] ?? false) === true; $this->traceQueueJobsAsTransactions = ($config['queue_job_transactions'] ?? false) === true; } @@ -112,9 +98,6 @@ public function __construct(array $config) * @uses self::transactionBeginningHandler() * @uses self::transactionCommittedHandler() * @uses self::transactionRolledBackHandler() - * @uses self::httpClientRequestSendingHandler() - * @uses self::httpClientResponseReceivedHandler() - * @uses self::httpClientConnectionFailedHandler() */ public function subscribe(Dispatcher $dispatcher): void { @@ -282,64 +265,6 @@ protected function transactionRolledBackHandler(DatabaseEvents\TransactionRolled } } - protected function httpClientRequestSendingHandler(HttpClientEvents\RequestSending $event): void - { - if (!$this->traceHttpClientRequests) { - return; - } - - $parentSpan = SentrySdk::getCurrentHub()->getSpan(); - - // If there is no tracing span active there is no need to handle the event - if ($parentSpan === null) { - return; - } - - $context = new SpanContext; - - $fullUri = $this->getFullUri($event->request->url()); - $partialUri = $this->getPartialUri($fullUri); - - $context->setOp('http.client'); - $context->setDescription($event->request->method() . ' ' . $partialUri); - $context->setData([ - 'url' => $partialUri, - 'http.request.method' => $event->request->method(), - 'http.query' => $fullUri->getQuery(), - 'http.fragment' => $fullUri->getFragment(), - ]); - - $this->pushSpan($parentSpan->startChild($context)); - } - - protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseReceived $event): void - { - if (!$this->traceHttpClientRequests) { - return; - } - - $span = $this->popSpan(); - - if ($span !== null) { - $span->finish(); - $span->setHttpStatus($event->response->status()); - } - } - - protected function httpClientConnectionFailedHandler(HttpClientEvents\ConnectionFailed $event): void - { - if (!$this->traceHttpClientRequests) { - return; - } - - $span = $this->popSpan(); - - if ($span !== null) { - $span->finish(); - $span->setStatus(SpanStatus::internalError()); - } - } - private function pushSpan(Span $span): void { $hub = SentrySdk::getCurrentHub(); diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index af4af4ce..fec2fbcf 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -5,18 +5,25 @@ use GuzzleHttp\Psr7\Request as PsrRequest; use GuzzleHttp\Psr7\Response as PsrResponse; use Illuminate\Http\Client\Events\ResponseReceived; -use Illuminate\Http\Client\Factory; use Illuminate\Http\Client\Request; use Illuminate\Http\Client\Response; use Illuminate\Support\Facades\Http; use Sentry\Laravel\Tests\TestCase; +use Sentry\Tracing\SpanStatus; class HttpClientIntegrationTest extends TestCase { - public function testHttpClientBreadcrumbIsRecordedForResponseReceivedEvent(): void + protected function setUp(): void { - $this->skipIfEventClassNotAvailable(); + if (!class_exists(ResponseReceived::class)) { + $this->markTestSkipped('The Laravel HTTP client events are only available in Laravel 8.0+'); + } + + parent::setUp(); + } + public function testHttpClientBreadcrumbIsRecordedForResponseReceivedEvent(): void + { $this->dispatchLaravelEvent(new ResponseReceived( new Request(new PsrRequest('GET', 'https://example.com', [], 'request')), new Response(new PsrResponse(200, [], 'response')) @@ -35,8 +42,6 @@ public function testHttpClientBreadcrumbIsRecordedForResponseReceivedEvent(): vo public function testHttpClientBreadcrumbDoesntConsumeBodyStream(): void { - $this->skipIfEventClassNotAvailable(); - $this->dispatchLaravelEvent(new ResponseReceived( $request = new Request(new PsrRequest('GET', 'https://example.com', [], 'request')), $response = new Response(new PsrResponse(200, [], 'response')) @@ -48,19 +53,8 @@ public function testHttpClientBreadcrumbDoesntConsumeBodyStream(): void $this->assertEquals('response', $response->toPsrResponse()->getBody()->getContents()); } - private function skipIfEventClassNotAvailable(): void + public function testHttpClientSpanIsRecorded(): void { - if (class_exists(ResponseReceived::class)) { - return; - } - - $this->markTestSkipped('The Laravel HTTP client events are only available in Laravel 8.0+'); - } - - public function testHttpClientMiddelwareIsRegistered(): void - { - $this->skipIfGlobalMiddlewareIsNotAvailable(); - $transaction = $this->startTransaction(); $client = Http::fake(); @@ -74,12 +68,29 @@ public function testHttpClientMiddelwareIsRegistered(): void $this->assertEquals('GET https://example.com', $span->getDescription()); } - private function skipIfGlobalMiddlewareIsNotAvailable(): void + public function testHttpClientSpanIsRecordedWithCorrectResult(): void { - if (method_exists(Factory::class, 'globalMiddleware')) { - return; - } + $transaction = $this->startTransaction(); + + $client = Http::fake([ + 'example.com/success' => Http::response('OK'), + 'example.com/error' => Http::response('Internal Server Error', 500), + ]); - $this->markTestSkipped('The Laravel HTTP client global middleware is only available in Laravel 10.14+'); + $client->get('https://example.com/success'); + + /** @var \Sentry\Tracing\Span $span */ + $span = last($transaction->getSpanRecorder()->getSpans()); + + $this->assertEquals('http.client', $span->getOp()); + $this->assertEquals(SpanStatus::ok(), $span->getStatus()); + + $client->get('https://example.com/error'); + + /** @var \Sentry\Tracing\Span $span */ + $span = last($transaction->getSpanRecorder()->getSpans()); + + $this->assertEquals('http.client', $span->getOp()); + $this->assertEquals(SpanStatus::internalError(), $span->getStatus()); } } From ecdfe629d518194507566939c55d93689432de3a Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:01:08 +0100 Subject: [PATCH 4/9] Move http client breadcrumb handling to feature class --- src/Sentry/Laravel/EventHandler.php | 68 ----------- .../Features/HttpClientIntegration.php | 106 ++++++++++++++++-- src/Sentry/Laravel/Util/WorksWithUris.php | 38 ------- 3 files changed, 97 insertions(+), 115 deletions(-) delete mode 100644 src/Sentry/Laravel/Util/WorksWithUris.php diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php index 7e250981..99c50d2c 100644 --- a/src/Sentry/Laravel/EventHandler.php +++ b/src/Sentry/Laravel/EventHandler.php @@ -11,7 +11,6 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Events as DatabaseEvents; -use Illuminate\Http\Client\Events as HttpClientEvents; use Illuminate\Http\Request; use Illuminate\Log\Events as LogEvents; use Illuminate\Routing\Events as RoutingEvents; @@ -19,8 +18,6 @@ use Laravel\Sanctum\Events as Sanctum; use RuntimeException; use Sentry\Breadcrumb; -use Sentry\Laravel\Tracing\Middleware; -use Sentry\Laravel\Util\WorksWithUris; use Sentry\SentrySdk; use Sentry\State\Scope; use Symfony\Component\Console\Input\ArgvInput; @@ -28,8 +25,6 @@ class EventHandler { - use WorksWithUris; - /** * Map event handlers to events. * @@ -41,8 +36,6 @@ class EventHandler DatabaseEvents\QueryExecuted::class => 'queryExecuted', ConsoleEvents\CommandStarting::class => 'commandStarting', ConsoleEvents\CommandFinished::class => 'commandFinished', - HttpClientEvents\ResponseReceived::class => 'httpClientResponseReceived', - HttpClientEvents\ConnectionFailed::class => 'httpClientConnectionFailed', ]; /** @@ -123,13 +116,6 @@ class EventHandler */ private $recordOctaneTaskInfo; - /** - * Indicates if we should add HTTP client requests info to the breadcrumbs. - * - * @var bool - */ - private $recordHttpClientRequests; - /** * Indicates if we pushed a scope for Octane. * @@ -153,7 +139,6 @@ public function __construct(Container $container, array $config) $this->recordCommandInfo = ($config['breadcrumbs.command_info'] ?? $config['breadcrumbs']['command_info'] ?? true) === true; $this->recordOctaneTickInfo = ($config['breadcrumbs.octane_tick_info'] ?? $config['breadcrumbs']['octane_tick_info'] ?? true) === true; $this->recordOctaneTaskInfo = ($config['breadcrumbs.octane_task_info'] ?? $config['breadcrumbs']['octane_task_info'] ?? true) === true; - $this->recordHttpClientRequests = ($config['breadcrumbs.http_client_requests'] ?? $config['breadcrumbs']['http_client_requests'] ?? true) === true; } /** @@ -278,59 +263,6 @@ protected function messageLoggedHandler(LogEvents\MessageLogged $logEntry): void )); } - protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseReceived $event): void - { - if (!$this->recordHttpClientRequests) { - return; - } - - $level = Breadcrumb::LEVEL_INFO; - if ($event->response->failed()) { - $level = Breadcrumb::LEVEL_ERROR; - } - - $fullUri = $this->getFullUri($event->request->url()); - - Integration::addBreadcrumb(new Breadcrumb( - $level, - Breadcrumb::TYPE_HTTP, - 'http', - null, - [ - 'url' => $this->getPartialUri($fullUri), - 'http.request.method' => $event->request->method(), - 'http.response.status_code' => $event->response->status(), - 'http.query' => $fullUri->getQuery(), - 'http.fragment' => $fullUri->getFragment(), - 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), - 'http.response.body.size' => $event->response->toPsrResponse()->getBody()->getSize(), - ] - )); - } - - protected function httpClientConnectionFailedHandler(HttpClientEvents\ConnectionFailed $event): void - { - if (!$this->recordHttpClientRequests) { - return; - } - - $fullUri = $this->getFullUri($event->request->url()); - - Integration::addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_ERROR, - Breadcrumb::TYPE_HTTP, - 'http', - null, - [ - 'url' => $this->getPartialUri($fullUri), - 'http.request.method' => $event->request->method(), - 'http.query' => $fullUri->getQuery(), - 'http.fragment' => $fullUri->getFragment(), - 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), - ] - )); - } - protected function authenticatedHandler(AuthEvents\Authenticated $event): void { $this->configureUserScopeFromModel($event->user); diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index e6779410..d0d8723a 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -2,33 +2,46 @@ namespace Sentry\Laravel\Features; +use GuzzleHttp\Psr7\Uri; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Http\Client\Events\ConnectionFailed; use Illuminate\Http\Client\Events\RequestSending; use Illuminate\Http\Client\Events\ResponseReceived; +use Psr\Http\Message\UriInterface; +use Sentry\Breadcrumb; use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans; -use Sentry\Laravel\Util\WorksWithUris; +use Sentry\Laravel\Integration; use Sentry\SentrySdk; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; class HttpClientIntegration extends Feature { - use TracksPushedScopesAndSpans, WorksWithUris; + use TracksPushedScopesAndSpans; + + private const FEATURE_KEY = 'http_client_requests'; public function isApplicable(): bool { - return $this->isTracingFeatureEnabled('http_client_requests'); + return $this->isTracingFeatureEnabled(self::FEATURE_KEY) + || $this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY); } public function onBoot(Dispatcher $events): void { - $events->listen(RequestSending::class, [$this, 'handleRequestSendingHandler']); - $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandler']); - $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandler']); + if ($this->isTracingFeatureEnabled(self::FEATURE_KEY)) { + $events->listen(RequestSending::class, [$this, 'handleRequestSendingHandlerForTracing']); + $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandlerForTracing']); + $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandlerForTracing']); + } + + if ($this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY)) { + $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandlerForBreadcrumb']); + $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandlerForBreadcrumb']); + } } - public function handleRequestSendingHandler(RequestSending $event): void + public function handleRequestSendingHandlerForTracing(RequestSending $event): void { $parentSpan = SentrySdk::getCurrentHub()->getSpan(); @@ -54,7 +67,7 @@ public function handleRequestSendingHandler(RequestSending $event): void $this->pushSpan($parentSpan->startChild($context)); } - public function handleResponseReceivedHandler(ResponseReceived $event): void + public function handleResponseReceivedHandlerForTracing(ResponseReceived $event): void { $span = $this->maybePopSpan(); @@ -64,7 +77,7 @@ public function handleResponseReceivedHandler(ResponseReceived $event): void } } - public function handleConnectionFailedHandler(ConnectionFailed $event): void + public function handleConnectionFailedHandlerForTracing(ConnectionFailed $event): void { $span = $this->maybePopSpan(); @@ -73,4 +86,79 @@ public function handleConnectionFailedHandler(ConnectionFailed $event): void $span->setStatus(SpanStatus::internalError()); } } + + public function handleResponseReceivedHandlerForBreadcrumb(ResponseReceived $event): void + { + $level = Breadcrumb::LEVEL_INFO; + + if ($event->response->failed()) { + $level = Breadcrumb::LEVEL_ERROR; + } + + $fullUri = $this->getFullUri($event->request->url()); + + Integration::addBreadcrumb(new Breadcrumb( + $level, + Breadcrumb::TYPE_HTTP, + 'http', + null, + [ + 'url' => $this->getPartialUri($fullUri), + 'http.request.method' => $event->request->method(), + 'http.response.status_code' => $event->response->status(), + 'http.query' => $fullUri->getQuery(), + 'http.fragment' => $fullUri->getFragment(), + 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), + 'http.response.body.size' => $event->response->toPsrResponse()->getBody()->getSize(), + ] + )); + } + + public function handleConnectionFailedHandlerForBreadcrumb(ConnectionFailed $event): void + { + $fullUri = $this->getFullUri($event->request->url()); + + Integration::addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_ERROR, + Breadcrumb::TYPE_HTTP, + 'http', + null, + [ + 'url' => $this->getPartialUri($fullUri), + 'http.request.method' => $event->request->method(), + 'http.query' => $fullUri->getQuery(), + 'http.fragment' => $fullUri->getFragment(), + 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), + ] + )); + } + + /** + * Construct a full URI. + * + * @param string $url + * + * @return UriInterface + */ + private function getFullUri(string $url): UriInterface + { + return new Uri($url); + } + + /** + * Construct a partial URI, excluding the authority, query and fragment parts. + * + * @param UriInterface $uri + * + * @return string + */ + private function getPartialUri(UriInterface $uri): string + { + return (string) Uri::fromParts([ + 'scheme' => $uri->getScheme(), + 'host' => $uri->getHost(), + 'port' => $uri->getPort(), + 'path' => $uri->getPath(), + ]); + } } diff --git a/src/Sentry/Laravel/Util/WorksWithUris.php b/src/Sentry/Laravel/Util/WorksWithUris.php deleted file mode 100644 index 84937ee5..00000000 --- a/src/Sentry/Laravel/Util/WorksWithUris.php +++ /dev/null @@ -1,38 +0,0 @@ - $uri->getScheme(), - 'host' => $uri->getHost(), - 'port' => $uri->getPort(), - 'path' => $uri->getPath(), - ]); - } -} From cd73c11ac91b9f8f63cdf8850ffe6e55726eaeff Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:06:23 +0100 Subject: [PATCH 5/9] Add tests for disabling http_client_requests tracing/breadcrumbs --- .../Features/HttpClientIntegrationTest.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index fec2fbcf..21f29c2d 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -53,6 +53,20 @@ public function testHttpClientBreadcrumbDoesntConsumeBodyStream(): void $this->assertEquals('response', $response->toPsrResponse()->getBody()->getContents()); } + public function testHttpClientBreadcrumbIsNotRecordedWhenDisabled(): void + { + $this->resetApplicationWithConfig([ + 'sentry.breadcrumbs.http_client_requests' => false, + ]); + + $this->dispatchLaravelEvent(new ResponseReceived( + new Request(new PsrRequest('GET', 'https://example.com', [], 'request')), + new Response(new PsrResponse(200, [], 'response')) + )); + + $this->assertEmpty($this->getCurrentBreadcrumbs()); + } + public function testHttpClientSpanIsRecorded(): void { $transaction = $this->startTransaction(); @@ -93,4 +107,22 @@ public function testHttpClientSpanIsRecordedWithCorrectResult(): void $this->assertEquals('http.client', $span->getOp()); $this->assertEquals(SpanStatus::internalError(), $span->getStatus()); } + + public function testHttpClientSpanIsNotRecordedWhenDisabled(): void + { + $this->resetApplicationWithConfig([ + 'sentry.tracing.http_client_requests' => false, + ]); + + $transaction = $this->startTransaction(); + + $client = Http::fake(); + + $client->get('https://example.com'); + + /** @var \Sentry\Tracing\Span $span */ + $span = last($transaction->getSpanRecorder()->getSpans()); + + $this->assertNotEquals('http.client', $span->getOp()); + } } From 79fcca09a1525e4731e83513c5d5db2b1f6d7340 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:17:49 +0100 Subject: [PATCH 6/9] Update http client breadcrumb/span data fields --- .../Features/HttpClientIntegration.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index d0d8723a..32353079 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -59,9 +59,11 @@ public function handleRequestSendingHandlerForTracing(RequestSending $event): vo $context->setDescription($event->request->method() . ' ' . $partialUri); $context->setData([ 'url' => $partialUri, - 'http.request.method' => $event->request->method(), + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http 'http.query' => $fullUri->getQuery(), 'http.fragment' => $fullUri->getFragment(), + 'http.request.method' => $event->request->method(), + 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), ]); $this->pushSpan($parentSpan->startChild($context)); @@ -73,6 +75,11 @@ public function handleResponseReceivedHandlerForTracing(ResponseReceived $event) if ($span !== null) { $span->finish(); + $span->setData(array_merge($span->getData(), [ + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http + 'http.response.status_code' => $event->response->status(), + 'http.response.body.size' => $event->response->toPsrResponse()->getBody()->getSize(), + ])); $span->setHttpStatus($event->response->status()); } } @@ -104,10 +111,11 @@ public function handleResponseReceivedHandlerForBreadcrumb(ResponseReceived $eve null, [ 'url' => $this->getPartialUri($fullUri), - 'http.request.method' => $event->request->method(), - 'http.response.status_code' => $event->response->status(), + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http 'http.query' => $fullUri->getQuery(), 'http.fragment' => $fullUri->getFragment(), + 'http.request.method' => $event->request->method(), + 'http.response.status_code' => $event->response->status(), 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), 'http.response.body.size' => $event->response->toPsrResponse()->getBody()->getSize(), ] @@ -125,9 +133,10 @@ public function handleConnectionFailedHandlerForBreadcrumb(ConnectionFailed $eve null, [ 'url' => $this->getPartialUri($fullUri), - 'http.request.method' => $event->request->method(), + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http 'http.query' => $fullUri->getQuery(), 'http.fragment' => $fullUri->getFragment(), + 'http.request.method' => $event->request->method(), 'http.request.body.size' => $event->request->toPsrRequest()->getBody()->getSize(), ] )); @@ -154,7 +163,7 @@ private function getFullUri(string $url): UriInterface */ private function getPartialUri(UriInterface $uri): string { - return (string) Uri::fromParts([ + return (string)Uri::fromParts([ 'scheme' => $uri->getScheme(), 'host' => $uri->getHost(), 'port' => $uri->getPort(), From 64a2acadf753ba097d24a8e38f3ed9d491210764 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:32:28 +0100 Subject: [PATCH 7/9] Fix re-using a little too much --- .../Concerns/TracksPushedScopesAndSpans.php | 9 --------- .../Laravel/Features/QueueIntegration.php | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php b/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php index edd11da4..922458f7 100644 --- a/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php +++ b/src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php @@ -4,8 +4,6 @@ use Sentry\Laravel\Integration; use Sentry\SentrySdk; -use Sentry\State\Scope; -use Sentry\Tracing\PropagationContext; use Sentry\Tracing\Span; trait TracksPushedScopesAndSpans @@ -47,13 +45,6 @@ protected function pushScope(): void SentrySdk::getCurrentHub()->pushScope(); ++$this->pushedScopeCount; - - // When a job starts, we want to make sure the scope is cleared of breadcrumbs - // as well as setting a new propagation context. - SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) { - $scope->clearBreadcrumbs(); - $scope->setPropagationContext(PropagationContext::fromDefaults()); - }); } protected function maybePopSpan(): ?Span diff --git a/src/Sentry/Laravel/Features/QueueIntegration.php b/src/Sentry/Laravel/Features/QueueIntegration.php index f655e35f..28560dc4 100644 --- a/src/Sentry/Laravel/Features/QueueIntegration.php +++ b/src/Sentry/Laravel/Features/QueueIntegration.php @@ -14,7 +14,6 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; -use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionContext; @@ -26,7 +25,9 @@ class QueueIntegration extends Feature { - use TracksPushedScopesAndSpans; + use TracksPushedScopesAndSpans { + pushScope as private pushScopeTrait; + } private const QUEUE_PAYLOAD_BAGGAGE_DATA = 'sentry_baggage_data'; private const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data'; @@ -172,4 +173,16 @@ private function finishJobWithStatus(SpanStatus $status): void $span->setStatus($status); } } + + protected function pushScope(): void + { + $this->pushScopeTrait(); + + // When a job starts, we want to make sure the scope is cleared of breadcrumbs + // as well as setting a new propagation context. + SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) { + $scope->clearBreadcrumbs(); + $scope->setPropagationContext(PropagationContext::fromDefaults()); + }); + } } From ed237a193d96e5464ad451672d5e4601677af055 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:56:09 +0100 Subject: [PATCH 8/9] Add tracing headers to http client requests --- .../Features/HttpClientIntegration.php | 35 ++++++++++++++++++- .../Features/HttpClientIntegrationTest.php | 21 +++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index 32353079..f8e25fe1 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -7,6 +7,8 @@ use Illuminate\Http\Client\Events\ConnectionFailed; use Illuminate\Http\Client\Events\RequestSending; use Illuminate\Http\Client\Events\ResponseReceived; +use Illuminate\Http\Client\Factory; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; use Sentry\Breadcrumb; use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans; @@ -14,6 +16,8 @@ use Sentry\SentrySdk; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; +use function Sentry\getBaggage; +use function Sentry\getTraceparent; class HttpClientIntegration extends Feature { @@ -27,12 +31,16 @@ public function isApplicable(): bool || $this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY); } - public function onBoot(Dispatcher $events): void + public function onBoot(Dispatcher $events, Factory $factory): void { if ($this->isTracingFeatureEnabled(self::FEATURE_KEY)) { $events->listen(RequestSending::class, [$this, 'handleRequestSendingHandlerForTracing']); $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandlerForTracing']); $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandlerForTracing']); + + if (method_exists($factory, 'globalRequestMiddleware')) { + $factory->globalRequestMiddleware([$this, 'attachTracingHeadersToRequest']); + } } if ($this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY)) { @@ -41,6 +49,17 @@ public function onBoot(Dispatcher $events): void } } + public function attachTracingHeadersToRequest(RequestInterface $request) + { + if ($this->shouldAttachTracingHeaders($request)) { + return $request + ->withHeader('baggage', getBaggage()) + ->withHeader('sentry-trace', getTraceparent()); + } + + return $request; + } + public function handleRequestSendingHandlerForTracing(RequestSending $event): void { $parentSpan = SentrySdk::getCurrentHub()->getSpan(); @@ -170,4 +189,18 @@ private function getPartialUri(UriInterface $uri): string 'path' => $uri->getPath(), ]); } + + private function shouldAttachTracingHeaders(RequestInterface $request): bool + { + $client = SentrySdk::getCurrentHub()->getClient(); + if ($client === null) { + return false; + } + + $sdkOptions = $client->getOptions(); + + // Check if the request destination is allow listed in the trace_propagation_targets option. + return $sdkOptions->getTracePropagationTargets() === null + || in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()); + } } diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index 21f29c2d..f47debe1 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -125,4 +125,25 @@ public function testHttpClientSpanIsNotRecordedWhenDisabled(): void $this->assertNotEquals('http.client', $span->getOp()); } + + public function testHttpClientRequestTracingHeadersAreAttached(): void + { + $this->resetApplicationWithConfig([ + 'sentry.trace_propagation_targets' => ['example.com'], + ]); + + $client = Http::fake(); + + $client->get('https://example.com'); + + Http::assertSent(function (Request $request) { + return $request->hasHeader('baggage') && $request->hasHeader('sentry-trace'); + }); + + $client->get('https://no-headers.example.com'); + + Http::assertSent(function (Request $request) { + return !$request->hasHeader('baggage') && !$request->hasHeader('sentry-trace'); + }); + } } From 154e32fbfbd8fc6c8c62805653f63284c729d744 Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Mon, 13 Nov 2023 22:58:33 +0100 Subject: [PATCH 9/9] Fix tests --- src/Sentry/Laravel/EventHandler.php | 1 + src/Sentry/Laravel/Features/HttpClientIntegration.php | 1 + test/Sentry/Features/HttpClientIntegrationTest.php | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php index 99c50d2c..7a9eddaa 100644 --- a/src/Sentry/Laravel/EventHandler.php +++ b/src/Sentry/Laravel/EventHandler.php @@ -18,6 +18,7 @@ use Laravel\Sanctum\Events as Sanctum; use RuntimeException; use Sentry\Breadcrumb; +use Sentry\Laravel\Tracing\Middleware; use Sentry\SentrySdk; use Sentry\State\Scope; use Symfony\Component\Console\Input\ArgvInput; diff --git a/src/Sentry/Laravel/Features/HttpClientIntegration.php b/src/Sentry/Laravel/Features/HttpClientIntegration.php index f8e25fe1..be57e7f5 100644 --- a/src/Sentry/Laravel/Features/HttpClientIntegration.php +++ b/src/Sentry/Laravel/Features/HttpClientIntegration.php @@ -38,6 +38,7 @@ public function onBoot(Dispatcher $events, Factory $factory): void $events->listen(ResponseReceived::class, [$this, 'handleResponseReceivedHandlerForTracing']); $events->listen(ConnectionFailed::class, [$this, 'handleConnectionFailedHandlerForTracing']); + // The `globalRequestMiddleware` functionality was introduced in Laravel 10.14 if (method_exists($factory, 'globalRequestMiddleware')) { $factory->globalRequestMiddleware([$this, 'attachTracingHeadersToRequest']); } diff --git a/test/Sentry/Features/HttpClientIntegrationTest.php b/test/Sentry/Features/HttpClientIntegrationTest.php index f47debe1..edd61698 100644 --- a/test/Sentry/Features/HttpClientIntegrationTest.php +++ b/test/Sentry/Features/HttpClientIntegrationTest.php @@ -64,7 +64,7 @@ public function testHttpClientBreadcrumbIsNotRecordedWhenDisabled(): void new Response(new PsrResponse(200, [], 'response')) )); - $this->assertEmpty($this->getCurrentBreadcrumbs()); + $this->assertEmpty($this->getCurrentSentryBreadcrumbs()); } public function testHttpClientSpanIsRecorded(): void @@ -128,6 +128,10 @@ public function testHttpClientSpanIsNotRecordedWhenDisabled(): void public function testHttpClientRequestTracingHeadersAreAttached(): void { + if (!method_exists(Http::class, 'globalRequestMiddleware')) { + $this->markTestSkipped('The `globalRequestMiddleware` functionality we rely on was introduced in Laravel 10.14'); + } + $this->resetApplicationWithConfig([ 'sentry.trace_propagation_targets' => ['example.com'], ]);