diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml index 232b85e2..e3676182 100644 --- a/.github/workflows/laravel.yml +++ b/.github/workflows/laravel.yml @@ -38,8 +38,10 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl + tools: pecl, composer:v2.2 extensions: ${{ matrix.extensions }} + env: + fail-fast: true # --no-update then a full `composer update` is needed to overcome locked dependencies # See: https://github.com/composer/composer/issues/9561 - name: "Remove existing requirements components (avoid conflicts)" @@ -85,8 +87,10 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl + tools: pecl, composer:v2.2 extensions: ${{ matrix.extensions }} + env: + fail-fast: true - name: "Install Laravel quickstart project" run: "composer create-project laravel/laravel:${{ matrix.laravel-version}} test-app --prefer-dist" - name: "Add scout-apm-php as a repository" diff --git a/.github/workflows/lumen.yml b/.github/workflows/lumen.yml index 5aa9dbf1..11b0b995 100644 --- a/.github/workflows/lumen.yml +++ b/.github/workflows/lumen.yml @@ -42,8 +42,10 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl + tools: pecl, composer:v2.2 extensions: ${{ matrix.extensions }} + env: + fail-fast: true # --no-update then a full `composer update` is needed to overcome locked dependencies # See: https://github.com/composer/composer/issues/9561 - name: "Remove existing requirements components (avoid conflicts)" @@ -91,8 +93,10 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl + tools: pecl, composer:v2.2 extensions: ${{ matrix.extensions }} + env: + fail-fast: true - name: "Install Lumen quickstart project" run: "composer create-project laravel/lumen:${{ matrix.lumen-version}} test-app --prefer-dist" - name: "Add scout-apm-php as a repository" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a818f5f6..6a65d3a7 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -19,6 +19,9 @@ jobs: with: coverage: "none" php-version: "8.0" + tools: composer:v2.2 + env: + fail-fast: true - name: "Install dependencies" run: "composer install" - name: "Run PHP_CodeSniffer" @@ -34,6 +37,9 @@ jobs: with: coverage: "none" php-version: "8.0" + tools: composer:v2.2 + env: + fail-fast: true - name: "Install dependencies" run: "composer install" - name: "Run Psalm" @@ -51,6 +57,9 @@ jobs: with: coverage: "none" php-version: "8.0" + tools: composer:v2.2 + env: + fail-fast: true - name: "Require Roave/BackwardCompatibilityCheck" run: "composer require --no-update --no-interaction --prefer-dist --prefer-stable --dev roave/backward-compatibility-check:^6.0.1" - name: "Composer update with new requirements" diff --git a/.github/workflows/symfony.yml b/.github/workflows/symfony.yml index 599ebad8..21f0b3aa 100644 --- a/.github/workflows/symfony.yml +++ b/.github/workflows/symfony.yml @@ -38,8 +38,10 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl + tools: pecl, composer:v2.2 extensions: ${{ matrix.extensions }} + env: + fail-fast: true # --no-update then a full `composer update` is needed to overcome locked dependencies # See: https://github.com/composer/composer/issues/9561 - name: "Remove existing requirements components (avoid conflicts)" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 84d84be8..36daa9f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,9 +16,8 @@ jobs: fail-fast: false matrix: dependencies: ["lowest", "highest"] + scout-ext: ["with-scout-ext", "no-scout-ext"] extensions: [ - "scoutapm", - "scoutapm, mongodb", "", "mongodb" ] @@ -42,8 +41,19 @@ jobs: with: coverage: "none" php-version: "${{ matrix.php-version }}" - tools: pecl - extensions: ${{ matrix.extensions }} + tools: pecl, composer:v2.2 + extensions: "curl, ${{ matrix.extensions }}" + env: + fail-fast: true + # Normally, we'd just add "scoutapm" to the above extensions in shivammathur/setup-php, but libcurl appears to + # be missing wherever the extension is built (not immediately obvious), so install it first + - name: "Install scoutapm extension" + if: ${{ matrix.scout-ext == 'with-scout-ext' }} + run: | + sudo apt-get install -y libcurl4-openssl-dev + sudo mkdir -p /tmp/pear/temp + sudo pecl update-channels + yes | sudo pecl install -f scoutapm - name: "Install lowest dependencies" if: ${{ matrix.dependencies == 'lowest' }} run: "composer update --prefer-lowest --prefer-dist --no-interaction --no-progress" diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d8e54d..9fb03a92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 6.7.0 - 2022-01-10 + +### Added + +- [#249](https://github.com/scoutapp/scout-apm-php/pull/249) Added HTTP spans for file_get_contents and curl_exec + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + ## 6.6.1 - 2022-01-03 ### Added diff --git a/src/Agent.php b/src/Agent.php index c781110c..7e1eca96 100644 --- a/src/Agent.php +++ b/src/Agent.php @@ -51,7 +51,7 @@ final class Agent implements ScoutApmAgent private const METADATA_CACHE_TTL_SECONDS = 600; - private const WARN_WHEN_EXTENSION_IS_OLDER_THAN = '1.4.0'; + private const WARN_WHEN_EXTENSION_IS_OLDER_THAN = '1.5.0'; /** @var Config */ private $config; @@ -320,6 +320,14 @@ function (): ?Span { foreach ($this->phpExtension->getCalls() as $recordedCall) { $callSpan = $this->request->startSpan($recordedCall->functionName(), $recordedCall->timeEntered()); + $maybeHttpUrl = $recordedCall->maybeHttpUrl(); + if ($maybeHttpUrl !== null) { + $httpMethod = $recordedCall->maybeHttpMethod() ?: 'GET'; + $httpSpan = $this->request->startSpan('HTTP/' . $httpMethod, $recordedCall->timeEntered()); + $httpSpan->tag(Tag::TAG_URI, $maybeHttpUrl); + $this->request->stopSpan($recordedCall->timeExited()); + } + $arguments = $recordedCall->filteredArguments(); if (count($arguments) > 0) { @@ -448,6 +456,7 @@ public function send(): bool $this->registerIfRequired(); $this->sendMetadataIfRequired(); + $this->addSpansFromExtension(); $this->request->stopIfRunning(); $shouldLogContent = $this->config->get(ConfigKey::LOG_PAYLOAD_CONTENT); diff --git a/src/Events/Tag/Tag.php b/src/Events/Tag/Tag.php index 9fe190d6..417c1417 100644 --- a/src/Events/Tag/Tag.php +++ b/src/Events/Tag/Tag.php @@ -18,6 +18,7 @@ abstract class Tag implements Command public const TAG_REQUEST_PATH = 'path'; public const TAG_QUEUE_TIME = 'scout.queue_time_ns'; public const TAG_REACHED_SPAN_CAP = 'scout.reached_span_cap'; + public const TAG_URI = 'uri'; /** @var RequestId */ protected $requestId; diff --git a/src/Extension/RecordedCall.php b/src/Extension/RecordedCall.php index 4b396962..f669078b 100644 --- a/src/Extension/RecordedCall.php +++ b/src/Extension/RecordedCall.php @@ -6,6 +6,15 @@ use Webmozart\Assert\Assert; +use function array_key_exists; +use function in_array; +use function is_array; +use function is_string; +use function json_decode; +use function preg_replace; +use function stripos; +use function strtoupper; + final class RecordedCall { /** @var string */ @@ -87,16 +96,90 @@ public function timeExited(): float * avoid potentially spilling personally identifiable information. Another reason to only return specific arguments * is to avoid sending loads of data unnecessarily. * - * @return mixed[] + * @return list|array{url: string, method: string} */ public function filteredArguments(): array { - if ($this->function === 'file_get_contents') { + if ($this->function === 'file_get_contents' || $this->function === 'curl_exec') { + $method = 'GET'; + + // file_get_contents was used with a stream context + if ( + $this->function === 'file_get_contents' + && array_key_exists(2, $this->arguments) + && is_string($this->arguments[2]) + ) { + /** @var mixed $fileGetContentsStreamContext */ + $fileGetContentsStreamContext = json_decode($this->arguments[2], true); + if ( + is_array($fileGetContentsStreamContext) + && array_key_exists('http', $fileGetContentsStreamContext) + && is_array($fileGetContentsStreamContext['http']) + && array_key_exists('method', $fileGetContentsStreamContext['http']) + && is_string($fileGetContentsStreamContext['http']['method']) + && ! empty($fileGetContentsStreamContext['http']['method']) + ) { + $method = $fileGetContentsStreamContext['http']['method']; + } + } + + // curl_exec with CURLOPT_POST option was used with a truthy value + if ($this->function === 'curl_exec' && array_key_exists(1, $this->arguments) && $this->arguments[1]) { + $method = 'POST'; + } + + // curl_exec with CURLOPT_POST option was used with a truthy value + if ( + $this->function === 'curl_exec' + && array_key_exists(2, $this->arguments) + && is_string($this->arguments[2]) + && ! empty($this->arguments[2]) + ) { + $method = $this->arguments[2]; + } + return [ 'url' => (string) $this->arguments[0], + 'method' => preg_replace('/[^A-Z]/', '', strtoupper($method)), ]; } return []; } + + public function maybeHttpUrl(): ?string + { + if (! in_array($this->function, ['file_get_contents', 'curl_exec'], true)) { + return null; + } + + $arguments = $this->filteredArguments(); + + if (! array_key_exists('url', $arguments)) { + return null; + } + + $url = $arguments['url']; + + if (stripos($url, 'http://') !== 0 && stripos($url, 'https://') !== 0) { + return null; + } + + return $url; + } + + public function maybeHttpMethod(): ?string + { + if (! in_array($this->function, ['file_get_contents', 'curl_exec'], true)) { + return null; + } + + $arguments = $this->filteredArguments(); + + if (! array_key_exists('method', $arguments)) { + return null; + } + + return $arguments['method']; + } } diff --git a/tests/Integration/AgentTest.php b/tests/Integration/AgentTest.php index f2337217..3fea3319 100644 --- a/tests/Integration/AgentTest.php +++ b/tests/Integration/AgentTest.php @@ -21,6 +21,9 @@ use Scoutapm\UnitTests\TestLogger; use function assert; +use function curl_exec; +use function curl_init; +use function curl_setopt; use function extension_loaded; use function file_get_contents; use function fopen; @@ -35,8 +38,14 @@ use function sleep; use function sprintf; use function str_repeat; +use function stream_context_create; use function uniqid; +use const CURLOPT_CUSTOMREQUEST; +use const CURLOPT_POST; +use const CURLOPT_RETURNTRANSFER; +use const CURLOPT_URL; + /** * @psalm-import-type UnserializedCapturedMessagesList from MessageCapturingConnectorDelegator * @coversNothing @@ -278,7 +287,7 @@ static function (array $commands): bool { if (TestHelper::scoutApmExtensionAvailable()) { $fileGetContentsSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents', 'parent_id' => $controllerSpanId], next($commands), 'span_id'); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__]], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__, 'method' => 'GET']], next($commands), null); TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $fileGetContentsSpanId], next($commands), null); } @@ -286,7 +295,7 @@ static function (array $commands): bool { if (TestHelper::scoutApmExtensionAvailable()) { $fileGetContentsSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents', 'parent_id' => $fooSpanId], next($commands), 'span_id'); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__]], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__, 'method' => 'GET']], next($commands), null); TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $fileGetContentsSpanId], next($commands), null); } @@ -294,7 +303,7 @@ static function (array $commands): bool { if (TestHelper::scoutApmExtensionAvailable()) { $fileGetContentsSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents', 'parent_id' => $barSpanId], next($commands), 'span_id'); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__]], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__, 'method' => 'GET']], next($commands), null); TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $fileGetContentsSpanId], next($commands), null); } @@ -306,7 +315,7 @@ static function (array $commands): bool { if (TestHelper::scoutApmExtensionAvailable()) { $fileGetContentsSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents', 'parent_id' => $controllerSpanId], next($commands), 'span_id'); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__]], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => __FILE__, 'method' => 'GET']], next($commands), null); TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $fileGetContentsSpanId], next($commands), null); } @@ -462,4 +471,104 @@ static function (array $commands): bool { null ); } + + /** @noinspection PhpExpressionResultUnusedInspection */ + public function testHttpSpansArePromoted(): void + { + if (! TestHelper::scoutApmExtensionAvailable()) { + self::markTestSkipped('scoutapm extension must be enabled for HTTP spans'); + } + + if (! extension_loaded('curl')) { + self::markTestSkipped('curl extension must be enabled for HTTP spans'); + } + + $this->setUpWithConfiguration(Config::fromArray([ + ConfigKey::APPLICATION_NAME => self::APPLICATION_NAME, + ConfigKey::MONITORING_ENABLED => true, + ])); + + $httpUrl = 'http://scoutapm.com/robots.txt'; + $httpsUrl = 'https://scoutapm.com/robots.txt'; + + $this->agent->webTransaction( + 'TestingHttpSpans', + static function () use ($httpUrl, $httpsUrl): void { + file_get_contents($httpUrl); + + // 405 Method Not Allowed is expected, and emitted as a warning, so ignore for this test + @file_get_contents($httpsUrl, false, stream_context_create([ + 'http' => ['method' => 'POST'], + ])); + + $httpCurl = curl_init(); + curl_setopt($httpCurl, CURLOPT_URL, $httpUrl); + curl_setopt($httpCurl, CURLOPT_RETURNTRANSFER, true); + curl_exec($httpCurl); + + $httpsPostCurl = curl_init(); + curl_setopt($httpsPostCurl, CURLOPT_URL, $httpsUrl); + curl_setopt($httpsPostCurl, CURLOPT_POST, 1); + curl_setopt($httpsPostCurl, CURLOPT_RETURNTRANSFER, true); + curl_exec($httpsPostCurl); + + $httpsPutCurl = curl_init(); + curl_setopt($httpsPutCurl, CURLOPT_URL, $httpsUrl); + curl_setopt($httpsPutCurl, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($httpsPutCurl, CURLOPT_RETURNTRANSFER, true); + curl_exec($httpsPutCurl); + } + ); + + self::assertTrue($this->agent->send(), 'Failed to send messages. ' . $this->formatCapturedLogMessages()); + + $unserialized = $this->connector->sentMessages; + reset($unserialized); // Skip Register event + next($unserialized); // Skip Metadata event + + $assertSpanContainingHttpSpan = + /** + * @psalm-param list>> $commands + */ + static function (&$commands, string $outerOperation, string $method, string $url): void { + $fileGetContentsSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => $outerOperation], next($commands), 'span_id'); + + $httpSpanId = TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'HTTP/' . $method, 'parent_id' => $fileGetContentsSpanId], next($commands), 'span_id'); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $httpSpanId, 'tag' => 'uri', 'value' => $url], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $httpSpanId], TestHelper::skipBacktraceTagIfNext($commands), null); + + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['span_id' => $fileGetContentsSpanId, 'tag' => 'args', 'value' => ['url' => $url, 'method' => $method]], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', ['span_id' => $fileGetContentsSpanId], TestHelper::skipBacktraceTagIfNext($commands), null); + }; + + TestHelper::assertUnserializedCommandContainsPayload( + 'BatchCommand', + [ + 'commands' => + /** @psalm-param UnserializedCapturedMessagesList $commands */ + static function (array $commands) use ($assertSpanContainingHttpSpan, $httpUrl, $httpsUrl): bool { + TestHelper::assertUnserializedCommandContainsPayload('StartRequest', [], reset($commands), null); + + TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'Controller/TestingHttpSpans'], next($commands), null); + + $assertSpanContainingHttpSpan($commands, 'file_get_contents', 'GET', $httpUrl); + $assertSpanContainingHttpSpan($commands, 'file_get_contents', 'POST', $httpsUrl); + $assertSpanContainingHttpSpan($commands, 'curl_exec', 'GET', $httpUrl); + $assertSpanContainingHttpSpan($commands, 'curl_exec', 'POST', $httpsUrl); + $assertSpanContainingHttpSpan($commands, 'curl_exec', 'PUT', $httpsUrl); + + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); + + TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'memory_delta'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'path'], next($commands), null); + + TestHelper::assertUnserializedCommandContainsPayload('FinishRequest', [], next($commands), null); + + return true; + }, + ], + next($unserialized), + null + ); + } } diff --git a/tests/Integration/TestHelper.php b/tests/Integration/TestHelper.php index da8f8b70..4a0ee053 100644 --- a/tests/Integration/TestHelper.php +++ b/tests/Integration/TestHelper.php @@ -9,11 +9,14 @@ use PHPUnit\Framework\Assert; use Scoutapm\Helper\Timer; +use function array_key_exists; use function array_keys; use function extension_loaded; use function implode; +use function is_array; use function is_callable; use function is_string; +use function next; use function sprintf; use function var_export; @@ -24,6 +27,27 @@ public static function scoutApmExtensionAvailable(): bool return extension_loaded('scoutapm'); } + /** + * @param list>> $commands + * + * @return array> + */ + public static function skipBacktraceTagIfNext(array &$commands): array + { + $nextCommand = next($commands); + if ( + array_key_exists('TagSpan', $nextCommand) + && is_array($nextCommand['TagSpan']) + && $nextCommand['TagSpan']['tag'] === 'stack' + ) { + // In this case, the request was slow (can happen occasionally), so the stack trace was added. + // We're not interested in stack traces in this test, so just skip it. + $nextCommand = next($commands); + } + + return $nextCommand; + } + /** * @param string[]|callable[]|array $keysAndValuesToExpect * @param mixed[][]|array> $actualCommand diff --git a/tests/Unit/AgentTest.php b/tests/Unit/AgentTest.php index 96496a69..1a3e2f7a 100644 --- a/tests/Unit/AgentTest.php +++ b/tests/Unit/AgentTest.php @@ -199,7 +199,7 @@ public function testFullAgentSequence(): void 'entered' => $microtime - 1, 'exited' => $microtime, 'time_taken' => 1, - 'argv' => ['http://some-url'], + 'argv' => ['http://some-url', 'method' => 'GET'], ]), ]); @@ -219,24 +219,31 @@ public function testFullAgentSequence(): void TestHelper::assertUnserializedCommandContainsPayload( 'BatchCommand', [ - 'commands' => static function (array $commands): bool { - TestHelper::assertUnserializedCommandContainsPayload('StartRequest', [], reset($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'args', 'value' => ['url' => 'http://some-url']], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'stack'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'Controller/Test'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'SQL/Query'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'sql.query', 'value' => 'select * from foo'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'uri', 'value' => 'example.com/foo/bar.php'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'memory_delta'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'path'], next($commands), null); - TestHelper::assertUnserializedCommandContainsPayload('FinishRequest', [], next($commands), null); - - return true; - }, + 'commands' => + /** + * @psalm-param list>> $commands + */ + static function (array $commands): bool { + TestHelper::assertUnserializedCommandContainsPayload('StartRequest', [], reset($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'file_get_contents'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'HTTP/GET'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'uri', 'value' => 'http://some-url'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], TestHelper::skipBacktraceTagIfNext($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'args', 'value' => ['url' => 'http://some-url', 'method' => 'GET']], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'stack'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'Controller/Test'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StartSpan', ['operation' => 'SQL/Query'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagSpan', ['tag' => 'sql.query', 'value' => 'select * from foo'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('StopSpan', [], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'uri', 'value' => 'example.com/foo/bar.php'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'memory_delta'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('TagRequest', ['tag' => 'path'], next($commands), null); + TestHelper::assertUnserializedCommandContainsPayload('FinishRequest', [], next($commands), null); + + return true; + }, ], $request->jsonSerialize(), null diff --git a/tests/Unit/Extension/RecordedCallTest.php b/tests/Unit/Extension/RecordedCallTest.php index 6abfd2a2..ceba1d43 100644 --- a/tests/Unit/Extension/RecordedCallTest.php +++ b/tests/Unit/Extension/RecordedCallTest.php @@ -48,7 +48,7 @@ public function filteredArgumentsDataProvider(): array return [ 'file_get_contents' => [ 'recordedFunctionName' => 'file_get_contents', - 'expectedFilteredArguments' => ['url' => 'a'], + 'expectedFilteredArguments' => ['url' => 'a', 'method' => 'GET'], ], 'password_hash' => [ 'recordedFunctionName' => 'password_hashj',