Skip to content

Commit 76b46d2

Browse files
ste93crycleptric
andauthored
Fix the decoration of the HTTP client when tracing is enabled (#786)
Co-authored-by: Michi Hoffmann <[email protected]>
1 parent bbbe69f commit 76b46d2

File tree

3 files changed

+81
-16
lines changed

3 files changed

+81
-16
lines changed

src/DependencyInjection/Compiler/HttpClientTracingPass.php

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212

1313
final class HttpClientTracingPass implements CompilerPassInterface
1414
{
15+
/**
16+
* List of service IDs that can be registered in the container by the
17+
* framework when decorating the mock client. The order is from the
18+
* outermost decorator to the innermost.
19+
*/
20+
private const MOCK_HTTP_CLIENT_SERVICE_IDS = [
21+
'http_client.retryable.inner.mock_client',
22+
'.debug.http_client.inner.mock_client',
23+
'http_client.mock_client',
24+
];
25+
1526
/**
1627
* {@inheritdoc}
1728
*/
@@ -21,12 +32,40 @@ public function process(ContainerBuilder $container): void
2132
return;
2233
}
2334

24-
foreach ($container->findTaggedServiceIds('http_client.client') as $id => $tags) {
25-
$container->register('.sentry.traceable.' . $id, TraceableHttpClient::class)
26-
->setDecoratedService($id)
27-
->setArgument(0, new Reference('.sentry.traceable.' . $id . '.inner'))
28-
->setArgument(1, new Reference(HubInterface::class))
29-
->addTag('kernel.reset', ['method' => 'reset']);
35+
$decoratedService = $this->getDecoratedService($container);
36+
37+
$container->register(TraceableHttpClient::class, TraceableHttpClient::class)
38+
->setArgument(0, new Reference(TraceableHttpClient::class . '.inner'))
39+
->setArgument(1, new Reference(HubInterface::class))
40+
->setDecoratedService($decoratedService[0], null, $decoratedService[1]);
41+
}
42+
43+
/**
44+
* @return array{string, int}
45+
*/
46+
private function getDecoratedService(ContainerBuilder $container): array
47+
{
48+
// Starting from Symfony 6.3, the raw HTTP client that serves as adapter
49+
// for the transport is registered as a separate service, so that the
50+
// scoped clients can inject it before any decoration is applied on them.
51+
// Since we need to access the full URL of the request, and such information
52+
// is available after the `ScopingHttpClient` class did its job, we have
53+
// to decorate such service. For more details, see https://github.com/symfony/symfony/pull/49513.
54+
if ($container->hasDefinition('http_client.transport')) {
55+
return ['http_client.transport', -15];
56+
}
57+
58+
// On versions of Symfony prior to 6.3, when the mock client is in-use,
59+
// each HTTP client is decorated by referencing explicitly the innermost
60+
// service rather than by using the standard decoration feature. Hence,
61+
// we have to look for the specific names of those services, and decorate
62+
// them instead of the raw HTTP client.
63+
foreach (self::MOCK_HTTP_CLIENT_SERVICE_IDS as $httpClientServiceId) {
64+
if ($container->hasDefinition($httpClientServiceId)) {
65+
return [$httpClientServiceId, 15];
66+
}
3067
}
68+
69+
return ['http_client', 15];
3170
}
3271
}

tests/DependencyInjection/Compiler/HttpClientTracingPassTest.php

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use Sentry\SentryBundle\Tracing\HttpClient\TraceableHttpClient;
1010
use Sentry\State\HubInterface;
1111
use Symfony\Component\DependencyInjection\ContainerBuilder;
12-
use Symfony\Component\HttpClient\HttpClient;
1312
use Symfony\Contracts\HttpClient\HttpClientInterface;
1413

1514
final class HttpClientTracingPassTest extends TestCase
@@ -21,23 +20,49 @@ public static function setUpBeforeClass(): void
2120
}
2221
}
2322

