From 382525e3ae660737de126d314fc616a1544282e1 Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Wed, 30 Oct 2024 22:46:47 +0100 Subject: [PATCH 1/5] Curl auto instrumentation distributed tracing headers propagation, request and response headers capturing (#1420) --- src/Instrumentation/Curl/README.md | 37 +++ .../Curl/src/CurlHandleMetadata.php | 139 ++++++++++ .../Curl/src/CurlInstrumentation.php | 240 +++++++++++++----- .../Curl/src/HeadersPropagator.php | 27 ++ .../Integration/CurlInstrumentationTest.php | 79 +++++- .../CurlMultiInstrumentationTest.php | 127 +++++++++ 6 files changed, 583 insertions(+), 66 deletions(-) create mode 100644 src/Instrumentation/Curl/src/CurlHandleMetadata.php create mode 100644 src/Instrumentation/Curl/src/HeadersPropagator.php diff --git a/src/Instrumentation/Curl/README.md b/src/Instrumentation/Curl/README.md index 2aa32435..1b7ebc0e 100644 --- a/src/Instrumentation/Curl/README.md +++ b/src/Instrumentation/Curl/README.md @@ -14,14 +14,51 @@ install and configure the extension and SDK. ## Overview Auto-instrumentation hooks are registered via composer, and client kind spans will automatically be created when calling `curl_exec` or `curl_multi_exec` functions. +Additionally, distributed tracing is supported by setting the `traceparent` header. ## Limitations The curl_multi instrumentation is not resilient to shortcomings in the application and requires proper implementation. If the application does not call the curl_multi_info_read function, the instrumentation will be unable to measure the execution time for individual requests-time will be aggregated for all transfers. Similarly, error detection will be impacted, as the error code information will be missing in this case. In case of encountered issues, it is recommended to review the application code and adjust it to match example #1 provided in [curl_multi_exec documentation](https://www.php.net/manual/en/function.curl-multi-exec.php). +To ensure the stability of the monitored application, capturing request headers sent to the server works only if the application does not use the `CURLOPT_VERBOSE` option. + ## Configuration +### Disabling curl instrumentation + The extension can be disabled via [runtime configuration](https://opentelemetry.io/docs/instrumentation/php/sdk/#configuration): ```shell OTEL_PHP_DISABLED_INSTRUMENTATIONS=curl ``` + +### Request and response headers captuing + +Curl auto-instrumentation enables capturing headers from both requests and responses. This feature is disabled by default and be enabled through environment variables or array directives in the `php.ini` configuration file. + +To enable response header capture from the server, specify the required headers as shown in the example below. In this case, the "Content-Type" and "Server" headers will be captured. These options values are case-insensitive: + +#### Environment variables configuration + +```bash +OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type,server +OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host,accept +``` + +#### php.ini configuration + +```ini +OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type,server +; or +otel.instrumentation.http.response_headers[]=content-type +otel.instrumentation.http.response_headers[]=server +``` + + +Similarly, to capture headers sent in a request to the server, use the following configuration: + +```ini +OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host,accept +; or +otel.instrumentation.http.request_headers[]=host +otel.instrumentation.http.request_headers[]=accept +``` \ No newline at end of file diff --git a/src/Instrumentation/Curl/src/CurlHandleMetadata.php b/src/Instrumentation/Curl/src/CurlHandleMetadata.php new file mode 100644 index 00000000..a11dcc6a --- /dev/null +++ b/src/Instrumentation/Curl/src/CurlHandleMetadata.php @@ -0,0 +1,139 @@ +attributes = [TraceAttributes::HTTP_REQUEST_METHOD => 'GET']; + $this->headers = []; + $headersToPropagate = []; + } + + public function isVerboseEnabled(): bool + { + return $this->verboseEnabled; + } + public function getAttributes(): array + { + return $this->attributes; + } + + public function setAttribute(string $key, mixed $value) + { + $this->attributes[$key] = $value; + } + + public function setHeaderToPropagate(string $key, $value): CurlHandleMetadata + { + $this->headersToPropagate[] = $key . ': ' . $value; + + return $this; + } + + public function getRequestHeadersToSend(): ?array + { + if (count($this->headersToPropagate) == 0) { + return null; + } + $headers = array_merge($this->headersToPropagate, $this->headers); + $this->headersToPropagate = []; + + return $headers; + } + + public function getCapturedResponseHeaders(): array + { + return $this->responseHeaders; + } + + public function getResponseHeaderCaptureFunction() + { + $this->responseHeaders = []; + $func = function (CurlHandle $handle, string $headerLine): int { + + $header = trim($headerLine, "\n\r"); + if (strlen($header) > 0) { + if (strpos($header, ': ') !== false) { + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + list($key, $value) = explode(': ', $header, 2); + $this->responseHeaders[strtolower($key)] = $value; + } + } + + if ($this->originalHeaderFunction) { + return call_user_func($this->originalHeaderFunction, $handle, $headerLine); + } + + return strlen($headerLine); + }; + + return \Closure::bind($func, $this, self::class); + } + + public function updateFromCurlOption(int $option, mixed $value) + { + switch ($option) { + case CURLOPT_CUSTOMREQUEST: + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $value); + + break; + case CURLOPT_HTTPGET: + // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L841 + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, 'GET'); + + break; + case CURLOPT_POST: + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'POST' : 'GET')); + + break; + case CURLOPT_POSTFIELDS: + // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L269 + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, 'POST'); + + break; + case CURLOPT_PUT: + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'PUT' : 'GET')); + + break; + case CURLOPT_NOBODY: + // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L269 + $this->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'HEAD' : 'GET')); + + break; + case CURLOPT_URL: + // $this->setAttribute(TraceAttributes::URL_FULL, self::redactUrlString($value)); + break; + case CURLOPT_USERAGENT: + $this->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $value); + + break; + case CURLOPT_HTTPHEADER: + $this->headers = $value; + + break; + case CURLOPT_HEADERFUNCTION: + $this->originalHeaderFunction = $value; + // no break + case CURLOPT_VERBOSE: + $this->verboseEnabled = false; + } + } +} diff --git a/src/Instrumentation/Curl/src/CurlInstrumentation.php b/src/Instrumentation/Curl/src/CurlInstrumentation.php index c3ef4522..cf8f6d56 100644 --- a/src/Instrumentation/Curl/src/CurlInstrumentation.php +++ b/src/Instrumentation/Curl/src/CurlInstrumentation.php @@ -6,6 +6,7 @@ use CurlHandle; use CurlMultiHandle; +use OpenTelemetry\API\Globals; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; use OpenTelemetry\API\Trace\Span; use OpenTelemetry\API\Trace\SpanInterface; @@ -13,6 +14,7 @@ use OpenTelemetry\API\Trace\StatusCode; use OpenTelemetry\Context\Context; use function OpenTelemetry\Instrumentation\hook; +use OpenTelemetry\SDK\Common\Configuration\Configuration; use OpenTelemetry\SemConv\TraceAttributes; use WeakMap; use WeakReference; @@ -23,7 +25,7 @@ class CurlInstrumentation public static function register(): void { - /** @var WeakMap */ + /** @var WeakMap */ $curlHandleToAttributes = new WeakMap(); /** @var WeakMap > @@ -38,6 +40,9 @@ public static function register(): void */ $curlMultiToHandle = new WeakMap(); + /** @var bool */ + $curlSetOptInstrumentationSuppressed = false; + $instrumentation = new CachedInstrumentation( 'io.opentelemetry.contrib.php.curl', null, @@ -50,9 +55,9 @@ public static function register(): void pre: null, post: static function ($obj, array $params, mixed $retVal) use ($curlHandleToAttributes) { if ($retVal instanceof CurlHandle) { - $curlHandleToAttributes[$retVal] = [TraceAttributes::HTTP_REQUEST_METHOD => 'GET']; - if (($handle = $params[0] ?? null) !== null) { - $curlHandleToAttributes[$retVal][TraceAttributes::URL_FULL] = self::redactUrlString($handle); + $curlHandleToAttributes[$retVal] = new CurlHandleMetadata(); + if (($fullUrl = $params[0] ?? null) !== null) { + $curlHandleToAttributes[$retVal]->setAttribute(TraceAttributes::URL_FULL, self::redactUrlString($fullUrl)); } } } @@ -62,15 +67,12 @@ public static function register(): void null, 'curl_setopt', pre: null, - post: static function ($obj, array $params, mixed $retVal) use ($curlHandleToAttributes) { - if ($retVal != true) { + post: static function ($obj, array $params, mixed $retVal) use ($curlHandleToAttributes, &$curlSetOptInstrumentationSuppressed) { + if ($retVal != true || $curlSetOptInstrumentationSuppressed) { return; } - $attribute = self::getAttributeFromCurlOption($params[1], $params[2]); - if ($attribute) { - $curlHandleToAttributes[$params[0]][$attribute[0]] = $attribute[1]; - } + $curlHandleToAttributes[$params[0]]->updateFromCurlOption($params[1], $params[2]); } ); @@ -84,10 +86,7 @@ public static function register(): void } foreach ($params[1] as $option => $value) { - $attribute = self::getAttributeFromCurlOption($option, $value); - if ($attribute) { - $curlHandleToAttributes[$params[0]][$attribute[0]] = $attribute[1]; - } + $curlHandleToAttributes[$params[0]]->updateFromCurlOption($option, $value); } } ); @@ -119,7 +118,7 @@ public static function register(): void 'curl_reset', pre: static function ($obj, array $params) use ($curlHandleToAttributes) { if (count($params) > 0 && $params[0] instanceof CurlHandle) { - $curlHandleToAttributes[$params[0]] = [TraceAttributes::HTTP_REQUEST_METHOD => 'GET']; + $curlHandleToAttributes[$params[0]] = new CurlHandleMetadata(); } }, post: null @@ -128,26 +127,56 @@ public static function register(): void hook( null, 'curl_exec', - pre: static function ($obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno) use ($instrumentation, $curlHandleToAttributes) { + pre: static function ($obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno) use ($instrumentation, $curlHandleToAttributes, &$curlSetOptInstrumentationSuppressed) { if (!($params[0] instanceof CurlHandle)) { return; } - $spanName = $curlHandleToAttributes[$params[0]][TraceAttributes::HTTP_REQUEST_METHOD] ?? 'curl_exec'; + $spanName = $curlHandleToAttributes[$params[0]]->getAttributes()[TraceAttributes::HTTP_REQUEST_METHOD] ?? 'curl_exec'; + + $propagator = Globals::propagator(); + $parent = Context::getCurrent(); $builder = $instrumentation->tracer() ->spanBuilder($spanName) + ->setParent($parent) ->setSpanKind(SpanKind::KIND_CLIENT) ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) ->setAttribute(TraceAttributes::CODE_LINENO, $lineno) - ->setAttributes($curlHandleToAttributes[$params[0]]); + ->setAttributes($curlHandleToAttributes[$params[0]]->getAttributes()); - $parent = Context::getCurrent(); $span = $builder->startSpan(); - Context::storage()->attach($span->storeInContext($parent)); + $context = $span->storeInContext($parent); + $propagator->inject($curlHandleToAttributes[$params[0]], HeadersPropagator::instance(), $context); + + Context::storage()->attach($context); + + $curlSetOptInstrumentationSuppressed = true; + + $headers = $curlHandleToAttributes[$params[0]]->getRequestHeadersToSend(); + if ($headers) { + curl_setopt($params[0], CURLOPT_HTTPHEADER, $headers); + } + + if (self::isResponseHeadersCapturingEnabled()) { + curl_setopt($params[0], CURLOPT_HEADERFUNCTION, $curlHandleToAttributes[$params[0]]->getResponseHeaderCaptureFunction()); + } + if (self::isRequestHeadersCapturingEnabled()) { + if (!$curlHandleToAttributes[$params[0]]->isVerboseEnabled()) { // we let go of captuing request headers because CURLINFO_HEADER_OUT is disabling CURLOPT_VERBOSE + curl_setopt($params[0], CURLINFO_HEADER_OUT, true); + } + //TODO log? + + } + $curlSetOptInstrumentationSuppressed = false; + }, - post: static function ($obj, array $params, mixed $retVal) { + post: static function ($obj, array $params, mixed $retVal) use ($curlHandleToAttributes) { + if (!($params[0] instanceof CurlHandle)) { + return; + } + $scope = Context::storage()->scope(); if (!$scope) { return; @@ -157,17 +186,20 @@ public static function register(): void $span = Span::fromContext($scope->context()); if ($retVal !== false) { - if ($params[0] instanceof CurlHandle) { - self::setAttributesFromCurlGetInfo($params[0], $span); - } + self::setAttributesFromCurlGetInfo($params[0], $span); } else { - if ($params[0] instanceof CurlHandle) { - $errno = curl_errno($params[0]); - if ($errno != 0) { - $errorDescription = curl_strerror($errno) . ' (' . $errno . ')'; - $span->setStatus(StatusCode::STATUS_ERROR, $errorDescription); - } - $span->setAttribute(TraceAttributes::ERROR_TYPE, 'cURL error (' . $errno . ')'); + $errno = curl_errno($params[0]); + if ($errno != 0) { + $errorDescription = curl_strerror($errno) . ' (' . $errno . ')'; + $span->setStatus(StatusCode::STATUS_ERROR, $errorDescription); + } + $span->setAttribute(TraceAttributes::ERROR_TYPE, 'cURL error (' . $errno . ')'); + } + + $capturedHeaders = $curlHandleToAttributes[$params[0]]->getCapturedResponseHeaders(); + foreach (self::getResponseHeadersToCapture() as $headerToCapture) { + if (($value = $capturedHeaders[strtolower($headerToCapture)] ?? null) != null) { + $span->setAttribute(sprintf('http.response.header.%s', strtolower(string: $headerToCapture)), $value); } } @@ -227,7 +259,7 @@ public static function register(): void null, 'curl_multi_exec', pre: null, - post: static function ($obj, array $params, mixed $retVal) use ($curlMultiToHandle, $instrumentation, $curlHandleToAttributes) { + post: static function ($obj, array $params, mixed $retVal) use ($curlMultiToHandle, $instrumentation, $curlHandleToAttributes, &$curlSetOptInstrumentationSuppressed) { if ($retVal == CURLM_OK) { $mHandle = &$curlMultiToHandle[$params[0]]; @@ -235,16 +267,40 @@ public static function register(): void if (!$mHandle['started']) { // on first call to curl_multi_exec we're marking it's a transfer start for all curl handles attached to multi handle $parent = Context::getCurrent(); + $propagator = Globals::propagator(); + foreach ($handles as $cHandle => &$metadata) { - $spanName = $curlHandleToAttributes[$cHandle][TraceAttributes::HTTP_REQUEST_METHOD] ?? 'curl_multi_exec'; + $spanName = $curlHandleToAttributes[$cHandle]->getAttributes()[TraceAttributes::HTTP_REQUEST_METHOD] ?? 'curl_multi_exec'; $builder = $instrumentation->tracer() ->spanBuilder($spanName) + ->setParent($parent) ->setSpanKind(SpanKind::KIND_CLIENT) ->setAttribute(TraceAttributes::CODE_FUNCTION, 'curl_multi_exec') - ->setAttributes($curlHandleToAttributes[$cHandle]); + ->setAttributes($curlHandleToAttributes[$cHandle]->getAttributes()); $span = $builder->startSpan(); - Context::storage()->attach($span->storeInContext($parent)); + $context = $span->storeInContext($parent); + $propagator->inject($curlHandleToAttributes[$cHandle], HeadersPropagator::instance(), $context); + + Context::storage()->attach($context); + + $curlSetOptInstrumentationSuppressed = true; + $headers = $curlHandleToAttributes[$cHandle]->getRequestHeadersToSend(); + if ($headers) { + curl_setopt($cHandle, CURLOPT_HTTPHEADER, $headers); + } + if (self::isResponseHeadersCapturingEnabled()) { + curl_setopt($cHandle, CURLOPT_HEADERFUNCTION, $curlHandleToAttributes[$cHandle]->getResponseHeaderCaptureFunction()); + } + if (self::isRequestHeadersCapturingEnabled()) { + if (!$curlHandleToAttributes[$cHandle]->isVerboseEnabled()) { // we let go of captuing request headers because CURLINFO_HEADER_OUT is disabling CURLOPT_VERBOSE + curl_setopt($cHandle, CURLINFO_HEADER_OUT, true); + } + //TODO log? + + } + $curlSetOptInstrumentationSuppressed = false; + $metadata['span'] = WeakReference::create($span); } $mHandle['started'] = true; @@ -252,12 +308,11 @@ public static function register(): void $isRunning = $params[1]; if ($isRunning == 0) { - // it is the last call to multi - in case curl_multi_info_read might not not be called anytime, we need to finish all spans left foreach ($handles as $cHandle => &$metadata) { if ($metadata['finished'] == false) { $metadata['finished'] = true; - self::finishMultiSpan(CURLE_OK, $cHandle, $metadata['span']->get()); // there is no way to get information if it was OK or not without calling curl_multi_info_read + self::finishMultiSpan(CURLE_OK, $cHandle, $curlHandleToAttributes, $metadata['span']->get()); // there is no way to get information if it was OK or not without calling curl_multi_info_read } } @@ -265,6 +320,7 @@ public static function register(): void // https://curl.se/libcurl/c/libcurl-multi.html If you want to reuse an easy handle that was added to the multi handle for transfer, you must first remove it from the multi stack and then re-add it again (possibly after having altered some options at your own choice). unset($mHandle['handles']); $mHandle['handles'] = new WeakMap(); + } } } @@ -275,7 +331,7 @@ public static function register(): void null, 'curl_multi_info_read', pre: null, - post: static function ($obj, array $params, mixed $retVal) use ($curlMultiToHandle) { + post: static function ($obj, array $params, mixed $retVal) use ($curlMultiToHandle, $curlHandleToAttributes) { $mHandle = &$curlMultiToHandle[$params[0]]; if ($retVal != false) { @@ -290,15 +346,22 @@ public static function register(): void } $currentHandle['finished'] = true; - self::finishMultiSpan($retVal['result'], $retVal['handle'], $currentHandle['span']->get()); + self::finishMultiSpan($retVal['result'], $retVal['handle'], $curlHandleToAttributes, $currentHandle['span']->get()); } } } ); } - private static function finishMultiSpan(int $curlResult, CurlHandle $curlHandle, SpanInterface $span) + private static function finishMultiSpan(int $curlResult, CurlHandle $curlHandle, $curlHandleToAttributes, SpanInterface $span) { + $scope = Context::storage()->scope(); + $scope?->detach(); + + if (!$scope || $scope->context() === Context::getCurrent()) { + return; + } + if ($curlResult == CURLE_OK) { self::setAttributesFromCurlGetInfo($curlHandle, $span); } else { @@ -306,6 +369,14 @@ private static function finishMultiSpan(int $curlResult, CurlHandle $curlHandle, $span->setStatus(StatusCode::STATUS_ERROR, $errorDescription); $span->setAttribute(TraceAttributes::ERROR_TYPE, 'cURL error (' . $curlResult . ')'); } + + $capturedHeaders = $curlHandleToAttributes[$curlHandle]->getCapturedResponseHeaders(); + foreach (self::getResponseHeadersToCapture() as $headerToCapture) { + if (($value = $capturedHeaders[strtolower($headerToCapture)] ?? null) != null) { + $span->setAttribute(sprintf('http.response.header.%s', strtolower(string: $headerToCapture)), $value); + } + } + $span->end(); } @@ -329,31 +400,26 @@ private static function redactUrlString(string $fullUrl) return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; } - private static function getAttributeFromCurlOption(int $option, mixed $value): ?array + private static function transformHeaderStringToArray(string $header): array { - switch ($option) { - case CURLOPT_CUSTOMREQUEST: - return [TraceAttributes::HTTP_REQUEST_METHOD, $value]; - case CURLOPT_HTTPGET: - // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L841 - return [TraceAttributes::HTTP_REQUEST_METHOD, 'GET']; - case CURLOPT_POST: - return [TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'POST' : 'GET')]; - case CURLOPT_POSTFIELDS: - // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L269 - return [TraceAttributes::HTTP_REQUEST_METHOD, 'POST']; - case CURLOPT_PUT: - return [TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'PUT' : 'GET')]; - case CURLOPT_NOBODY: - // Based on https://github.com/curl/curl/blob/curl-7_73_0/lib/setopt.c#L269 - return [TraceAttributes::HTTP_REQUEST_METHOD, ($value == 1 ? 'HEAD' : 'GET')]; - case CURLOPT_URL: - return [TraceAttributes::URL_FULL, self::redactUrlString($value)]; - case CURLOPT_USERAGENT: - return [TraceAttributes::USER_AGENT_ORIGINAL, $value]; + $lines = explode("\n", $header); + array_shift($lines); // skip request line + + $headersResult = []; + foreach ($lines as $line) { + $line = trim($line, "\r"); + if (empty($line)) { + continue; + } + + if (strpos($line, ': ') !== false) { + /** @psalm-suppress PossiblyUndefinedArrayOffset */ + list($key, $value) = explode(': ', $line, 2); + $headersResult[strtolower($key)] = $value; + } } - return null; + return $headersResult; } private static function setAttributesFromCurlGetInfo(CurlHandle $handle, SpanInterface $span) @@ -377,5 +443,55 @@ private static function setAttributesFromCurlGetInfo(CurlHandle $handle, SpanInt if (($value = $info['primary_port']) != 0) { $span->setAttribute(TraceAttributes::SERVER_PORT, $value); } + + if (($value = $info['primary_port']) != 0) { + $span->setAttribute(TraceAttributes::SERVER_PORT, $value); + } + + /** @phpstan-ignore-next-line */ + if (($requestHeader = $info['request_header'] ?? null) != null) { + $capturedHeaders = self::transformHeaderStringToArray($requestHeader); + foreach (self::getRequestHeadersToCapture() as $headerToCapture) { + if (($value = $capturedHeaders[strtolower($headerToCapture)] ?? null) != null) { + $span->setAttribute(sprintf('http.request.header.%s', strtolower(string: $headerToCapture)), $value); + } + } + } + } + + private static function isRequestHeadersCapturingEnabled(): bool + { + if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration') && count(Configuration::getList('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS', [])) > 0) { + return true; + } + + return get_cfg_var('otel.instrumentation.http.request_headers') !== false; + } + + private static function getRequestHeadersToCapture(): array + { + if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration') && count($values = Configuration::getList('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS', [])) > 0) { + return $values; + } + + return (array) (get_cfg_var('otel.instrumentation.http.request_headers') ?: []); + } + + private static function isResponseHeadersCapturingEnabled(): bool + { + if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration') && count(Configuration::getList('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS', [])) > 0) { + return true; + } + + return get_cfg_var('otel.instrumentation.http.response_headers') !== false; + } + + private static function getResponseHeadersToCapture(): array + { + if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration') && count($values = Configuration::getList('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS', [])) > 0) { + return $values; + } + + return (array) (get_cfg_var('otel.instrumentation.http.response_headers') ?: []); } } diff --git a/src/Instrumentation/Curl/src/HeadersPropagator.php b/src/Instrumentation/Curl/src/HeadersPropagator.php new file mode 100644 index 00000000..40c33fb7 --- /dev/null +++ b/src/Instrumentation/Curl/src/HeadersPropagator.php @@ -0,0 +1,27 @@ +setHeaderToPropagate($key, $value); + } +} diff --git a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php index 12951797..62001163 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php @@ -6,6 +6,7 @@ use ArrayObject; use OpenTelemetry\API\Instrumentation\Configurator; +use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator; use OpenTelemetry\Context\ScopeInterface; use OpenTelemetry\SDK\Trace\ImmutableSpan; use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter; @@ -31,6 +32,7 @@ public function setUp(): void $this->scope = Configurator::create() ->withTracerProvider($tracerProvider) + ->withPropagator(TraceContextPropagator::getInstance()) ->activate(); } @@ -65,7 +67,7 @@ public function test_curl_setopt(): void $span = $this->storage->offsetGet(0); $this->assertSame('POST', $span->getName()); $this->assertSame('Error', $span->getStatus()->getCode()); - $this->assertSame('Couldn\'t resolve host name (6)', $span->getStatus()->getDescription()); + $this->assertStringContainsString('resolve host', $span->getStatus()->getDescription()); } public function test_curl_setopt_array(): void @@ -78,7 +80,7 @@ public function test_curl_setopt_array(): void $span = $this->storage->offsetGet(0); $this->assertSame('POST', $span->getName()); $this->assertSame('Error', $span->getStatus()->getCode()); - $this->assertSame('Couldn\'t resolve host name (6)', $span->getStatus()->getDescription()); + $this->assertStringContainsString('resolve host', $span->getStatus()->getDescription()); } public function test_curl_copy_handle(): void @@ -95,7 +97,7 @@ public function test_curl_copy_handle(): void $span = $this->storage->offsetGet(0); $this->assertSame('POST', $span->getName()); $this->assertSame('Error', $span->getStatus()->getCode()); - $this->assertSame('Couldn\'t resolve host name (6)', $span->getStatus()->getDescription()); + $this->assertStringContainsString('resolve host', $span->getStatus()->getDescription()); } public function test_curl_exec_with_error(): void @@ -107,7 +109,7 @@ public function test_curl_exec_with_error(): void $span = $this->storage->offsetGet(0); $this->assertSame('GET', $span->getName()); $this->assertSame('Error', $span->getStatus()->getCode()); - $this->assertSame('Couldn\'t resolve host name (6)', $span->getStatus()->getDescription()); + $this->assertStringContainsString('resolve host', $span->getStatus()->getDescription()); $this->assertEquals('cURL error (6)', $span->getAttributes()->get(TraceAttributes::ERROR_TYPE)); $this->assertEquals('GET', $span->getAttributes()->get(TraceAttributes::HTTP_REQUEST_METHOD)); $this->assertEquals('http://gugugaga.gugugaga/', $span->getAttributes()->get(TraceAttributes::URL_FULL)); @@ -126,4 +128,73 @@ public function test_curl_exec(): void $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); } + + public function test_curl_exec_calls_user_defined_headerfunc(): void + { + // test if response header capturing is not breaking user header func invocation + + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=server'); + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host'); + + $ch = curl_init('http://example.com/'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + $func = function (\CurlHandle $ch, string $headerLine) { + return strlen($headerLine); + }; + + $mockedFunc = $this->getMockBuilder(\stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + + $mockedFunc->expects($this->atLeastOnce()) + ->method('__invoke') + ->willReturnCallback($func); + + curl_setopt($ch, CURLOPT_HEADERFUNCTION, $mockedFunc); + curl_exec($ch); + + $this->assertCount(1, $this->storage); + $span = $this->storage->offsetGet(0); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); + } + + public function test_curl_exec_headers_captuing(): void + { + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type'); + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host'); + + $ch = curl_init('http://example.com/'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + curl_exec($ch); + + $this->assertCount(1, $this->storage); + $span = $this->storage->offsetGet(0); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertStringContainsStringIgnoringCase('text/html', $span->getAttributes()->get('http.response.header.content-type')); + $this->assertEquals('example.com', $span->getAttributes()->get('http.request.header.host')); + } + + public function test_curl_exec_sets_traceparent(): void + { + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=traceparent'); + + $ch = curl_init('http://example.com/'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + curl_exec($ch); + + $this->assertCount(1, $this->storage); + $span = $this->storage->offsetGet(0); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertNotEmpty($span->getAttributes()->get('http.request.header.traceparent')); + } } diff --git a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php index 2f07bbb2..2c314c8e 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php @@ -6,6 +6,7 @@ use ArrayObject; use OpenTelemetry\API\Instrumentation\Configurator; +use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator; use OpenTelemetry\Context\ScopeInterface; use OpenTelemetry\SDK\Trace\ImmutableSpan; use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter; @@ -31,6 +32,7 @@ public function setUp(): void $this->scope = Configurator::create() ->withTracerProvider($tracerProvider) + ->withPropagator(TraceContextPropagator::getInstance()) ->activate(); } @@ -121,4 +123,129 @@ public function test_curl_multi_remove_handle() $span = $this->storage->offsetGet(0); $this->assertEquals('other://scheme.com/', actual: $span->getAttributes()->get(TraceAttributes::URL_FULL)); } + + public function test_curl_multi_exec_calls_user_defined_headerfunc(): void + { + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type'); + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host'); + + $mh = curl_multi_init(); + $ch1 = curl_init('http://example.com/'); + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); + + $func = function (\CurlHandle $ch, string $headerLine) { + return strlen($headerLine); + }; + + $mockedFunc = $this->getMockBuilder(\stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + + $mockedFunc->expects($this->atLeastOnce()) + ->method('__invoke') + ->willReturnCallback($func); + + curl_setopt($ch1, CURLOPT_HEADERFUNCTION, $mockedFunc); + + $ch2 = curl_copy_handle($ch1); + + curl_multi_add_handle($mh, $ch1); + curl_multi_add_handle($mh, $ch2); + + $running = null; + do { + curl_multi_exec($mh, $running); + + while (($info = curl_multi_info_read($mh)) !== false) { + } + } while ($running); + + curl_multi_remove_handle($mh, $ch1); + curl_multi_remove_handle($mh, $ch2); + curl_multi_close($mh); + + $this->assertCount(2, $this->storage); + foreach ([0, 1] as $offset) { + $span = $this->storage->offsetGet($offset); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); + } + } + + public function test_curl_multi_exec_headers_captuing(): void + { + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type'); + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host'); + + $mh = curl_multi_init(); + $ch1 = curl_init('http://example.com/'); + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); + + $ch2 = curl_copy_handle($ch1); + + curl_multi_add_handle($mh, $ch1); + curl_multi_add_handle($mh, $ch2); + + $running = null; + do { + curl_multi_exec($mh, $running); + + while (($info = curl_multi_info_read($mh)) !== false) { + } + } while ($running); + + curl_multi_remove_handle($mh, $ch1); + curl_multi_remove_handle($mh, $ch2); + curl_multi_close($mh); + + $this->assertCount(2, $this->storage); + foreach ([0, 1] as $offset) { + $span = $this->storage->offsetGet($offset); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); + $this->assertStringContainsStringIgnoringCase('text/html', $span->getAttributes()->get('http.response.header.content-type')); + $this->assertEquals('example.com', $span->getAttributes()->get('http.request.header.host')); + } + } + + public function test_curl_multi_exec_sets_traceparent(): void + { + putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=traceparent'); + + $mh = curl_multi_init(); + $ch1 = curl_init('http://example.com/'); + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); + + $ch2 = curl_copy_handle($ch1); + + curl_multi_add_handle($mh, $ch1); + curl_multi_add_handle($mh, $ch2); + + $running = null; + do { + curl_multi_exec($mh, $running); + + while (($info = curl_multi_info_read($mh)) !== false) { + } + } while ($running); + + curl_multi_remove_handle($mh, $ch1); + curl_multi_remove_handle($mh, $ch2); + curl_multi_close($mh); + + $this->assertCount(2, $this->storage); + foreach ([0, 1] as $offset) { + $span = $this->storage->offsetGet($offset); + $this->assertSame('GET', $span->getName()); + $this->assertEquals(200, $span->getAttributes()->get(TraceAttributes::HTTP_RESPONSE_STATUS_CODE)); + $this->assertEqualsIgnoringCase('http', $span->getAttributes()->get(TraceAttributes::URL_SCHEME)); + $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); + $this->assertNotEmpty($span->getAttributes()->get('http.request.header.traceparent')); + } + } + } From f9d9d7e24703f66cb07fa890e9ccfb045544c66a Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Mon, 4 Nov 2024 10:24:03 +0100 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Chris Lightfoot-Wild --- src/Instrumentation/Curl/README.md | 2 +- src/Instrumentation/Curl/src/CurlHandleMetadata.php | 5 +++-- .../Curl/tests/Integration/CurlMultiInstrumentationTest.php | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Instrumentation/Curl/README.md b/src/Instrumentation/Curl/README.md index 1b7ebc0e..a2ccc261 100644 --- a/src/Instrumentation/Curl/README.md +++ b/src/Instrumentation/Curl/README.md @@ -31,7 +31,7 @@ The extension can be disabled via [runtime configuration](https://opentelemetry. OTEL_PHP_DISABLED_INSTRUMENTATIONS=curl ``` -### Request and response headers captuing +### Request and response headers capturing Curl auto-instrumentation enables capturing headers from both requests and responses. This feature is disabled by default and be enabled through environment variables or array directives in the `php.ini` configuration file. diff --git a/src/Instrumentation/Curl/src/CurlHandleMetadata.php b/src/Instrumentation/Curl/src/CurlHandleMetadata.php index a11dcc6a..3d91f273 100644 --- a/src/Instrumentation/Curl/src/CurlHandleMetadata.php +++ b/src/Instrumentation/Curl/src/CurlHandleMetadata.php @@ -31,6 +31,7 @@ public function isVerboseEnabled(): bool { return $this->verboseEnabled; } + public function getAttributes(): array { return $this->attributes; @@ -68,12 +69,12 @@ public function getResponseHeaderCaptureFunction() { $this->responseHeaders = []; $func = function (CurlHandle $handle, string $headerLine): int { + $header = trim($headerLine, "\n\r"); - $header = trim($headerLine, "\n\r"); if (strlen($header) > 0) { if (strpos($header, ': ') !== false) { /** @psalm-suppress PossiblyUndefinedArrayOffset */ - list($key, $value) = explode(': ', $header, 2); + [$key, $value] = explode(': ', $header, 2); $this->responseHeaders[strtolower($key)] = $value; } } diff --git a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php index 2c314c8e..5f72b0b8 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php @@ -247,5 +247,4 @@ public function test_curl_multi_exec_sets_traceparent(): void $this->assertNotEmpty($span->getAttributes()->get('http.request.header.traceparent')); } } - } From caf990c8f70d4ac98167b8548334a8ee55581421 Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Mon, 4 Nov 2024 10:35:10 +0100 Subject: [PATCH 3/5] Fixes after code review --- .../Curl/src/CurlHandleMetadata.php | 32 +++++++++++++++++-- .../Curl/src/CurlInstrumentation.php | 30 ++--------------- .../Integration/CurlInstrumentationTest.php | 13 ++++++++ 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Instrumentation/Curl/src/CurlHandleMetadata.php b/src/Instrumentation/Curl/src/CurlHandleMetadata.php index 3d91f273..90f57591 100644 --- a/src/Instrumentation/Curl/src/CurlHandleMetadata.php +++ b/src/Instrumentation/Curl/src/CurlHandleMetadata.php @@ -120,7 +120,8 @@ public function updateFromCurlOption(int $option, mixed $value) break; case CURLOPT_URL: - // $this->setAttribute(TraceAttributes::URL_FULL, self::redactUrlString($value)); + $this->setAttribute(TraceAttributes::URL_FULL, self::redactUrlString($value)); + break; case CURLOPT_USERAGENT: $this->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $value); @@ -132,9 +133,34 @@ public function updateFromCurlOption(int $option, mixed $value) break; case CURLOPT_HEADERFUNCTION: $this->originalHeaderFunction = $value; - // no break - case CURLOPT_VERBOSE: $this->verboseEnabled = false; + + break; + case CURLOPT_VERBOSE: + $this->verboseEnabled = $value; + + break; } } + + public static function redactUrlString(string $fullUrl) + { + $urlParts = parse_url($fullUrl); + if ($urlParts == false) { + return; + } + + $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : ''; + $host = isset($urlParts['host']) ? $urlParts['host'] : ''; + $port = isset($urlParts['port']) ? ':' . $urlParts['port'] : ''; + $user = isset($urlParts['user']) ? 'REDACTED' : ''; + $pass = isset($urlParts['pass']) ? ':' . 'REDACTED' : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($urlParts['path']) ? $urlParts['path'] : ''; + $query = isset($urlParts['query']) ? '?' . $urlParts['query'] : ''; + $fragment = isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''; + + return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; + } + } diff --git a/src/Instrumentation/Curl/src/CurlInstrumentation.php b/src/Instrumentation/Curl/src/CurlInstrumentation.php index cf8f6d56..299639f1 100644 --- a/src/Instrumentation/Curl/src/CurlInstrumentation.php +++ b/src/Instrumentation/Curl/src/CurlInstrumentation.php @@ -57,7 +57,7 @@ public static function register(): void if ($retVal instanceof CurlHandle) { $curlHandleToAttributes[$retVal] = new CurlHandleMetadata(); if (($fullUrl = $params[0] ?? null) !== null) { - $curlHandleToAttributes[$retVal]->setAttribute(TraceAttributes::URL_FULL, self::redactUrlString($fullUrl)); + $curlHandleToAttributes[$retVal]->setAttribute(TraceAttributes::URL_FULL, CurlHandleMetadata::redactUrlString($fullUrl)); } } } @@ -380,26 +380,6 @@ private static function finishMultiSpan(int $curlResult, CurlHandle $curlHandle, $span->end(); } - private static function redactUrlString(string $fullUrl) - { - $urlParts = parse_url($fullUrl); - if ($urlParts == false) { - return; - } - - $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : ''; - $host = isset($urlParts['host']) ? $urlParts['host'] : ''; - $port = isset($urlParts['port']) ? ':' . $urlParts['port'] : ''; - $user = isset($urlParts['user']) ? 'REDACTED' : ''; - $pass = isset($urlParts['pass']) ? ':' . 'REDACTED' : ''; - $pass = ($user || $pass) ? "$pass@" : ''; - $path = isset($urlParts['path']) ? $urlParts['path'] : ''; - $query = isset($urlParts['query']) ? '?' . $urlParts['query'] : ''; - $fragment = isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''; - - return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; - } - private static function transformHeaderStringToArray(string $header): array { $lines = explode("\n", $header); @@ -407,14 +387,14 @@ private static function transformHeaderStringToArray(string $header): array $headersResult = []; foreach ($lines as $line) { - $line = trim($line, "\r"); + $line = trim($line, "\r"); if (empty($line)) { continue; } if (strpos($line, ': ') !== false) { /** @psalm-suppress PossiblyUndefinedArrayOffset */ - list($key, $value) = explode(': ', $line, 2); + [$key, $value] = explode(': ', $line, 2); $headersResult[strtolower($key)] = $value; } } @@ -444,10 +424,6 @@ private static function setAttributesFromCurlGetInfo(CurlHandle $handle, SpanInt $span->setAttribute(TraceAttributes::SERVER_PORT, $value); } - if (($value = $info['primary_port']) != 0) { - $span->setAttribute(TraceAttributes::SERVER_PORT, $value); - } - /** @phpstan-ignore-next-line */ if (($requestHeader = $info['request_header'] ?? null) != null) { $capturedHeaders = self::transformHeaderStringToArray($requestHeader); diff --git a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php index 62001163..7707f1f8 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php @@ -65,11 +65,24 @@ public function test_curl_setopt(): void $this->assertCount(1, $this->storage); $span = $this->storage->offsetGet(0); + $this->assertEquals('http://gugugaga.gugugaga/', $span->getAttributes()->get(TraceAttributes::URL_FULL)); $this->assertSame('POST', $span->getName()); $this->assertSame('Error', $span->getStatus()->getCode()); $this->assertStringContainsString('resolve host', $span->getStatus()->getDescription()); } + public function test_curl_setopt_overrides_url(): void + { + $ch = curl_init('http://example.com'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_URL, 'http://gugugaga.gugugaga/'); + curl_exec($ch); + + $this->assertCount(1, $this->storage); + $span = $this->storage->offsetGet(0); + $this->assertEquals('http://gugugaga.gugugaga/', $span->getAttributes()->get(TraceAttributes::URL_FULL)); + } + public function test_curl_setopt_array(): void { $ch = curl_init(); From be05c52946c8aad73fecc98a7dbda50f162addbf Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Tue, 5 Nov 2024 11:22:51 +0100 Subject: [PATCH 4/5] Update src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php Co-authored-by: Brett McBride --- .../Curl/tests/Integration/CurlInstrumentationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php index 7707f1f8..4ff18f10 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlInstrumentationTest.php @@ -175,7 +175,7 @@ public function test_curl_exec_calls_user_defined_headerfunc(): void $this->assertEquals(80, $span->getAttributes()->get(TraceAttributes::SERVER_PORT)); } - public function test_curl_exec_headers_captuing(): void + public function test_curl_exec_headers_capturing(): void { putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type'); putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host'); From 884a13fcbc3d24318a429bc0fa85ada05ae59a44 Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Tue, 5 Nov 2024 11:22:58 +0100 Subject: [PATCH 5/5] Update src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php Co-authored-by: Brett McBride --- .../Curl/tests/Integration/CurlMultiInstrumentationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php index 5f72b0b8..ebdd7150 100644 --- a/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php +++ b/src/Instrumentation/Curl/tests/Integration/CurlMultiInstrumentationTest.php @@ -174,7 +174,7 @@ public function test_curl_multi_exec_calls_user_defined_headerfunc(): void } } - public function test_curl_multi_exec_headers_captuing(): void + public function test_curl_multi_exec_headers_capturing(): void { putenv('OTEL_PHP_INSTRUMENTATION_HTTP_RESPONSE_HEADERS=content-type'); putenv('OTEL_PHP_INSTRUMENTATION_HTTP_REQUEST_HEADERS=host');