Skip to content

Commit 5472681

Browse files
authored
Set the span status when tracing an HTTP client request (#748)
1 parent e429950 commit 5472681

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

Diff for: src/Tracing/HttpClient/AbstractTraceableResponse.php

+22-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Sentry\SentryBundle\Tracing\HttpClient;
66

77
use Sentry\Tracing\Span;
8+
use Sentry\Tracing\SpanStatus;
89
use Symfony\Contracts\HttpClient\ChunkInterface;
910
use Symfony\Contracts\HttpClient\HttpClientInterface;
1011
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -123,9 +124,27 @@ public static function stream(HttpClientInterface $client, iterable $responses,
123124

124125
private function finishSpan(): void
125126
{
126-
if (null !== $this->span) {
127-
$this->span->finish();
128-
$this->span = null;
127+
if (null === $this->span) {
128+
return;
129129
}
130+
131+
// We finish the span (which means setting the span end timestamp) first
132+
// to ensure the measured time is as close as possible to the duration of
133+
// the HTTP request
134+
$this->span->finish();
135+
136+
/** @var int $statusCode */
137+
$statusCode = $this->response->getInfo('http_code');
138+
139+
// If the returned status code is 0, it means that this info isn't available
140+
// yet (e.g. an error happened before the request was sent), hence we cannot
141+
// determine what happened.
142+
if (0 === $statusCode) {
143+
$this->span->setStatus(SpanStatus::unknownError());
144+
} else {
145+
$this->span->setStatus(SpanStatus::createFromHttpStatusCode($statusCode));
146+
}
147+
148+
$this->span = null;
130149
}
131150
}

Diff for: tests/Tracing/HttpClient/TraceableHttpClientTest.php

+53-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Sentry\State\Scope;
2020
use Sentry\Tracing\PropagationContext;
2121
use Sentry\Tracing\SpanId;
22+
use Sentry\Tracing\SpanStatus;
2223
use Sentry\Tracing\TraceId;
2324
use Sentry\Tracing\Transaction;
2425
use Sentry\Tracing\TransactionContext;
@@ -107,10 +108,17 @@ public function testRequest(): void
107108
'http.fragment' => 'baz',
108109
];
109110

111+
// Call gc to invoke destructors at the right time.
112+
unset($response);
113+
114+
gc_mem_caches();
115+
gc_collect_cycles();
116+
110117
$this->assertCount(2, $spans);
111-
$this->assertNull($spans[1]->getEndTimestamp());
118+
$this->assertNotNull($spans[1]->getEndTimestamp());
112119
$this->assertSame('http.client', $spans[1]->getOp());
113120
$this->assertSame('GET https://www.example.com/test-page', $spans[1]->getDescription());
121+
$this->assertSame(SpanStatus::ok(), $spans[1]->getStatus());
114122
$this->assertSame($expectedTags, $spans[1]->getTags());
115123
$this->assertSame($expectedData, $spans[1]->getData());
116124
}
@@ -194,6 +202,50 @@ public function testRequestDoesContainsTracingHeadersWithoutTransaction(): void
194202
$this->assertSame(['baggage: sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=test'], $mockResponse->getRequestOptions()['normalized_headers']['baggage']);
195203
}
196204

205+
public function testRequestSetsUnknownErrorAsSpanStatusIfResponseStatusCodeIsUnavailable(): void
206+
{
207+
$client = $this->createMock(ClientInterface::class);
208+
$client->expects($this->once())
209+
->method('getOptions')
210+
->willReturn(new Options(['dsn' => 'http://public:[email protected]/sentry/1']));
211+
212+
$transaction = new Transaction(new TransactionContext());
213+
$transaction->initSpanRecorder();
214+
215+
$this->hub->expects($this->once())
216+
->method('getSpan')
217+
->willReturn($transaction);
218+
219+
$this->hub->expects($this->once())
220+
->method('getClient')
221+
->willReturn($client);
222+
223+
$decoratedHttpClient = new MockHttpClient(new MockResponse());
224+
$httpClient = new TraceableHttpClient($decoratedHttpClient, $this->hub);
225+
226+
// Cancelling the response is the only way that does not override in any
227+
// way the status code and leave it set to 0. This is a required precondition
228+
// for the span status to be set to the expected value.
229+
$response = $httpClient->request('GET', 'https://www.example.com/test-page');
230+
$response->cancel();
231+
232+
$this->assertNotNull($transaction->getSpanRecorder());
233+
$this->assertInstanceOf(AbstractTraceableResponse::class, $response);
234+
235+
// Call gc to invoke destructors at the right time.
236+
unset($response);
237+
238+
gc_mem_caches();
239+
gc_collect_cycles();
240+
241+
$spans = $transaction->getSpanRecorder()->getSpans();
242+
243+
$this->assertCount(2, $spans);
244+
$this->assertNotNull($spans[1]->getEndTimestamp());
245+
$this->assertSame('GET https://www.example.com/test-page', $spans[1]->getDescription());
246+
$this->assertSame(SpanStatus::unknownError(), $spans[1]->getStatus());
247+
}
248+
197249
public function testStream(): void
198250
{
199251
$transaction = new Transaction(new TransactionContext());

0 commit comments

Comments
 (0)