HttpClientDataCollector.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpClient\DataCollector;
  11. use Symfony\Component\HttpClient\TraceableHttpClient;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\DataCollector\DataCollector;
  15. use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
  16. use Symfony\Component\VarDumper\Caster\ImgStub;
  17. /**
  18. * @author Jérémy Romey <jeremy@free-agent.fr>
  19. */
  20. final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface
  21. {
  22. /**
  23. * @var TraceableHttpClient[]
  24. */
  25. private $clients = [];
  26. public function registerClient(string $name, TraceableHttpClient $client)
  27. {
  28. $this->clients[$name] = $client;
  29. }
  30. /**
  31. * {@inheritdoc}
  32. */
  33. public function collect(Request $request, Response $response, \Throwable $exception = null)
  34. {
  35. $this->reset();
  36. foreach ($this->clients as $name => $client) {
  37. [$errorCount, $traces] = $this->collectOnClient($client);
  38. $this->data['clients'][$name] = [
  39. 'traces' => $traces,
  40. 'error_count' => $errorCount,
  41. ];
  42. $this->data['request_count'] += \count($traces);
  43. $this->data['error_count'] += $errorCount;
  44. }
  45. }
  46. public function lateCollect()
  47. {
  48. foreach ($this->clients as $client) {
  49. $client->reset();
  50. }
  51. }
  52. public function getClients(): array
  53. {
  54. return $this->data['clients'] ?? [];
  55. }
  56. public function getRequestCount(): int
  57. {
  58. return $this->data['request_count'] ?? 0;
  59. }
  60. public function getErrorCount(): int
  61. {
  62. return $this->data['error_count'] ?? 0;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function getName(): string
  68. {
  69. return 'http_client';
  70. }
  71. public function reset()
  72. {
  73. $this->data = [
  74. 'clients' => [],
  75. 'request_count' => 0,
  76. 'error_count' => 0,
  77. ];
  78. }
  79. private function collectOnClient(TraceableHttpClient $client): array
  80. {
  81. $traces = $client->getTracedRequests();
  82. $errorCount = 0;
  83. $baseInfo = [
  84. 'response_headers' => 1,
  85. 'retry_count' => 1,
  86. 'redirect_count' => 1,
  87. 'redirect_url' => 1,
  88. 'user_data' => 1,
  89. 'error' => 1,
  90. 'url' => 1,
  91. ];
  92. foreach ($traces as $i => $trace) {
  93. if (400 <= ($trace['info']['http_code'] ?? 0)) {
  94. ++$errorCount;
  95. }
  96. $info = $trace['info'];
  97. $traces[$i]['http_code'] = $info['http_code'] ?? 0;
  98. unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']);
  99. if (($info['http_method'] ?? null) === $trace['method']) {
  100. unset($info['http_method']);
  101. }
  102. if (($info['url'] ?? null) === $trace['url']) {
  103. unset($info['url']);
  104. }
  105. foreach ($info as $k => $v) {
  106. if (!$v || (is_numeric($v) && 0 > $v)) {
  107. unset($info[$k]);
  108. }
  109. }
  110. if (\is_string($content = $trace['content'])) {
  111. $contentType = 'application/octet-stream';
  112. foreach ($info['response_headers'] ?? [] as $h) {
  113. if (0 === stripos($h, 'content-type: ')) {
  114. $contentType = substr($h, \strlen('content-type: '));
  115. break;
  116. }
  117. }
  118. if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) {
  119. $content = new ImgStub($content, $contentType, '');
  120. } else {
  121. $content = [$content];
  122. }
  123. $content = ['response_content' => $content];
  124. } elseif (\is_array($content)) {
  125. $content = ['response_json' => $content];
  126. } else {
  127. $content = [];
  128. }
  129. if (isset($info['retry_count'])) {
  130. $content['retries'] = $info['previous_info'];
  131. unset($info['previous_info']);
  132. }
  133. $debugInfo = array_diff_key($info, $baseInfo);
  134. $info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + $content;
  135. unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient
  136. $traces[$i]['info'] = $this->cloneVar($info);
  137. $traces[$i]['options'] = $this->cloneVar($trace['options']);
  138. }
  139. return [$errorCount, $traces];
  140. }
  141. }