diff --git a/src/Concerns/CapturesState.php b/src/Concerns/CapturesState.php index 9fc83671..9f84076e 100644 --- a/src/Concerns/CapturesState.php +++ b/src/Concerns/CapturesState.php @@ -171,12 +171,12 @@ public function report(Throwable $e, ?bool $handled = null): void } try { - if ($e instanceof FatalError) { - if ($this->sampling) { - $this->ingest->writeNow($this->sensor->fatalError($e)); - } + $record = $this->sensor->exception($e, $handled); + + if ($this->sampling && ($e instanceof FatalError || ! $record['handled'])) { + $this->ingest->writeNow($record); } else { - $this->ingest->write($this->sensor->exception($e, $handled)); + $this->ingest->write($record); } } catch (Throwable $e) { Nightwatch::unrecoverableExceptionOccurred($e); diff --git a/src/SensorManager.php b/src/SensorManager.php index 5789070c..a55038a3 100644 --- a/src/SensorManager.php +++ b/src/SensorManager.php @@ -42,7 +42,6 @@ use Laravel\Nightwatch\Sensors\UserSensor; use Laravel\Nightwatch\State\CommandState; use Laravel\Nightwatch\State\RequestState; -use Laravel\Nightwatch\Types\Str; use Monolog\LogRecord; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -50,8 +49,6 @@ use Symfony\Component\HttpFoundation\Response; use Throwable; -use function hash; - /** * @internal */ @@ -252,38 +249,6 @@ public function exception(Throwable $e, ?bool $handled): array return $sensor($e, $handled); } - /** - * @return array - */ - public function fatalError(Throwable $e): array - { - $file = $this->location->normalizeFile($e->getFile()); - - return [ - 'v' => 3, - 't' => 'exception', - 'timestamp' => $this->clock->microtime(), - 'deploy' => $this->executionState->deploy, - 'server' => $this->executionState->server, - '_group' => hash('xxh128', $e::class.','.$e->getCode().','.$file.','.$e->getLine()), - 'trace_id' => $this->executionState->trace, - 'execution_source' => $this->executionState->source, - 'execution_id' => '', - 'execution_preview' => $this->executionState->executionPreview, - 'execution_stage' => $this->executionState->stage, - 'user' => $this->executionState->user->resolvedUserId(), - 'class' => $e::class, - 'file' => Str::tinyText($file), - 'line' => $e->getLine(), - 'message' => Str::text($e->getMessage()), - 'code' => (string) $e->getCode(), - 'trace' => '', - 'handled' => false, - 'php_version' => $this->executionState->phpVersion, - 'laravel_version' => $this->executionState->laravelVersion, - ]; - } - /** * @return array */ diff --git a/src/Sensors/ExceptionSensor.php b/src/Sensors/ExceptionSensor.php index 3c803ae3..5cd47546 100644 --- a/src/Sensors/ExceptionSensor.php +++ b/src/Sensors/ExceptionSensor.php @@ -13,6 +13,7 @@ use Spatie\LaravelIgnition\Exceptions\ViewException as IgnitionViewException; use SplFileObject; use stdClass; +use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; use function array_is_list; @@ -35,6 +36,8 @@ */ final class ExceptionSensor { + private const VERSION = 3; + /** * @var array */ @@ -56,6 +59,10 @@ public function __construct( */ public function __invoke(Throwable $e, ?bool $handled): array { + if ($e instanceof FatalError) { + return $this->fatalError($e); + } + $nowMicrotime = $this->clock->microtime(); [$file, $line] = $this->location->forException($e); $normalizedException = match ($e->getPrevious()) { @@ -76,7 +83,7 @@ public function __invoke(Throwable $e, ?bool $handled): array $this->executionState->exceptions++; return [ - 'v' => 3, + 'v' => self::VERSION, 't' => 'exception', 'timestamp' => $nowMicrotime, 'deploy' => $this->executionState->deploy, @@ -100,6 +107,38 @@ public function __invoke(Throwable $e, ?bool $handled): array ]; } + /** + * @return array + */ + private function fatalError(FatalError $e): array + { + $file = $this->location->normalizeFile($e->getFile()); + + return [ + 'v' => self::VERSION, + 't' => 'exception', + 'timestamp' => $this->clock->microtime(), + 'deploy' => $this->executionState->deploy, + 'server' => $this->executionState->server, + '_group' => hash('xxh128', $e::class.','.$e->getCode().','.$file.','.$e->getLine()), + 'trace_id' => $this->executionState->trace, + 'execution_source' => $this->executionState->source, + 'execution_id' => '', + 'execution_preview' => $this->executionState->executionPreview, + 'execution_stage' => $this->executionState->stage, + 'user' => $this->executionState->user->resolvedUserId(), + 'class' => $e::class, + 'file' => Str::tinyText($file), + 'line' => $e->getLine(), + 'message' => Str::text($e->getMessage()), + 'code' => (string) $e->getCode(), + 'trace' => '', + 'handled' => false, + 'php_version' => $this->executionState->phpVersion, + 'laravel_version' => $this->executionState->laravelVersion, + ]; + } + private function wasManuallyReported(Throwable $e): bool { foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, limit: 20) as $frame) { diff --git a/tests/Unit/Hooks/GuzzleMiddlewareTest.php b/tests/Unit/Hooks/GuzzleMiddlewareTest.php index ad82f5df..47eeabdf 100644 --- a/tests/Unit/Hooks/GuzzleMiddlewareTest.php +++ b/tests/Unit/Hooks/GuzzleMiddlewareTest.php @@ -17,7 +17,9 @@ public function test_it_gracefully_handles_exceptions_in_the_before_middleware() $this->core->sensor->exceptionSensor = function ($e) use (&$exceptions): array { $exceptions[] = $e; - return []; + return [ + 'handled' => true, + ]; }; $thrownInMicrotimeResolver = false; $this->core->clock->microtimeResolver = function () use (&$thrownInMicrotimeResolver): float { diff --git a/tests/Unit/SamplingTest.php b/tests/Unit/SamplingTest.php index a7dd5bab..3f830459 100644 --- a/tests/Unit/SamplingTest.php +++ b/tests/Unit/SamplingTest.php @@ -224,7 +224,7 @@ public function test_it_samples_on_exception(): void { $ingest = $this->fakeIngest(); $this->core->config['sampling']['requests'] = 0; - $this->core->sensor->exceptionSensor = fn () => []; + $this->core->sensor->exceptionSensor = fn () => ['handled' => false]; $exception = new RuntimeException('Whoops!'); Route::get('/users', fn () => throw $exception);