Skip to content

Commit 38f70b7

Browse files
Add support for HTTP Client baggage propagation (#675)
Co-authored-by: Michi Hoffmann <[email protected]>
1 parent b1b8b17 commit 38f70b7

File tree

10 files changed

+125
-0
lines changed

10 files changed

+125
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- feat: Add support for tracing of the Symfony HTTP client requests (#606)
66
- feat: Support logging the impersonator user, if any (#647)
77
- ref: Use constant for the SDK version (#662)
8+
- Add support for HTTP client baggage propagation (#663)
89

910
## 4.4.0 (2022-10-20)
1011

src/DependencyInjection/Configuration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function getConfigTreeBuilder(): TreeBuilder
4848
->arrayNode('options')
4949
->addDefaultsIfNotSet()
5050
->fixXmlConfig('integration')
51+
->fixXmlConfig('trace_propagation_target')
5152
->fixXmlConfig('tag')
5253
->fixXmlConfig('class_serializer')
5354
->fixXmlConfig('prefix', 'prefixes')
@@ -72,6 +73,10 @@ public function getConfigTreeBuilder(): TreeBuilder
7273
->info('The sampling factor to apply to transactions. A value of 0 will deny sending any transaction, and a value of 1 will send all transactions.')
7374
->end()
7475
->scalarNode('traces_sampler')->end()
76+
->arrayNode('trace_propagation_targets')
77+
->scalarPrototype()->end()
78+
->beforeNormalization()->castToArray()->end()
79+
->end()
7580
->booleanNode('attach_stacktrace')->end()
7681
->integerNode('context_lines')->min(0)->end()
7782
->booleanNode('enable_compression')->end()

src/Resources/config/schema/sentry-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<xsd:complexType name="options">
2424
<xsd:sequence>
2525
<xsd:element name="integration" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
26+
<xsd:element name="trace-propagation-target" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
2627
<xsd:element name="prefix" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
2728
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
2829
<xsd:element name="in-app-exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

src/Tracing/HttpClient/AbstractTraceableHttpClient.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Sentry\SentryBundle\Tracing\HttpClient;
66

7+
use GuzzleHttp\Psr7\Uri;
78
use Psr\Log\LoggerAwareInterface;
89
use Psr\Log\LoggerInterface;
910
use Sentry\State\HubInterface;
@@ -49,6 +50,18 @@ public function request(string $method, string $url, array $options = []): Respo
4950
if (null !== $parent) {
5051
$headers = $options['headers'] ?? [];
5152
$headers['sentry-trace'] = $parent->toTraceparent();
53+
54+
// Check if the request destination is allow listed in the trace_propagation_targets option.
55+
$client = $this->hub->getClient();
56+
if (null !== $client) {
57+
$sdkOptions = $client->getOptions();
58+
$uri = new Uri($url);
59+
60+
if (\in_array($uri->getHost(), $sdkOptions->getTracePropagationTargets())) {
61+
$headers['baggage'] = $parent->toBaggage();
62+
}
63+
}
64+
5265
$options['headers'] = $headers;
5366

5467
$context = new SpanContext();

tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
2626
'options' => [
2727
'integrations' => [],
2828
'prefixes' => array_merge(['%kernel.project_dir%'], array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: ''))),
29+
'trace_propagation_targets' => [],
2930
'environment' => '%kernel.environment%',
3031
'release' => PrettyVersions::getRootPackageVersion()->getPrettyVersion(),
3132
'tags' => [],

tests/DependencyInjection/Fixtures/php/full.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
'sample_rate' => 1,
1818
'traces_sample_rate' => 1,
1919
'traces_sampler' => 'App\\Sentry\\Tracing\\TracesSampler',
20+
'trace_propagation_targets' => ['website.invalid'],
2021
'attach_stacktrace' => true,
2122
'context_lines' => 0,
2223
'enable_compression' => true,

tests/DependencyInjection/Fixtures/xml/full.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
max-request-body-size="none"
3737
>
3838
<sentry:integration>App\Sentry\Integration\FooIntegration</sentry:integration>
39+
<sentry:trace-propagation-target>website.invalid</sentry:trace-propagation-target>
3940
<sentry:prefix>%kernel.project_dir%</sentry:prefix>
4041
<sentry:tag name="context">development</sentry:tag>
4142
<sentry:in-app-exclude>%kernel.cache_dir%</sentry:in-app-exclude>

tests/DependencyInjection/Fixtures/yml/full.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ sentry:
1212
sample_rate: 1
1313
traces_sample_rate: 1
1414
traces_sampler: App\Sentry\Tracing\TracesSampler
15+
trace_propagation_targets:
16+
- 'website.invalid'
1517
attach_stacktrace: true
1618
context_lines: 0
1719
enable_compression: true

tests/DependencyInjection/SentryExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ public function testClientIsCreatedFromOptions(): void
202202
'sample_rate' => 1,
203203
'traces_sample_rate' => 1,
204204
'traces_sampler' => new Reference('App\\Sentry\\Tracing\\TracesSampler'),
205+
'trace_propagation_targets' => ['website.invalid'],
205206
'attach_stacktrace' => true,
206207
'context_lines' => 0,
207208
'enable_compression' => true,

tests/Tracing/HttpClient/TraceableHttpClientTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use PHPUnit\Framework\TestCase;
99
use Psr\Log\LoggerAwareInterface;
1010
use Psr\Log\NullLogger;
11+
use Sentry\ClientInterface;
12+
use Sentry\Options;
1113
use Sentry\SentryBundle\Tracing\HttpClient\AbstractTraceableResponse;
1214
use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClient;
1315
use Sentry\State\HubInterface;
@@ -68,6 +70,7 @@ public function testRequest(): void
6870
$this->assertSame('GET', $response->getInfo('http_method'));
6971
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
7072
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
73+
$this->assertArrayNotHasKey('baggage', $mockResponse->getRequestOptions()['normalized_headers']);
7174
$this->assertNotNull($transaction->getSpanRecorder());
7275

7376
$spans = $transaction->getSpanRecorder()->getSpans();
@@ -83,6 +86,102 @@ public function testRequest(): void
8386
$this->assertSame($expectedTags, $spans[1]->getTags());
8487
}
8588

89+
public function testRequestDoesNotContainBaggageHeader(): void
90+
{
91+
$options = new Options([
92+
'dsn' => 'http://public:[email protected]/sentry/1',
93+
'trace_propagation_targets' => ['non-matching-host.invalid'],
94+
]);
95+
$client = $this->createMock(ClientInterface::class);
96+
$client
97+
->expects($this->once())
98+
->method('getOptions')
99+
->willReturn($options);
100+
101+
$transaction = new Transaction(new TransactionContext());
102+
$transaction->initSpanRecorder();
103+
104+
$this->hub->expects($this->once())
105+
->method('getSpan')
106+
->willReturn($transaction);
107+
$this->hub->expects($this->once())
108+
->method('getClient')
109+
->willReturn($client);
110+
111+
$mockResponse = new MockResponse();
112+
$decoratedHttpClient = new MockHttpClient($mockResponse);
113+
$httpClient = new TraceableHttpClient($decoratedHttpClient, $this->hub);
114+
$response = $httpClient->request('PUT', 'https://www.example.com/test-page');
115+
116+
$this->assertInstanceOf(AbstractTraceableResponse::class, $response);
117+
$this->assertSame(200, $response->getStatusCode());
118+
$this->assertSame('PUT', $response->getInfo('http_method'));
119+
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
120+
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
121+
$this->assertArrayNotHasKey('baggage', $mockResponse->getRequestOptions()['normalized_headers']);
122+
$this->assertNotNull($transaction->getSpanRecorder());
123+
124+
$spans = $transaction->getSpanRecorder()->getSpans();
125+
$expectedTags = [
126+
'http.method' => 'PUT',
127+
'http.url' => 'https://www.example.com/test-page',
128+
];
129+
130+
$this->assertCount(2, $spans);
131+
$this->assertNull($spans[1]->getEndTimestamp());
132+
$this->assertSame('http.client', $spans[1]->getOp());
133+
$this->assertSame('HTTP PUT', $spans[1]->getDescription());
134+
$this->assertSame($expectedTags, $spans[1]->getTags());
135+
}
136+
137+
public function testRequestDoesContainBaggageHeader(): void
138+
{
139+
$options = new Options([
140+
'dsn' => 'http://public:[email protected]/sentry/1',
141+
'trace_propagation_targets' => ['www.example.com'],
142+
]);
143+
$client = $this->createMock(ClientInterface::class);
144+
$client
145+
->expects($this->once())
146+
->method('getOptions')
147+
->willReturn($options);
148+
149+
$transaction = new Transaction(new TransactionContext());
150+
$transaction->initSpanRecorder();
151+
152+
$this->hub->expects($this->once())
153+
->method('getSpan')
154+
->willReturn($transaction);
155+
$this->hub->expects($this->once())
156+
->method('getClient')
157+
->willReturn($client);
158+
159+
$mockResponse = new MockResponse();
160+
$decoratedHttpClient = new MockHttpClient($mockResponse);
161+
$httpClient = new TraceableHttpClient($decoratedHttpClient, $this->hub);
162+
$response = $httpClient->request('POST', 'https://www.example.com/test-page');
163+
164+
$this->assertInstanceOf(AbstractTraceableResponse::class, $response);
165+
$this->assertSame(200, $response->getStatusCode());
166+
$this->assertSame('POST', $response->getInfo('http_method'));
167+
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
168+
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
169+
$this->assertSame(['baggage: ' . $transaction->toBaggage()], $mockResponse->getRequestOptions()['normalized_headers']['baggage']);
170+
$this->assertNotNull($transaction->getSpanRecorder());
171+
172+
$spans = $transaction->getSpanRecorder()->getSpans();
173+
$expectedTags = [
174+
'http.method' => 'POST',
175+
'http.url' => 'https://www.example.com/test-page',
176+
];
177+
178+
$this->assertCount(2, $spans);
179+
$this->assertNull($spans[1]->getEndTimestamp());
180+
$this->assertSame('http.client', $spans[1]->getOp());
181+
$this->assertSame('HTTP POST', $spans[1]->getDescription());
182+
$this->assertSame($expectedTags, $spans[1]->getTags());
183+
}
184+
86185
public function testStream(): void
87186
{
88187
$transaction = new Transaction(new TransactionContext());

0 commit comments

Comments
 (0)