Describe your environment
PHP | 8.3+
open-telemetry/opentelemetry-auto-guzzle | 1.2.1 (also present on main)
open-telemetry/sdk | 1.x
ext-opentelemetry | 1.x
guzzlehttp/guzzle | 7.9 / 7.10
Steps to reproduce
GuzzleHttp\Client::transfer() can throw synchronously before it returns a
promise, because Client::applyOptions() runs outside transfer()'s
try/catch and validates request options. Any option that fails validation
triggers this path. Minimal reproducer (a numerically-indexed headers array
makes applyOptions() throw InvalidArgumentException):
$client = new \GuzzleHttp\Client();
$client->get('https://example.com/', ['headers' => ['invalid']]);
(Real-world triggers: invalid cookies, conflicting form_params/multipart,
malformed headers, etc.)
What is the expected behavior?
The client span is ended/recorded for the failed call, and no PHP warning is
emitted.
What is the actual behavior?
The auto-guzzle transfer post hook is declared with a non-nullable return
value parameter:
post: static function (Client $client, array $params, PromiseInterface $promise, ?Throwable $exception): void
When transfer() throws, the extension invokes the post hook with a null
return value. The runtime signature check fails, the post hook is skipped,
the span leaks (never ended/detached), and an E_CORE_WARNING is emitted:
Core Warning: GuzzleHttp\Client::transfer(): OpenTelemetry: post hook invalid
signature, class=GuzzleHttp\Client function=transfer in Unknown on line 0
Error trackers such as Sentry capture this as a fatal error.
Additional context
This is the same "post hook invalid / span leak when the instrumented function
throws" class that has already been fixed for other instrumentations:
auto-curl — #1531 → fix open-telemetry/opentelemetry-php-contrib#335
Symfony Messenger — open-telemetry/opentelemetry-php-contrib#267
Fix: make the promise parameter nullable and end the span on the synchronous
throw path.
PR: open-telemetry/opentelemetry-php-contrib#629
Describe your environment
PHP | 8.3+
open-telemetry/opentelemetry-auto-guzzle | 1.2.1 (also present on main)
open-telemetry/sdk | 1.x
ext-opentelemetry | 1.x
guzzlehttp/guzzle | 7.9 / 7.10
Steps to reproduce
GuzzleHttp\Client::transfer()can throw synchronously before it returns apromise, because
Client::applyOptions()runs outsidetransfer()'stry/catch and validates request options. Any option that fails validation
triggers this path. Minimal reproducer (a numerically-indexed
headersarraymakes
applyOptions()throwInvalidArgumentException):PR: open-telemetry/opentelemetry-php-contrib#629