diff --git a/src/Instrumentation/Guzzle/src/GuzzleInstrumentation.php b/src/Instrumentation/Guzzle/src/GuzzleInstrumentation.php index ace1c622b..fd70c503d 100644 --- a/src/Instrumentation/Guzzle/src/GuzzleInstrumentation.php +++ b/src/Instrumentation/Guzzle/src/GuzzleInstrumentation.php @@ -6,6 +6,7 @@ use function get_cfg_var; use GuzzleHttp\Client; +use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Promise\PromiseInterface; use OpenTelemetry\API\Globals; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; @@ -120,6 +121,12 @@ public static function register(): void return $response; }, onRejected: function (\Throwable $t) use ($span) { + if ($t instanceof BadResponseException && $t->hasResponse()) { + $response = $t->getResponse(); + $span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode()); + $span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion()); + $span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->getBody()->getSize()); + } $span->recordException($t); $span->setStatus(StatusCode::STATUS_ERROR, $t->getMessage()); $span->end(); diff --git a/src/Instrumentation/Guzzle/tests/Integration/GuzzleInstrumentationTest.php b/src/Instrumentation/Guzzle/tests/Integration/GuzzleInstrumentationTest.php index 64fdc8ccc..ca951949e 100644 --- a/src/Instrumentation/Guzzle/tests/Integration/GuzzleInstrumentationTest.php +++ b/src/Instrumentation/Guzzle/tests/Integration/GuzzleInstrumentationTest.php @@ -6,6 +6,7 @@ use ArrayObject; use GuzzleHttp\Client; +use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Promise\PromiseInterface; @@ -165,4 +166,47 @@ public function test_headers_propagation(): void $this->client->get('/'); } + + /** + * @dataProvider exceptionProvider + */ + public function test_exceptions_enabled_sets_response_attributes($response, ?int $expected = null): void + { + $client = new Client([ + 'handler' => $this->handlerStack, + 'base_uri' => 'https://example.com/', + 'http_errors' => true, + 'exceptions' => true, + ]); + $this->mock->append($response); + $this->assertCount(0, $this->storage); + + try { + $client->send(new Request('GET', 'https://example.com/error')); + } catch (\Exception $e) { + // Expected exception + } + $this->assertCount(1, $this->storage); + $span = $this->storage->offsetGet(0); + $attributes = $span->getAttributes()->toArray(); + if ($expected) { + $this->assertSame($expected, $attributes[TraceAttributes::HTTP_RESPONSE_STATUS_CODE]); + $this->assertGreaterThan(0, $attributes[TraceAttributes::HTTP_RESPONSE_BODY_SIZE]); + $this->assertArrayHasKey(TraceAttributes::NETWORK_PROTOCOL_VERSION, $attributes); + } else { + $this->assertArrayNotHasKey(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $attributes); + } + } + + public static function exceptionProvider(): array + { + return [ + '400 Bad Request' => [new Response(400, [], 'Bad Request'), 400], + '404 Not Found' => [new Response(404, [], 'Not Found'), 404], + '500 Internal Server Error' => [new Response(500, [], 'Internal Server Error'), 500], + '503 Service Unavailable' => [new Response(503, [], 'Service Unavailable'), 503], + 'network connection error' => [new ConnectException('network error', new Request('GET', 'https://example.com/error'))], + 'runtime exception' => [new \RuntimeException('runtime error')], + ]; + } }