diff --git a/src/Sentry/Laravel/Integration.php b/src/Sentry/Laravel/Integration.php index df319a9f..e74ad703 100644 --- a/src/Sentry/Laravel/Integration.php +++ b/src/Sentry/Laravel/Integration.php @@ -2,6 +2,7 @@ namespace Sentry\Laravel; +use Illuminate\Database\Eloquent\MissingAttributeException; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\LazyLoadingViolationException; use Illuminate\Routing\Route; @@ -215,6 +216,55 @@ public static function captureUnhandledException(Throwable $throwable): ?EventId return SentrySdk::getCurrentHub()->captureException($throwable, $hint); } + /** + * Returns a callback that can be passed to `Model::handleMissingAttributeViolationUsing` to report missing attribute violations to Sentry. + * + * @param callable|null $callback Optional callback to be called after the violation is reported to Sentry. + * + * @return callable + */ + public static function missingAttributeViolationReporter(?callable $callback = null): callable + { + return new class($callback) { + use ResolvesEventOrigin; + + /** @var callable|null $callback */ + private $callback; + + public function __construct(?callable $callback) + { + $this->callback = $callback; + } + + public function __invoke(Model $model, string $attribute): void + { + SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($model, $attribute) { + $scope->setContext('violation', [ + 'model' => get_class($model), + 'attribute' => $attribute, + 'origin' => $this->resolveEventOrigin(), + 'kind' => 'missing_attribute', + ]); + + SentrySdk::getCurrentHub()->captureEvent( + tap(Event::createEvent(), static function (Event $event) { + $event->setLevel(Severity::warning()); + }), + EventHint::fromArray([ + 'exception' => new MissingAttributeException($model, $attribute), + 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), + ]) + ); + }); + + // Forward the violation to the next handler if there is one + if ($this->callback !== null) { + call_user_func($this->callback, $model, $attribute); + } + } + }; + } + /** * Returns a callback that can be passed to `Model::handleLazyLoadingViolationUsing` to report lazy loading violations to Sentry. * @@ -238,7 +288,7 @@ public function __construct(?callable $callback) public function __invoke(Model $model, string $relation): void { // Laravel uses these checks itself to not throw an exception if the model doesn't exist or was just created - // See: https://github.com/laravel/framework/blob/438d02d3a891ab4d73ffea2c223b5d37947b5e93/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L563 + // See: https://github.com/laravel/framework/blob/438d02d3a891ab4d73ffea2c223b5d37947b5e93/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L559-L561 if (!$model->exists || $model->wasRecentlyCreated) { return; } @@ -248,6 +298,7 @@ public function __invoke(Model $model, string $relation): void 'model' => get_class($model), 'relation' => $relation, 'origin' => $this->resolveEventOrigin(), + 'kind' => 'lazy_loading', ]); SentrySdk::getCurrentHub()->captureEvent(