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

Add support for HTTP Client baggage propagation #675

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
- Add support for tracing of the Symfony HTTP client requests (#606)
- Support logging the impersonator user, if any (#647)
- ref: Use constant for the SDK version (#662)
- Add support for HTTP client baggage propagation (#663)

## 4.3.1 (2022-10-10)

Expand Down
5 changes: 5 additions & 0 deletions src/DependencyInjection/Configuration.php
Expand Up @@ -48,6 +48,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->arrayNode('options')
->addDefaultsIfNotSet()
->fixXmlConfig('integration')
->fixXmlConfig('trace_propagation_target')
->fixXmlConfig('tag')
->fixXmlConfig('class_serializer')
->fixXmlConfig('prefix', 'prefixes')
Expand All @@ -72,6 +73,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->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.')
->end()
->scalarNode('traces_sampler')->end()
->arrayNode('trace_propagation_targets')
->scalarPrototype()->end()
->beforeNormalization()->castToArray()->end()
->end()
->booleanNode('attach_stacktrace')->end()
->integerNode('context_lines')->min(0)->end()
->booleanNode('enable_compression')->end()
Expand Down
1 change: 1 addition & 0 deletions src/Resources/config/schema/sentry-1.0.xsd
Expand Up @@ -23,6 +23,7 @@
<xsd:complexType name="options">
<xsd:sequence>
<xsd:element name="integration" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="trace-propagation-target" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="prefix" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="in-app-exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
Expand Down
13 changes: 13 additions & 0 deletions src/Tracing/HttpClient/AbstractTraceableHttpClient.php
Expand Up @@ -4,6 +4,7 @@

namespace Sentry\SentryBundle\Tracing\HttpClient;

use GuzzleHttp\Psr7\Uri;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Sentry\State\HubInterface;
Expand Down Expand Up @@ -49,6 +50,18 @@ public function request(string $method, string $url, array $options = []): Respo
if (null !== $parent) {
$headers = $options['headers'] ?? [];
$headers['sentry-trace'] = $parent->toTraceparent();

// Check if the request destination is allow listed in the trace_propagation_targets option.
$client = $this->hub->getClient();
if (null !== $client) {
$sdkOptions = $client->getOptions();
$uri = new Uri($url);

if (\in_array($uri->getHost(), $sdkOptions->getTracePropagationTargets())) {
$headers['baggage'] = $parent->toBaggage();
}
}

$options['headers'] = $headers;

$context = new SpanContext();
Expand Down
1 change: 1 addition & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Expand Up @@ -26,6 +26,7 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
'options' => [
'integrations' => [],
'prefixes' => array_merge(['%kernel.project_dir%'], array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: ''))),
'trace_propagation_targets' => [],
'environment' => '%kernel.environment%',
'release' => PrettyVersions::getRootPackageVersion()->getPrettyVersion(),
'tags' => [],
Expand Down
1 change: 1 addition & 0 deletions tests/DependencyInjection/Fixtures/php/full.php
Expand Up @@ -17,6 +17,7 @@
'sample_rate' => 1,
'traces_sample_rate' => 1,
'traces_sampler' => 'App\\Sentry\\Tracing\\TracesSampler',
'trace_propagation_targets' => ['website.invalid'],
'attach_stacktrace' => true,
'context_lines' => 0,
'enable_compression' => true,
Expand Down
1 change: 1 addition & 0 deletions tests/DependencyInjection/Fixtures/xml/full.xml
Expand Up @@ -36,6 +36,7 @@
max-request-body-size="none"
>
<sentry:integration>App\Sentry\Integration\FooIntegration</sentry:integration>
<sentry:trace-propagation-target>website.invalid</sentry:trace-propagation-target>
<sentry:prefix>%kernel.project_dir%</sentry:prefix>
<sentry:tag name="context">development</sentry:tag>
<sentry:in-app-exclude>%kernel.cache_dir%</sentry:in-app-exclude>
Expand Down
2 changes: 2 additions & 0 deletions tests/DependencyInjection/Fixtures/yml/full.yml
Expand Up @@ -12,6 +12,8 @@ sentry:
sample_rate: 1
traces_sample_rate: 1
traces_sampler: App\Sentry\Tracing\TracesSampler
trace_propagation_targets:
- 'website.invalid'
attach_stacktrace: true
context_lines: 0
enable_compression: true
Expand Down
1 change: 1 addition & 0 deletions tests/DependencyInjection/SentryExtensionTest.php
Expand Up @@ -202,6 +202,7 @@ public function testClientIsCreatedFromOptions(): void
'sample_rate' => 1,
'traces_sample_rate' => 1,
'traces_sampler' => new Reference('App\\Sentry\\Tracing\\TracesSampler'),
'trace_propagation_targets' => ['website.invalid'],
'attach_stacktrace' => true,
'context_lines' => 0,
'enable_compression' => true,
Expand Down
99 changes: 99 additions & 0 deletions tests/Tracing/HttpClient/TraceableHttpClientTest.php
Expand Up @@ -8,6 +8,8 @@
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\NullLogger;
use Sentry\ClientInterface;
use Sentry\Options;
use Sentry\SentryBundle\Tracing\HttpClient\AbstractTraceableResponse;
use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClient;
use Sentry\State\HubInterface;
Expand Down Expand Up @@ -68,6 +70,7 @@ public function testRequest(): void
$this->assertSame('GET', $response->getInfo('http_method'));
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
$this->assertArrayNotHasKey('baggage', $mockResponse->getRequestOptions()['normalized_headers']);
$this->assertNotNull($transaction->getSpanRecorder());

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

public function testRequestDoesNotContainBaggageHeader(): void
{
$options = new Options([
'dsn' => 'http://public:secret@example.com/sentry/1',
'trace_propagation_targets' => ['non-matching-host.invalid'],
]);
$client = $this->createMock(ClientInterface::class);
$client
->expects($this->once())
->method('getOptions')
->willReturn($options);

$transaction = new Transaction(new TransactionContext());
$transaction->initSpanRecorder();

$this->hub->expects($this->once())
->method('getSpan')
->willReturn($transaction);
$this->hub->expects($this->once())
->method('getClient')
->willReturn($client);

$mockResponse = new MockResponse();
$decoratedHttpClient = new MockHttpClient($mockResponse);
$httpClient = new TraceableHttpClient($decoratedHttpClient, $this->hub);
$response = $httpClient->request('PUT', 'https://www.example.com/test-page');

$this->assertInstanceOf(AbstractTraceableResponse::class, $response);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('PUT', $response->getInfo('http_method'));
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
$this->assertArrayNotHasKey('baggage', $mockResponse->getRequestOptions()['normalized_headers']);
$this->assertNotNull($transaction->getSpanRecorder());

$spans = $transaction->getSpanRecorder()->getSpans();
$expectedTags = [
'http.method' => 'PUT',
'http.url' => 'https://www.example.com/test-page',
];

$this->assertCount(2, $spans);
$this->assertNull($spans[1]->getEndTimestamp());
$this->assertSame('http.client', $spans[1]->getOp());
$this->assertSame('HTTP PUT', $spans[1]->getDescription());
$this->assertSame($expectedTags, $spans[1]->getTags());
}

public function testRequestDoesContainBaggageHeader(): void
{
$options = new Options([
'dsn' => 'http://public:secret@example.com/sentry/1',
'trace_propagation_targets' => ['www.example.com'],
]);
$client = $this->createMock(ClientInterface::class);
$client
->expects($this->once())
->method('getOptions')
->willReturn($options);

$transaction = new Transaction(new TransactionContext());
$transaction->initSpanRecorder();

$this->hub->expects($this->once())
->method('getSpan')
->willReturn($transaction);
$this->hub->expects($this->once())
->method('getClient')
->willReturn($client);

$mockResponse = new MockResponse();
$decoratedHttpClient = new MockHttpClient($mockResponse);
$httpClient = new TraceableHttpClient($decoratedHttpClient, $this->hub);
$response = $httpClient->request('POST', 'https://www.example.com/test-page');

$this->assertInstanceOf(AbstractTraceableResponse::class, $response);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('POST', $response->getInfo('http_method'));
$this->assertSame('https://www.example.com/test-page', $response->getInfo('url'));
$this->assertSame(['sentry-trace: ' . $transaction->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']);
$this->assertSame(['baggage: ' . $transaction->toBaggage()], $mockResponse->getRequestOptions()['normalized_headers']['baggage']);
$this->assertNotNull($transaction->getSpanRecorder());

$spans = $transaction->getSpanRecorder()->getSpans();
$expectedTags = [
'http.method' => 'POST',
'http.url' => 'https://www.example.com/test-page',
];

$this->assertCount(2, $spans);
$this->assertNull($spans[1]->getEndTimestamp());
$this->assertSame('http.client', $spans[1]->getOp());
$this->assertSame('HTTP POST', $spans[1]->getDescription());
$this->assertSame($expectedTags, $spans[1]->getTags());
}

public function testStream(): void
{
$transaction = new Transaction(new TransactionContext());
Expand Down