8000 [HttpClient] Added TraceableHttpClient and WebProfiler panel · symfony/symfony@94eb7ea · GitHub
[go: up one dir, main page]

Skip to content

Commit 94eb7ea

Browse files
jeremyFreeAgenttyx
authored and
Timothée BARRAY
committed
[HttpClient] Added TraceableHttpClient and WebProfiler panel
Co-authored-by: Jérémy Romey <jeremy@free-agent.fr> Co-authored-by: Timothée Barray <tim@amicalement-web.net>
1 parent b59c865 commit 94eb7ea

File tree

13 files changed

+748
-7
lines changed

13 files changed

+748
-7
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
use Symfony\Component\Form\FormTypeGuesserInterface;
6565
use Symfony\Component\Form\FormTypeInterface;
6666
use Symfony\Component\HttpClient\ScopingHttpClient;
67+
use Symfony\Component\HttpClient\TraceableHttpClient;
6768
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
6869
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
6970
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
@@ -149,6 +150,7 @@ class FrameworkExtension extends Extension
149150
private $validatorConfigEnabled = false;
150151
private $messengerConfigEnabled = false;
151152
private $mailerConfigEnabled = false;
153+
private $httpClientConfigEnabled = false;
152154

153155
/**
154156
* Responds to the app.config configuration parameter.
@@ -310,6 +312,10 @@ public function load(array $configs, ContainerBuilder $container)
310312
$container->removeDefinition('console.command.messenger_failed_messages_remove');
311313
}
312314

315+
if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) {
316+
$this->registerHttpClientConfiguration($config['http_client'], $container, $loader);
317+
}
318+
313319
$propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']);
314320
$this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
315321
$this->registerEsiConfiguration($config['esi'], $container, $loader);
@@ -340,10 +346,6 @@ public function load(array $configs, ContainerBuilder $container)
340346
$this->registerLockConfiguration($config['lock'], $container, $loader);
341347
}
342348

343-
if ($this->isConfigEnabled($container, $config['http_client'])) {
344-
$this->registerHttpClientConfiguration($config['http_client'], $container, $loader);
345-
}
346-
347349
if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) {
348350
$this->registerMailerConfiguration($config['mailer'], $container, $loader);
349351
}
@@ -558,6 +560,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
558560
$loader->load('mailer_debug.xml');
559561
}
560562

563+
if ($this->httpClientConfigEnabled) {
564+
$loader->load('http_client_debug.xml');
565+
}
566+
561567
$container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']);
562568
$container->setParameter('profiler_listener.only_master_requests', $config['only_master_requests']);
563569

@@ -1946,10 +1952,14 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder
19461952
if (null === $scope) {
19471953
$container->register($name, ScopingHttpClient::class)
19481954
->setFactory([ScopingHttpClient::class, 'forBaseUri'])
1949-
->setArguments([new Reference('http_client'), $scopeConfig['base_uri'], $scopeConfig]);
1955+
->setArguments([new Reference('http_client'), $scopeConfig['base_uri'], $scopeConfig])
1956+
->addTag('http_client.client')
1957+
;
19501958
} else {
19511959
$container->register($name, ScopingHttpClient::class)
1952-
->setArguments([new Reference('http_client'), [$scope => $scopeConfig], $scope]);
1960+
->setArguments([new Reference('http_client'), [$scope => $scopeConfig], $scope])
1961+
->addTag('http_client.client')
1962+
;
19531963
}
19541964

19551965
$container->registerAliasForArgument($name, HttpClientInterface::class);

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass;
3737
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
3838
use Symfony\Component\Form\DependencyInjection\FormPass;
39+
use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass;
3940
use Symfony\Component\HttpFoundation\Request;
4041
use Symfony\Component\HttpKernel\Bundle\Bundle;
4142
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
@@ -129,6 +130,7 @@ public function build(ContainerBuilder $container)
129130
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
130131
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
131132
$this->addCompilerPassIfExists($container, MessengerPass::class);
133+
$this->addCompilerPassIfExists($container, HttpClientPass::class);
132134
$this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class);
133135
$container->addCompilerPass(new RegisterReverseContainerPass(true));
134136
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);

src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<services>
88
<service id="http_client" class="Symfony\Contracts\HttpClient\HttpClientInterface">
99
<tag name="monolog.logger" channel="http_client" />
10+
<tag name="http_client.client" />
1011
<factory class="Symfony\Component\HttpClient\HttpClient" method="create" />
1112
<argument type="collection" /> <!-- default options -->
1213
<argument /> <!-- max host connections -->
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="data_collector.http_client" class="Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector">
9+
<tag name="data_collector" template="@WebProfiler/Collector/http_client.html.twig" id="http_client" priority="240" />
10+
</service>
11+
</services>
12+
</container>

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"symfony/polyfill-intl-icu": "~1.0",
4141
"symfony/form": "^4.3|^5.0",
4242
"symfony/expression-language": "^3.4|^4.0|^5.0",
43-
"symfony/http-client": "^4.3|^5.0",
43+
"symfony/http-client": "^4.4|^5.0",
4444
"symfony/lock": "^4.4|^5.0",
4545
"symfony/mailer": "^4.4|^5.0",
4646
"symfony/messenger": "^4.3|^5.0",
@@ -72,6 +72,7 @@
7272
"symfony/console": "<4.3",
7373
"symfony/dotenv": "<4.2",
7474
"symfony/dom-crawler": "<4.3",
75+
"symfony/http-client": "<4.4",
7576
"symfony/form": "<4.3",
7677
"symfony/lock": "<4.4",
7778
"symfony/mailer": "<4.4",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% block toolbar %}
4+
{% if collector.requestCount %}
5+
{% set icon %}
6+
{{ include('@WebProfiler/Icon/http-client.svg') }}
7+
{% set status_color = '' %}
8+
<span class="sf-toolbar-value">{{ collector.requestCount }}</span>
9+
{% endset %}
10+
11+
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
12+
{% endif %}
13+
{% endblock %}
14+
15+
{% block menu %}
16+
<span class="label {{ collector.requestCount == 0 ? 'disabled' }}">
17+
<span class="icon">{{ include('@WebProfiler/Icon/http-client.svg') }}</span>
18+
<strong>HTTP Client</strong>
19+
{% if collector.requestCount %}
20+
<span class="count">
21+
{{ collector.requestCount }}
22+
</span>
23+
{% endif %}
24+
</span>
25+
{% endblock %}
26+
27+
{% block panel %}
28+
<h2>HTTP Client</h2>
29+
{% if collector.requestCount == 0 %}
30+
<div class="empty">
31+
<p>No HTTP requests were made.</p>
32+
</div>
33+
{% else %}
34+
<div class="metrics">
35+
<div class="metric">
36+
<span class="value">{{ collector.requestCount }}</span>
37+
<span class="label">Total requests</span>
38+
</div>
39+
<div class="metric">
40+
<span class="value">{{ collector.errorCount }}</span>
41+
<span class="label">HTTP errors</span>
42+
</div>
43+
</div>
44+
<h2>Clients</h2>
45+
<div class="sf-tabs">
46+
{% for name, client in collector.clients %}
47+
<div class="tab {{ client.traces|length == 0 ? 'disabled' }}">
48+
<h3 class="tab-title">{{ name }} <span class="badge">{{ client.traces|length }}</span></h3>
49+
<div class="tab-content">
50+
{% if client.traces|length == 0 %}
51+
<div class="empty">
52+
<p>No requests were made with the "{{ name }}" service.</p>
53+
</div>
54+
{% else %}
55+
<h4>Requests</h4>
56+
{% for trace in client.traces %}
57+
<table>
58+
<thead>
59+
<tr>
60+
<th>
61+
<span class="label">{{ trace.method }}</span>
62+
</th>
63+
<th>
64+
{{ trace.url }}
65+
{% if trace.options is not empty %}
66+
{{ profiler_dump(trace.options, maxDepth=1) }}
67+
{% endif %}
68+
</th>
69+
</tr>
70+
</thead>
71+
<tbody>
72+
<tr>
73+
<th>
74+
{% if trace.http_code >= 500 %}
75+
{% set responseStatus = 'error' %}
76+
{% elseif trace.http_code >= 400 %}
77+
{% set responseStatus = 'warning' %}
78+
{% else %}
79+
{% set responseStatus = 'success' %}
80+
{% endif %}
81+
<span class="label status-{{ responseStatus }}">
82+
{{ trace.http_code }}
83+
</span>
84+
</th>
85+
<td>
86+
{{ profiler_dump(trace.info, maxDepth=1) }}
87+
</td>
88+
</tr>
89+
</tbody>
90+
</table>
91+
{% endfor %}
92+
{% endif %}
93+
</div>
94+
</div>
95+
{% endfor %}
96+
{% endif %}
97+
</div>
98+
{% endblock %}
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\DataCollector;
13+
14+
use Symfony\Component\HttpClient\TraceableHttpClient;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
18+
19+
/**
20+
* @author Jérémy Romey <jeremy@free-agent.fr>
21+
*/
22+
final class HttpClientDataCollector extends DataCollector
23+
{
24+
/**
25+
* @var TraceableHttpClient[]
26+
*/
27+
private $clients = [];
28+
29+
public function registerClient(string $name, TraceableHttpClient $client)
30+
{
31+
$this->clients[$name] = $client;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function collect(Request $request, Response $response, \Exception $exception = null)
38+
{
39+
$this->initData();
40+
41+
foreach ($this->clients as $name => $client) {
42+
[
43+
'request_count' => $requestCount,
44+
'error_count' => $errorCount,
45+
'traces' => $traces
46+
] = $this->collectOnClient($client);
47+
48+
$this->storeCollectedData($name, $traces, $errorCount);
49+
$this->incrementRequestCount($requestCount);
50+
$this->incrementErrorCount($errorCount);
51+
}
52+
}
53+
54+
public function getClients(): array
55+
{
56+
return $this->data['clients'] ?? [];
57+
}
58+
59+
public function getRequestCount(): int
60+
{
61+
return $this->data['request_count'] ?? 0;
62+
}
63+
64+
public function getErrorCount(): int
65+
{
66+
return $this->data['error_count'] ?? 0;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function reset()
73+
{
74+
$this->initData();
75+
foreach ($this->clients as $client) {
76+
$client->reset();
77+
}
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function getName(): string
84+
{
85+
return 'http_client';
86+
}
87+
88+
private function initData()
89+
{
90+
$this->data = [
91+
'clients' => [],
92+
'request_count' => 0,
93+
'error_count' => 0,
94+
];
95+
}
96+
97+
private function incrementRequestCount(int $requestCount)
98+
{
99+
$this->data['request_count'] += $requestCount;
100+
}
101+
102+
private function incrementErrorCount(int $errorCount)
103+
{
104+
$this->data['error_count'] += $errorCount;
105+
}
106+
107+
private function storeCollectedData(string $clientName, array $traces, int $errorCount)
108+
{
109+
$this->data['clients'][$clientName] = [
110+
'traces' => $traces,
111+
'error_count' => $errorCount,
112+
];
113+
}
114+
115+
private function collectOnClient(TraceableHttpClient $client): array
116+
{
117+
$traces = $client->getTracedRequests();
118+
$errorCount = 0;
119+
120+
foreach ($traces as $i => $trace) {
121+
if (400 <= ($trace['info']['http_code'] ?? 0)) {
122+
++$errorCount;
123+
}
124+
125+
$info = $trace['info'];
126+
$traces[$i]['http_code'] = $info['http_code'];
127+
128+
unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']);
129+
130+
if ($trace['method'] === $info['http_method']) {
131+
unset($info['http_method']);
132+
}
133+
134+
if ($trace['url'] === $info['url']) {
135+
unset($info['url']);
136+
}
137+
138+
foreach ($info as $k => $v) {
139+
if (!$v || (is_numeric($v) && 0 > $v)) {
140+
unset($info[$k]);
141+
}
142+
}
143+
144+
$traces[$i]['info'] = $this->cloneVar($info);
145+
$traces[$i]['options'] = $this->cloneVar($trace['options']);
146+
}
147+
148+
return [
149+
'request_count' => \count($traces),
150+
'error_count' => $errorCount,
151+
'traces' => $traces,
152+
];
153+
}
154+
}

0 commit comments

Comments
 (0)
0