diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php new file mode 100644 index 000000000..fcf7a5908 --- /dev/null +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -0,0 +1,158 @@ +telescopeConfig->isEnable('guzzle') + || ! TelescopeContext::getBatchId() + ) { + return $proceedingJoinPoint->process(); + } + + $options = $proceedingJoinPoint->arguments['keys']['options'] ?? []; + $guzzleConfig = (fn () => $this->config ?? [])->call($proceedingJoinPoint->getInstance()); + + // If the no_telescope_aspect option is set to true, we will not record the request. + if ( + ($options['no_telescope_aspect'] ?? null) === true + || ($guzzleConfig['no_telescope_aspect'] ?? null) === true + ) { + return $proceedingJoinPoint->process(); + } + + // Add or override the on_stats option to record the request duration. + $onStats = $options['on_stats'] ?? null; + $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats) { + try { + $request = $stats->getRequest(); + $response = $stats->getResponse(); + $data = [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders(), + 'body' => $this->getResponsePayload($response), + ]; + + Telescope::recordClientRequest(IncomingEntry::make([ + 'method' => $request->getMethod(), + 'uri' => $request->getUri()->__toString(), + 'headers' => $request->getHeaders(), + 'response_status' => $data['status'], + 'response_headers' => $data['headers'], + 'response_data' => $data, + 'duration' => $stats->getTransferTime() * 1000, + ])); + } catch (Throwable $e) { + // We will catch the exception to prevent the request from being interrupted. + } + + if (is_callable($onStats)) { + $onStats($stats); + } + }; + + return $proceedingJoinPoint->process(); + } + + public function getResponsePayload(ResponseInterface $response) + { + $stream = $response->getBody(); + try { + if ($stream->isSeekable()) { + $stream->rewind(); + } + + $content = $stream->getContents(); + + if (is_string($content)) { + if (! $this->contentWithinLimits($content)) { + return 'Purged By Hyperf Telescope'; + } + if ( + is_array(json_decode($content, true)) + && json_last_error() === JSON_ERROR_NONE + ) { + return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ + ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) + : 'Purged By Hyperf Telescope'; + } + if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { + return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ + } + if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { + return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; + } + } + + if (empty($content)) { + return 'Empty Response'; + } + } catch (Throwable $e) { + return 'Purged By Hyperf Telescope: ' . $e->getMessage(); + } finally { + if ($stream->isSeekable()) { + $stream->rewind(); + } + } + + return 'HTML Response'; + } + + protected function contentWithinLimits(string $content): bool + { + $limit = 64; + return mb_strlen($content) / 1000 <= $limit; + } + + /** + * Hide the given parameters. + */ + protected function hideParameters(array $data, array $hidden): array + { + foreach ($hidden as $parameter) { + if (Arr::get($data, $parameter)) { + Arr::set($data, $parameter, '********'); + } + } + + return $data; + } +} diff --git a/src/telescope/src/Aspect/HttpClientAspect.php b/src/telescope/src/Aspect/HttpClientAspect.php index 84605a977..356577a76 100644 --- a/src/telescope/src/Aspect/HttpClientAspect.php +++ b/src/telescope/src/Aspect/HttpClientAspect.php @@ -11,141 +11,9 @@ namespace FriendsOfHyperf\Telescope\Aspect; -use FriendsOfHyperf\Telescope\IncomingEntry; -use FriendsOfHyperf\Telescope\Telescope; -use FriendsOfHyperf\Telescope\TelescopeConfig; -use FriendsOfHyperf\Telescope\TelescopeContext; -use GuzzleHttp\Client; -use Hyperf\Collection\Arr; -use Hyperf\Di\Aop\AbstractAspect; -use Hyperf\Di\Aop\ProceedingJoinPoint; -use Hyperf\Stringable\Str; -use Psr\Http\Message\ResponseInterface; -use Throwable; - /** - * @property array $config + * @deprecated since v3.1, use `GuzzleHttpClientAspect` instead, will be removed in v3.2. */ -class HttpClientAspect extends AbstractAspect +class HttpClientAspect extends GuzzleHttpClientAspect { - public array $classes = [ - Client::class . '::request', - Client::class . '::requestAsync', - ]; - - public function __construct(protected TelescopeConfig $telescopeConfig) - { - } - - public function process(ProceedingJoinPoint $proceedingJoinPoint) - { - if ( - ! $this->telescopeConfig->isEnable('guzzle') - || ! TelescopeContext::getBatchId() - ) { - return $proceedingJoinPoint->process(); - } - - $startTime = microtime(true); - $instance = $proceedingJoinPoint->getInstance(); - $arguments = $proceedingJoinPoint->arguments; - $options = $arguments['keys']['options'] ?? []; - $guzzleConfig = (fn () => $this->config ?? [])->call($instance); - - if (($options['no_telescope_aspect'] ?? null) === true || ($guzzleConfig['no_telescope_aspect'] ?? null) === true) { - return $proceedingJoinPoint->process(); - } - - // Disable the aspect for the requestAsync method. - if ($proceedingJoinPoint->methodName == 'request') { - $proceedingJoinPoint->arguments['keys']['options']['no_telescope_aspect'] = true; - } - - $arguments = $proceedingJoinPoint->arguments; - $method = $arguments['keys']['method'] ?? 'GET'; - $uri = $arguments['keys']['uri'] ?? ''; - $headers = $options['headers'] ?? []; - $result = $proceedingJoinPoint->process(); - $response = []; - - if ($result instanceof ResponseInterface) { - $response['status'] = $result->getStatusCode(); - $response['reason'] = $result->getReasonPhrase(); - $response['headers'] = $result->getHeaders(); - $response['body'] = $this->getResponsePayload($result); - $result->getBody()->rewind(); - } - - Telescope::recordClientRequest(IncomingEntry::make([ - 'method' => $method, - 'uri' => $uri, - 'headers' => $headers, - 'response_status' => $response['status'] ?? 0, - 'response_headers' => $response['headers'] ?? '', - 'response' => $response, - 'duration' => floor((microtime(true) - $startTime) * 1000), - ])); - - return $result; - } - - public function getResponsePayload(ResponseInterface $response) - { - $stream = $response->getBody(); - try { - if ($stream->isSeekable()) { - $stream->rewind(); - } - - $content = $stream->getContents(); - } catch (Throwable $e) { - return 'Purged By Hyperf Telescope: ' . $e->getMessage(); - } - - if (is_string($content)) { - if (! $this->contentWithinLimits($content)) { - return 'Purged By Hyperf Telescope'; - } - if ( - is_array(json_decode($content, true)) - && json_last_error() === JSON_ERROR_NONE - ) { - return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ - ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) - : 'Purged By Hyperf Telescope'; - } - if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { - return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ - } - if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { - return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; - } - } - - if (empty($content)) { - return 'Empty Response'; - } - - return 'HTML Response'; - } - - protected function contentWithinLimits(string $content): bool - { - $limit = 64; - return mb_strlen($content) / 1000 <= $limit; - } - - /** - * Hide the given parameters. - */ - protected function hideParameters(array $data, array $hidden): array - { - foreach ($hidden as $parameter) { - if (Arr::get($data, $parameter)) { - Arr::set($data, $parameter, '********'); - } - } - - return $data; - } } diff --git a/src/telescope/src/ConfigProvider.php b/src/telescope/src/ConfigProvider.php index 50b9f98ee..8d908b7c5 100644 --- a/src/telescope/src/ConfigProvider.php +++ b/src/telescope/src/ConfigProvider.php @@ -19,16 +19,16 @@ public function __invoke(): array return [ 'aspects' => [ - Aspect\CoroutineAspect::class, Aspect\CacheAspect::class, + Aspect\CoroutineAspect::class, Aspect\EventAspect::class, Aspect\GrpcClientAspect::class, - Aspect\HttpClientAspect::class, + Aspect\GrpcCoreMiddlewareAspect::class, + Aspect\GuzzleHttpClientAspect::class, Aspect\LogAspect::class, Aspect\RedisAspect::class, - Aspect\RpcAspect::class, Aspect\RequestDispatcherAspect::class, - Aspect\GrpcCoreMiddlewareAspect::class, + Aspect\RpcAspect::class, ], 'commands' => [ Command\ClearCommand::class,