*/ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. */ public function register(): void { $this->reportable(function (Throwable $e) { if ($this->shouldSkipSentry($e)) { return; } if (! app()->bound('sentry') || empty(config('sentry.dsn'))) { return; } $this->configureSentryScope(); app('sentry')->captureException($e); }); } public function render($request, Throwable $e) { if ($request->expectsJson()) { if ($e instanceof ValidationException) { return ApiError::response( 'validation_failed', 'Validation failed', 'The given data was invalid.', 422, ['errors' => $e->errors()], ); } $status = $this->isHttpException($e) ? $this->toHttpException($e)->getStatusCode() : 500; $code = $status >= 500 ? 'server_error' : 'request_failed'; return ApiError::response( $code, $status >= 500 ? 'Unexpected error' : 'Request could not be completed', $this->buildGenericMessage($status), $status, ); } if ($request->inertia()) { if ($e instanceof ValidationException) { return back()->withErrors($e->errors())->withInput($request->all()); } } if ($e instanceof InvalidSignatureException && ! $request->expectsJson()) { $request->session()->flash('verification', [ 'status' => 'error', 'title' => __('auth.verification.expired_title'), 'message' => __('auth.verification.expired_message'), ]); return redirect()->route('verification.notice'); } if (! $request->expectsJson() && ! $request->inertia()) { if ($hintKey = $this->resolveServerErrorHint($e)) { $request->attributes->set('serverErrorHint', __($hintKey, [], app()->getLocale())); } } return parent::render($request, $e); } private function configureSentryScope(): void { if (! function_exists('\Sentry\configureScope')) { return; } \Sentry\configureScope(function (Scope $scope): void { $user = Auth::user(); if ($user) { $userData = [ 'id' => (string) $user->getAuthIdentifier(), ]; if (! empty($user->email)) { $userData['email'] = $user->email; } if (! empty($user->username)) { $userData['username'] = $user->username; } $scope->setUser($userData); if (! empty($user->tenant_id)) { $scope->setTag('tenant_id', (string) $user->tenant_id); } if (! empty($user->role)) { $scope->setTag('user_role', $user->role); } } }); } private function shouldSkipSentry(Throwable $throwable): bool { if ($throwable instanceof ValidationException) { return true; } $status = null; if ($this->isHttpException($throwable)) { $status = $this->toHttpException($throwable)->getStatusCode(); } elseif ($throwable instanceof HttpExceptionInterface) { $status = $throwable->getStatusCode(); } if (is_int($status) && $status < 500 && $status !== 429) { return true; } return false; } private function buildGenericMessage(int $status): string { if ($status >= 500) { return 'Something went wrong on our side. Please try again later.'; } return 'Your request could not be processed. Please verify the details and try again.'; } private function resolveServerErrorHint(Throwable $throwable): ?string { $status = $this->isHttpException($throwable) ? $this->toHttpException($throwable)->getStatusCode() : 500; if ($status < 500) { return null; } if ($this->exceptionChainContains($throwable, [ QueryException::class, DatabaseConnectionException::class, PDOException::class, 'Doctrine\\DBAL\\Exception', ])) { return 'marketing.server_error.hints.database'; } if ($this->exceptionChainContains($throwable, [ FilesystemException::class, 'League\\Flysystem\\UnableToWriteFile', 'League\\Flysystem\\UnableToCreateDirectory', 'League\\Flysystem\\UnableToCheckExistence', ])) { return 'marketing.server_error.hints.storage'; } if ($this->exceptionChainContains($throwable, [ InvalidQueueException::class, MaxAttemptsExceededException::class, 'RedisException', 'Predis\\Connection\\ConnectionException', ])) { return 'marketing.server_error.hints.queue'; } if ($this->exceptionChainContains($throwable, [ HttpClientConnectionException::class, 'GuzzleHttp\\Exception\\ConnectException', 'Psr\\Http\\Client\\NetworkExceptionInterface', 'Symfony\\Component\\HttpClient\\Exception\\TransportExceptionInterface', ])) { return 'marketing.server_error.hints.network'; } return 'marketing.server_error.hints.generic'; } private function exceptionChainContains(Throwable $throwable, array $classNames): bool { do { foreach ($classNames as $className) { if ($this->throwableIsInstanceOf($throwable, $className)) { return true; } } $throwable = $throwable->getPrevious(); } while ($throwable); return false; } private function throwableIsInstanceOf(Throwable $throwable, string $className): bool { if ($className === '') { return false; } if (! class_exists($className) && ! interface_exists($className)) { return false; } return $throwable instanceof $className; } }