From 02608fca0e4d615c22a71cf83245a6e595478333 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:51:18 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20GuzzleHttpClie?= =?UTF-8?q?ntAspect=20=E4=BB=A5=E6=94=AF=E6=8C=81=20transfer=20=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=B9=B6=E5=A2=9E=E5=BC=BA=E8=AF=B7=E6=B1=82=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/sentry/src/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Aspect/GuzzleHttpClientAspect.php index 0ba8a3e08..0d2433bba 100644 --- a/src/sentry/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Aspect/GuzzleHttpClientAspect.php @@ -14,9 +14,9 @@ use FriendsOfHyperf\Sentry\Integration; use FriendsOfHyperf\Sentry\Switcher; use GuzzleHttp\Client; +use GuzzleHttp\TransferStats; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; -use Psr\Http\Message\ResponseInterface; use Sentry\Breadcrumb; /** @@ -26,8 +26,7 @@ class GuzzleHttpClientAspect extends AbstractAspect { public array $classes = [ - Client::class . '::request', - Client::class . '::requestAsync', + Client::class . '::transfer', ]; public function __construct(protected Switcher $switcher) @@ -36,47 +35,48 @@ public function __construct(protected Switcher $switcher) public function process(ProceedingJoinPoint $proceedingJoinPoint) { + // If the guzzle aspect is disabled, we will not record the request. if (! $this->switcher->isBreadcrumbEnable('guzzle')) { return $proceedingJoinPoint->process(); } - $startTime = microtime(true); - $instance = $proceedingJoinPoint->getInstance(); - $arguments = $proceedingJoinPoint->arguments; - $options = $arguments['keys']['options'] ?? []; - $guzzleConfig = (fn () => $this->config ?? [])->call($instance); + $options = $proceedingJoinPoint->arguments['keys']['options'] ?? []; + $guzzleConfig = (fn () => $this->config ?? [])->call($proceedingJoinPoint->getInstance()); + // If the no_sentry_aspect option is set to true, we will not record the request. if (($options['no_sentry_aspect'] ?? null) === true || ($guzzleConfig['no_sentry_aspect'] ?? null) === true) { return $proceedingJoinPoint->process(); } - // Disable the aspect for the requestAsync method. - if ($proceedingJoinPoint->methodName == 'request') { - $proceedingJoinPoint->arguments['keys']['options']['no_sentry_aspect'] = true; - } + $onStats = $options['on_stats'] ?? null; - $uri = (string) ($arguments['keys']['uri'] ?? ''); - $data['config'] = $guzzleConfig; - $data['request']['method'] = $arguments['keys']['method'] ?? 'GET'; - $data['request']['options'] = $arguments['keys']['options'] ?? []; + $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats, $guzzleConfig, $options) { + $request = $stats->getRequest(); + $response = $stats->getResponse(); - $result = $proceedingJoinPoint->process(); + $uri = $request->getUri()->__toString(); + $data = []; + $data['config'] = $guzzleConfig; + $data['request']['method'] = $request->getMethod(); + $data['request']['options'] = $options; + $data['response']['status'] = $response?->getStatusCode(); + $data['response']['reason'] = $response?->getReasonPhrase(); + $data['response']['headers'] = $response?->getHeaders(); + $data['duration'] = $stats->getTransferTime() * 1000; - if ($result instanceof ResponseInterface) { - $data['response']['status'] = $result->getStatusCode(); - $data['response']['reason'] = $result->getReasonPhrase(); - $data['response']['headers'] = $result->getHeaders(); - } - $data['timeMs'] = (microtime(true) - $startTime) * 1000; + Integration::addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_DEFAULT, + 'guzzle', + $uri, + $data + )); - Integration::addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_INFO, - Breadcrumb::TYPE_DEFAULT, - 'guzzle', - $uri, - $data - )); + if (is_callable($onStats)) { + ($onStats)($stats); + } + }; - return $result; + return $proceedingJoinPoint->process(); } } From eb9add777421b9eb705b43857e7803175c11295a Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:18:26 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20GuzzleHttpClie?= =?UTF-8?q?ntAspect=20=E4=BB=A5=E6=94=AF=E6=8C=81=20transfer=20=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=B9=B6=E5=A2=9E=E5=BC=BA=E8=AF=B7=E6=B1=82=E8=BF=BD?= =?UTF-8?q?=E8=B8=AA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 6 +- .../Tracing/Aspect/GuzzleHttpClientAspect.php | 105 ++++++++---------- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/src/sentry/src/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Aspect/GuzzleHttpClientAspect.php index 0d2433bba..ae33a54b6 100644 --- a/src/sentry/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Aspect/GuzzleHttpClientAspect.php @@ -41,7 +41,11 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } $options = $proceedingJoinPoint->arguments['keys']['options'] ?? []; - $guzzleConfig = (fn () => $this->config ?? [])->call($proceedingJoinPoint->getInstance()); + $guzzleConfig = (fn () => match (true) { + method_exists($this, 'getConfig') => $this->getConfig(), // @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0. + property_exists($this, 'config') => $this->config, + default => [], + })->call($proceedingJoinPoint->getInstance()); // If the no_sentry_aspect option is set to true, we will not record the request. if (($options['no_sentry_aspect'] ?? null) === true || ($guzzleConfig['no_sentry_aspect'] ?? null) === true) { diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index 3bcf0d99e..61881ebc3 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -14,15 +14,14 @@ use FriendsOfHyperf\Sentry\Switcher; use FriendsOfHyperf\Sentry\Tracing\SpanStarter; use GuzzleHttp\Client; +use GuzzleHttp\TransferStats; use Hyperf\Context\Context; use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use Psr\Container\ContainerInterface; -use Psr\Http\Message\ResponseInterface; use Sentry\SentrySdk; use Sentry\Tracing\SpanStatus; -use Throwable; /** * @method array getConfig() @@ -33,8 +32,7 @@ class GuzzleHttpClientAspect extends AbstractAspect use SpanStarter; public array $classes = [ - Client::class . '::request', - Client::class . '::requestAsync', + Client::class . '::transfer', ]; public function __construct( @@ -52,15 +50,17 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $instance = $proceedingJoinPoint->getInstance(); $arguments = $proceedingJoinPoint->arguments['keys']; + $uri = (string) ($arguments['uri'] ?? '/'); + $method = $arguments['method'] ?? 'GET'; $options = $arguments['options'] ?? []; $guzzleConfig = (fn () => match (true) { method_exists($this, 'getConfig') => $this->getConfig(), // @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0. property_exists($this, 'config') => $this->config, default => [], - })->call($instance); + })->call($proceedingJoinPoint->getInstance()); + // If the no_sentry_tracing option is set to true, we will not record the request. if ( ($options['no_sentry_tracing'] ?? null) === true || ($guzzleConfig['no_sentry_tracing'] ?? null) === true @@ -68,28 +68,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - // Disable the aspect for the requestAsync method. - if ($proceedingJoinPoint->methodName == 'request') { - $proceedingJoinPoint->arguments['keys']['options']['no_sentry_tracing'] = true; - } - - $uri = (string) ($arguments['uri'] ?? '/'); - $method = $arguments['method'] ?? 'GET'; - $fullUri = new \GuzzleHttp\Psr7\Uri($uri); - - $data = [ - // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http - 'http.query' => $fullUri->getQuery(), - 'http.fragment' => $fullUri->getFragment(), - 'http.request.method' => $method, - 'http.request.body.size' => strlen($arguments['options']['body'] ?? ''), - // Other - 'coroutine.id' => Coroutine::id(), - 'http.system' => 'guzzle', - 'http.guzzle.config' => $guzzleConfig, - 'http.guzzle.options' => $arguments['options'] ?? [], - ]; - + // Inject trace context $parent = SentrySdk::getCurrentHub()->getSpan(); $options['headers'] = array_replace($options['headers'] ?? [], [ 'sentry-trace' => $parent->toTraceparent(), @@ -98,44 +77,56 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) ]); $proceedingJoinPoint->arguments['keys']['options']['headers'] = $options['headers']; + // Start span $span = $this->startSpan('http.client', $method . ' ' . (string) $uri); - try { - $result = $proceedingJoinPoint->process(); + if (! $span) { + return $proceedingJoinPoint->process(); + } - if (! $span) { - return $result; + $onStats = $options['on_stats'] ?? null; + + $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($arguments, $guzzleConfig, $onStats, $span) { + $request = $stats->getRequest(); + $response = $stats->getResponse(); + $uri = $request->getUri(); + $method = $request->getMethod(); + $statusCode = $response->getStatusCode(); + $data = [ + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http + 'http.query' => $uri->getQuery(), + 'http.fragment' => $uri->getFragment(), + 'http.request.method' => $method, + 'http.request.body.size' => strlen($arguments['options']['body'] ?? ''), + // Other + 'coroutine.id' => Coroutine::id(), + 'http.system' => 'guzzle', + 'http.guzzle.config' => $guzzleConfig, + 'http.guzzle.options' => $arguments['options'] ?? [], + 'duration' => $stats->getTransferTime() * 1000, // in milliseconds + 'response.status' => $statusCode, + 'response.reason' => $response->getReasonPhrase(), + 'response.headers' => $response->getHeaders(), + ]; + + if ($this->switcher->isTracingExtraTagEnable('response.body')) { + $data['response.body'] = $response->getBody()->getContents(); + $response->getBody()->isSeekable() && $response->getBody()->rewind(); } - if ($result instanceof ResponseInterface) { - $data += [ - 'response.status' => $result->getStatusCode(), - 'response.reason' => $result->getReasonPhrase(), - 'response.headers' => $result->getHeaders(), - ]; - if ($this->switcher->isTracingExtraTagEnable('response.body')) { - $data['response.body'] = $result->getBody()->getContents(); - $result->getBody()->rewind(); - } - } - } catch (Throwable $exception) { - $span->setStatus(SpanStatus::internalError()); - $span->setTags([ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; + if ($statusCode >= 400 && $statusCode < 600) { + $span->setStatus(SpanStatus::internalError()); + $span->setTags(['error' => true]); } - throw $exception; - } finally { $span->setData($data); $span->finish(); - } - return $result; + if (is_callable($onStats)) { + ($onStats)($stats); + } + }; + + return $proceedingJoinPoint->process(); } } From b212034cb9501d1d08c572ca119b0df26497153d Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:26:07 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=88=96?= =?UTF-8?q?=E8=A6=86=E7=9B=96=20on=5Fstats=20=E9=80=89=E9=A1=B9=E4=BB=A5?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=AF=B7=E6=B1=82=E6=8C=81=E7=BB=AD=E6=97=B6?= =?UTF-8?q?=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index 61881ebc3..e39ff0dd2 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -86,6 +86,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $onStats = $options['on_stats'] ?? null; + // Add or override the on_stats option to record the request duration. $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($arguments, $guzzleConfig, $onStats, $span) { $request = $stats->getRequest(); $response = $stats->getResponse(); From bf77bee222b636b27384b726ea87d2645b12899f Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:36:43 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20GuzzleHttpClie?= =?UTF-8?q?ntAspect=20=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BC=82=E5=B8=B8=E4=BF=A1=E6=81=AF=E5=92=8C?= =?UTF-8?q?=E5=A0=86=E6=A0=88=E8=B7=9F=E8=B8=AA=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tracing/Aspect/GuzzleHttpClientAspect.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index e39ff0dd2..e77dcde74 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -22,6 +22,7 @@ use Psr\Container\ContainerInterface; use Sentry\SentrySdk; use Sentry\Tracing\SpanStatus; +use Throwable; /** * @method array getConfig() @@ -115,9 +116,23 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $response->getBody()->isSeekable() && $response->getBody()->rewind(); } - if ($statusCode >= 400 && $statusCode < 600) { + if (($exception = $stats->getHandlerErrorData()) instanceof Throwable) { $span->setStatus(SpanStatus::internalError()); - $span->setTags(['error' => true]); + $span->setTags([ + 'error' => true, + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $data['exception.stack_trace'] = (string) $exception; + } + } elseif ($statusCode >= 400 && $statusCode < 600) { + $span->setStatus(SpanStatus::internalError()); + $span->setTags([ + 'error' => true, + 'response.reason' => $response->getReasonPhrase(), + ]); } $span->setData($data); From 640af58f624794c0b8b3108c79b01f1a7d9a28ac Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:49:41 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20GuzzleHttpClie?= =?UTF-8?q?ntAspect=20=E4=BB=A5=E4=BD=BF=E7=94=A8=20RequestInterface=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AF=B7=E6=B1=82=E6=96=B9=E6=B3=95=E5=92=8C?= =?UTF-8?q?=20URI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index e77dcde74..b5022f3dd 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -20,6 +20,7 @@ use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use Psr\Container\ContainerInterface; +use Psr\Http\Message\RequestInterface; use Sentry\SentrySdk; use Sentry\Tracing\SpanStatus; use Throwable; @@ -52,8 +53,8 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } $arguments = $proceedingJoinPoint->arguments['keys']; - $uri = (string) ($arguments['uri'] ?? '/'); - $method = $arguments['method'] ?? 'GET'; + /** @var RequestInterface $request */ + $request = $arguments['request']; $options = $arguments['options'] ?? []; $guzzleConfig = (fn () => match (true) { method_exists($this, 'getConfig') => $this->getConfig(), // @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0. @@ -79,7 +80,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $proceedingJoinPoint->arguments['keys']['options']['headers'] = $options['headers']; // Start span - $span = $this->startSpan('http.client', $method . ' ' . (string) $uri); + $span = $this->startSpan('http.client', $request->getMethod() . ' ' . (string) $request->getUri()); if (! $span) { return $proceedingJoinPoint->process();