Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions config/sentry.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
// Capture HTTP client requests as spans
'http_client_requests' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED', true),

// Capture where the HTTP client request originated from on the HTTP client request spans
'http_client_requests_origin' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ORIGIN_ENABLED', true),

// Define a threshold in milliseconds for HTTP client requests to resolve their origin
'http_client_requests_origin_threshold_ms' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ORIGIN_THRESHOLD_MS', 250),

// Capture Laravel cache events (hits, writes etc.) as spans
'cache' => env('SENTRY_TRACE_CACHE_ENABLED', true),

Expand Down
57 changes: 57 additions & 0 deletions src/Sentry/Laravel/Features/HttpClientIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use Sentry\Breadcrumb;
use Sentry\Laravel\Features\Concerns\ResolvesEventOrigin;
use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans;
use Sentry\Laravel\Integration;
use Sentry\SentrySdk;
Expand All @@ -21,10 +22,25 @@

class HttpClientIntegration extends Feature
{
use ResolvesEventOrigin;
use TracksPushedScopesAndSpans;

private const FEATURE_KEY = 'http_client_requests';

/**
* Indicates if we should trace the origin of the HTTP client requests.
*
* @var bool|null
*/
private $traceHttpClientRequestsOrigin;

/**
* The threshold in milliseconds for HTTP client requests to resolve their origin.
*
* @var int|null
*/
private $traceHttpClientRequestsOriginThresholdMs;

public function isApplicable(): bool
{
return $this->isTracingFeatureEnabled(self::FEATURE_KEY)
Expand Down Expand Up @@ -101,6 +117,19 @@ public function handleResponseReceivedHandlerForTracing(ResponseReceived $event)
'http.response.status_code' => $event->response->status(),
'http.response.body.size' => $event->response->toPsrResponse()->getBody()->getSize(),
]));

if ($this->shouldTraceHttpClientRequestsOrigin()) {
$duration = ($span->getEndTimestamp() ?? microtime(true)) - $span->getStartTimestamp();
$durationMs = $duration * 1000;

if ($durationMs >= $this->getHttpClientRequestsOriginThresholdMs()) {
$requestOrigin = $this->resolveEventOrigin();
if ($requestOrigin !== null) {
$span->setData(array_merge($span->getData(), $requestOrigin));
}
}
}

$span->setHttpStatus($event->response->status());
$span->finish();
}
Expand Down Expand Up @@ -203,4 +232,32 @@ private function shouldAttachTracingHeaders(RequestInterface $request): bool
return $sdkOptions->getTracePropagationTargets() === null
|| in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets());
}

/**
* Indicates if we should trace the origin of the HTTP client requests.
*/
private function shouldTraceHttpClientRequestsOrigin(): bool
{
if ($this->traceHttpClientRequestsOrigin === null) {
$tracingConfig = $this->getUserConfig()['tracing'] ?? [];

$this->traceHttpClientRequestsOrigin = ($tracingConfig['http_client_requests_origin'] ?? true) === true;
}

return $this->traceHttpClientRequestsOrigin;
}

/**
* Get the threshold in milliseconds for HTTP client requests to resolve their origin.
*/
private function getHttpClientRequestsOriginThresholdMs(): int
{
if ($this->traceHttpClientRequestsOriginThresholdMs === null) {
$tracingConfig = $this->getUserConfig()['tracing'] ?? [];

$this->traceHttpClientRequestsOriginThresholdMs = $tracingConfig['http_client_requests_origin_threshold_ms'] ?? 250;
}

return $this->traceHttpClientRequestsOriginThresholdMs;
}
}
84 changes: 84 additions & 0 deletions test/Sentry/Features/HttpClientIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,88 @@ public function testHttpClientRequestTracingHeadersAreAttached(): void
return !$request->hasHeader('baggage') && !$request->hasHeader('sentry-trace');
});
}

public function testHttpClientOriginIsResolvedWhenEnabled(): void
{
$this->resetApplicationWithConfig([
'sentry.tracing.http_client_requests_origin' => true,
'sentry.tracing.http_client_requests_origin_threshold_ms' => 0,
]);

$transaction = $this->startTransaction();

$client = Http::fake();

$client->get('https://example.com');

/** @var \Sentry\Tracing\Span $span */
$span = last($transaction->getSpanRecorder()->getSpans());

$this->assertArrayHasKey('code.filepath', $span->getData());
$this->assertArrayHasKey('code.lineno', $span->getData());
}

public function testHttpClientOriginIsNotResolvedWhenDisabled(): void
{
$this->resetApplicationWithConfig([
'sentry.tracing.http_client_requests_origin' => false,
]);

$transaction = $this->startTransaction();

$client = Http::fake();

$client->get('https://example.com');

/** @var \Sentry\Tracing\Span $span */
$span = last($transaction->getSpanRecorder()->getSpans());

$this->assertArrayNotHasKey('code.filepath', $span->getData());
$this->assertArrayNotHasKey('code.lineno', $span->getData());
}

public function testHttpClientOriginIsResolvedWhenOverThreshold(): void
{
$this->resetApplicationWithConfig([
'sentry.tracing.http_client_requests_origin' => true,
'sentry.tracing.http_client_requests_origin_threshold_ms' => 10,
]);

$transaction = $this->startTransaction();

$client = Http::fake([
'slow.example.com' => function () {
usleep(20000); // 20ms delay
return Http::response('OK');
},
]);

$client->get('https://slow.example.com');

/** @var \Sentry\Tracing\Span $span */
$span = last($transaction->getSpanRecorder()->getSpans());

$this->assertArrayHasKey('code.filepath', $span->getData());
$this->assertArrayHasKey('code.lineno', $span->getData());
}

public function testHttpClientOriginIsNotResolvedWhenUnderThreshold(): void
{
$this->resetApplicationWithConfig([
'sentry.tracing.http_client_requests_origin' => true,
'sentry.tracing.http_client_requests_origin_threshold_ms' => 1000,
]);

$transaction = $this->startTransaction();

$client = Http::fake();

$client->get('https://example.com');

/** @var \Sentry\Tracing\Span $span */
$span = last($transaction->getSpanRecorder()->getSpans());

$this->assertArrayNotHasKey('code.filepath', $span->getData());
$this->assertArrayNotHasKey('code.lineno', $span->getData());
}
}