24-
public function testProcess(): void
23+
/**
24+
* @dataProvider processDataProvider
25+
*/
26+
public function testProcess(string $httpClientServiceId): void
2527
{
26-
$container = $this->createContainerBuilder(true, true);
28+
$container = $this->createContainerBuilder(true, true, $httpClientServiceId);
2729
$container->compile();
2830

29-
$this->assertSame(TraceableHttpClient::class, $container->findDefinition('http.client')->getClass());
31+
$this->assertSame(TraceableHttpClient::class, $container->getDefinition($httpClientServiceId)->getClass());
32+
}
33+
34+
public function processDataProvider(): \Generator
35+
{
36+
yield 'The framework version is >=6.3' => [
37+
'http_client.transport',
38+
];
39+
40+
yield 'The framework version is <6.3 and the mocked HTTP client is decorated by the retryable client' => [
41+
'http_client.retryable.inner.mock_client',
42+
];
43+
44+
yield 'The framework version is <6.3 and the mocked HTTP client is decorated by the profiler' => [
45+
'.debug.http_client.inner.mock_client',
46+
];
47+
48+
yield 'The framework version is <6.3 and the mocked HTTP client is not decorated' => [
49+
'http_client.mock_client',
50+
];
51+
52+
yield 'The framework version is <6.3 and the HTTP client is not mocked' => [
53+
'http_client',
54+
];
3055
}
3156

3257
/**
3358
* @dataProvider processDoesNothingIfConditionsForEnablingTracingAreMissingDataProvider
3459
*/
3560
public function testProcessDoesNothingIfConditionsForEnablingTracingAreMissing(bool $isTracingEnabled, bool $isHttpClientTracingEnabled): void
3661
{
37-
$container = $this->createContainerBuilder($isTracingEnabled, $isHttpClientTracingEnabled);
62+
$container = $this->createContainerBuilder($isTracingEnabled, $isHttpClientTracingEnabled, 'http_client.transport');
3863
$container->compile();
3964

40-
$this->assertSame(HttpClient::class, $container->getDefinition('http.client')->getClass());
65+
$this->assertSame(HttpClientInterface::class, $container->getDefinition('http_client.transport')->getClass());
4166
}
4267

4368
/**
@@ -61,7 +86,7 @@ public function processDoesNothingIfConditionsForEnablingTracingAreMissingDataPr
6186
];
6287
}
6388

64-
private function createContainerBuilder(bool $isTracingEnabled, bool $isHttpClientTracingEnabled): ContainerBuilder
89+
private function createContainerBuilder(bool $isTracingEnabled, bool $isHttpClientTracingEnabled, string $httpClientServiceId): ContainerBuilder
6590
{
6691
$container = new ContainerBuilder();
6792
$container->addCompilerPass(new HttpClientTracingPass());
@@ -71,9 +96,8 @@ private function createContainerBuilder(bool $isTracingEnabled, bool $isHttpClie
7196
$container->register(HubInterface::class, HubInterface::class)
7297
->setPublic(true);
7398

74-
$container->register('http.client', HttpClient::class)
75-
->setPublic(true)
76-
->addTag('http_client.client');
99+
$container->register($httpClientServiceId, HttpClientInterface::class)
100+
->setPublic(true);
77101

78102
return $container;
79103
}

tests/DependencyInjection/SentryExtensionTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
use Symfony\Component\DependencyInjection\Reference;
4141
use Symfony\Component\ErrorHandler\Error\FatalError;
4242
use Symfony\Component\HttpClient\HttpClient;
43+
use Symfony\Component\HttpClient\TraceableHttpClient;
4344
use Symfony\Component\HttpKernel\KernelEvents;
4445
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
4546
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
@@ -373,6 +374,7 @@ public function testInstrumentationIsDisabledWhenTracingIsDisabled(): void
373374
$this->assertFalse($container->hasDefinition(TracingDriverMiddleware::class));
374375
$this->assertFalse($container->hasDefinition(ConnectionConfigurator::class));
375376
$this->assertFalse($container->hasDefinition(TwigTracingExtension::class));
377+
$this->assertFalse($container->hasDefinition(TraceableHttpClient::class));
376378
$this->assertFalse($container->getParameter('sentry.tracing.enabled'));
377379
$this->assertEmpty($container->getParameter('sentry.tracing.dbal.connections'));
378380
}

0 commit comments

Comments
 (0)