From 4bfe25424d8b9ce447dce55804be7a931eebfb44 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 23 Mar 2022 12:03:43 +0000 Subject: [PATCH] Prevent the same exception from being sent to Scout multiple times --- CHANGELOG.md | 22 ++++++++++++++++++++ src/Errors/ScoutErrorHandling.php | 13 +++++++++++- tests/Unit/Errors/ScoutErrorHandlingTest.php | 20 ++++++++++++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b395d1..e13a5300 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. +## 8.0.1 - 2022-03-23 + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#261](https://github.com/scoutapp/scout-apm-php/pull/261) Prevent the same exception from being sent to Scout multiple times + ## 8.0.0 - 2022-03-08 ### Added diff --git a/src/Errors/ScoutErrorHandling.php b/src/Errors/ScoutErrorHandling.php index ff68310d..032886a2 100644 --- a/src/Errors/ScoutErrorHandling.php +++ b/src/Errors/ScoutErrorHandling.php @@ -18,6 +18,7 @@ use function is_array; use function register_shutdown_function; use function set_exception_handler; +use function spl_object_hash; use const E_COMPILE_ERROR; use const E_CORE_ERROR; @@ -36,6 +37,8 @@ final class ScoutErrorHandling implements ErrorHandling private $reportingClient; /** @var Config */ private $config; + /** @var list */ + private $objectHashesOfHandledExceptions = []; /** @var list */ private $errorEvents = []; /** @var callable|null */ @@ -102,7 +105,15 @@ public function recordThrowable(Throwable $throwable): void return; } - $this->errorEvents[] = ErrorEvent::fromThrowable($this->request, $throwable); + $thisThrowableObjectHash = spl_object_hash($throwable); + + // Storing the object hashes & checking means we don't send exactly the same exception twice in one request + if (! in_array($thisThrowableObjectHash, $this->objectHashesOfHandledExceptions, true)) { + $this->objectHashesOfHandledExceptions[] = $thisThrowableObjectHash; + + $this->errorEvents[] = ErrorEvent::fromThrowable($this->request, $throwable); + } + $this->sendCollectedErrors(); } diff --git a/tests/Unit/Errors/ScoutErrorHandlingTest.php b/tests/Unit/Errors/ScoutErrorHandlingTest.php index 686f61af..4b993ee5 100644 --- a/tests/Unit/Errors/ScoutErrorHandlingTest.php +++ b/tests/Unit/Errors/ScoutErrorHandlingTest.php @@ -175,11 +175,27 @@ public function testExceptionIsNotRecordedWhenDisabled(): void $this->reportingClient ->expects(self::never()) - ->method('sendErrorToScout') - ->with(self::isInstanceOf(ErrorEvent::class)); + ->method('sendErrorToScout'); $handling->recordThrowable(new RuntimeException()); $handling->sendCollectedErrors(); } + + public function testExactlyTheSameExceptionIsNotSentTwice(): void + { + $handling = $this->errorHandlingFromConfig(Config::fromArray([Config\ConfigKey::ERRORS_ENABLED => true])); + + $this->reportingClient + ->expects(self::once()) + ->method('sendErrorToScout') + ->with(self::containsOnlyInstancesOf(ErrorEvent::class)); + + $exceptionInstance = new RuntimeException(); + + $handling->recordThrowable($exceptionInstance); + $handling->recordThrowable($exceptionInstance); + + $handling->sendCollectedErrors(); + } }