diff --git a/src/Events/Request/Request.php b/src/Events/Request/Request.php index c8f96646..b047156d 100644 --- a/src/Events/Request/Request.php +++ b/src/Events/Request/Request.php @@ -11,7 +11,6 @@ use Scoutapm\Events\Tag\TagRequest; use Scoutapm\Helper\Backtrace; use Scoutapm\Helper\Timer; -use function array_slice; /** @internal */ class Request implements CommandWithChildren @@ -78,9 +77,7 @@ public function stopSpan(?float $overrideTimestamp = null) : void $command->stop($overrideTimestamp); if ($command->duration() > self::STACK_TRACE_THRESHOLD_SECONDS) { - $stack = Backtrace::capture(); - $stack = array_slice($stack, 4); - $command->tag('stack', $stack); + $command->tag('stack', Backtrace::capture()); } $this->currentCommand = $command->parent(); diff --git a/src/Helper/Backtrace.php b/src/Helper/Backtrace.php index 584e80cd..fd807f5f 100644 --- a/src/Helper/Backtrace.php +++ b/src/Helper/Backtrace.php @@ -4,26 +4,93 @@ namespace Scoutapm\Helper; -use function array_push; +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use function array_filter; +use function array_key_exists; +use function array_slice; use function debug_backtrace; +use function strpos; /** @internal */ final class Backtrace { - /** @return array> */ + /** + * @return array> + * + * @psalm-return array + */ public static function capture() : array { - $stack = debug_backtrace(); + $capturedStack = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 1); - $formatted_stack = []; - foreach ($stack as $frame) { - if (! isset($frame['file']) || ! isset($frame['line']) || ! isset($frame['function'])) { + $formattedStack = []; + foreach ($capturedStack as $frame) { + if (! isset($frame['file'], $frame['line'], $frame['function'])) { continue; } - array_push($formatted_stack, ['file' => $frame['file'], 'line' => $frame['line'], 'function' => $frame['function']]); + /** @psalm-var array{file: string, line: int, function: string, class: string, type: string} $frame */ + $formattedStack[] = [ + 'file' => $frame['file'], + 'line' => $frame['line'], + 'function' => self::formatFunctionNameFromFrame($frame), + ]; } - return $formatted_stack; + return self::filterScoutRelatedFramesFromTopOfStack($formattedStack); + } + + /** + * @param array $frame + * + * @psalm-param array{file: string, line: int, function: string, class: string, type: string} $frame + */ + private static function formatFunctionNameFromFrame(array $frame) : string + { + if (! array_key_exists('class', $frame) || ! array_key_exists('type', $frame)) { + return $frame['function']; + } + + return $frame['class'] . $frame['type'] . $frame['function']; + } + + /** + * @param array $frame + * + * @psalm-param array{file: string, line: int, function: string} $frame + */ + private static function isScoutRelated(array $frame) : bool + { + return strpos($frame['function'], 'Scoutapm') === 0; + } + + /** + * @param array> $formattedStack + * + * @return array> + * + * @psalm-param array $formattedStack + * @psalm-return array + */ + private static function filterScoutRelatedFramesFromTopOfStack(array $formattedStack) : array + { + $stillInsideScout = true; + + return array_filter( + $formattedStack, + static function (array $frame) use (&$stillInsideScout) : bool { + if (! $stillInsideScout) { + return true; + } + + if (self::isScoutRelated($frame)) { + return false; + } + + $stillInsideScout = false; + + return true; + } + ); } } diff --git a/tests/Unit/Helper/BacktraceTest.php b/tests/Unit/Helper/BacktraceTest.php index 413db218..b141402d 100644 --- a/tests/Unit/Helper/BacktraceTest.php +++ b/tests/Unit/Helper/BacktraceTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Scoutapm\Helper\Backtrace; +use function array_keys; /** @covers \Scoutapm\Helper\Backtrace */ final class BacktraceTest extends TestCase @@ -13,11 +14,9 @@ final class BacktraceTest extends TestCase public function testCapturingBacktrace() : void { $stack = Backtrace::capture(); - self::assertNotNull($stack); + foreach ($stack as $frame) { - self::assertArrayHasKey('file', $frame); - self::assertArrayHasKey('line', $frame); - self::assertArrayHasKey('function', $frame); + self::assertEquals(['file', 'line', 'function'], array_keys($frame)); } } }