diff --git a/Model/SentryInteraction.php b/Model/SentryInteraction.php index eac8097..44e7471 100644 --- a/Model/SentryInteraction.php +++ b/Model/SentryInteraction.php @@ -6,11 +6,33 @@ // phpcs:disable Magento2.Functions.DiscouragedFunction +use Magento\Authorization\Model\UserContextInterface; +use Magento\Backend\Model\Auth\Session as AdminSession; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\App\Area; +use Magento\Framework\App\State; +use Magento\Framework\Exception\LocalizedException; +use ReflectionClass; +use Sentry\State\Scope; + use function Sentry\captureException; +use function Sentry\configureScope; use function Sentry\init; class SentryInteraction { + /** + * SentryInteraction constructor. + * + * @param UserContextInterface $userContext + * @param State $appState + */ + public function __construct( + private UserContextInterface $userContext, + private State $appState + ) { + } + /** * Initialize Sentry with passed config. * @@ -23,6 +45,118 @@ public function initialize($config) init($config); } + /** + * Check if we might be able to get the user data. + */ + private function canGetUserData() + { + try { + // @phpcs:ignore Generic.PHP.NoSilencedErrors + return in_array(@$this->appState->getAreaCode(), [Area::AREA_ADMINHTML, Area::AREA_FRONTEND]); + } catch (LocalizedException $ex) { + return false; + } + } + + /** + * Attempt to get userdata from the current session. + */ + private function getSessionUserData() + { + if (!$this->canGetUserData()) { + return []; + } + + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $reflectionClass = new ReflectionClass($objectManager); + $sharedInstances = $reflectionClass->getProperty('_sharedInstances'); + $sharedInstances->setAccessible(true); + + if ($this->appState->getAreaCode() === Area::AREA_ADMINHTML) { + if (!array_key_exists(ltrim(AdminSession::class, '\\'), $sharedInstances->getValue($objectManager))) { + // Don't intitialise session if it has not already been started, this causes problems with dynamic resources. + return []; + } + $adminSession = $objectManager->get(AdminSession::class); + + if ($adminSession->isLoggedIn()) { + return [ + 'id' => $adminSession->getUser()->getId(), + 'email' => $adminSession->getUser()->getEmail(), + 'user_type' => UserContextInterface::USER_TYPE_ADMIN, + ]; + } + } + + if ($this->appState->getAreaCode() === Area::AREA_FRONTEND) { + if (!array_key_exists(ltrim(CustomerSession::class, '\\'), $sharedInstances->getValue($objectManager))) { + return []; + } + $customerSession = $objectManager->get(CustomerSession::class); + + if ($customerSession->isLoggedIn()) { + return [ + 'id' => $customerSession->getCustomer()->getId(), + 'email' => $customerSession->getCustomer()->getEmail(), + 'website_id' => $customerSession->getCustomer()->getWebsiteId(), + 'store_id' => $customerSession->getCustomer()->getStoreId(), + 'user_type' => UserContextInterface::USER_TYPE_CUSTOMER, + ]; + } + } + + return []; + } + + /** + * Attempt to add the user context to the exception. + */ + public function addUserContext() + { + $userId = null; + $userType = null; + $userData = []; + + \Magento\Framework\Profiler::start('SENTRY::add_user_context'); + + try { + $userId = $this->userContext->getUserId(); + if ($userId) { + $userType = $this->userContext->getUserType(); + } + + if ($this->canGetUserData() && count($userData = $this->getSessionUserData())) { + $userId = $userData['id'] ?? $userId; + $userType = $userData['user_type'] ?? $userType; + unset($userData['user_type']); + } + + if (!$userId) { + return; + } + + configureScope(function (Scope $scope) use ($userType, $userId, $userData) { + $scope->setUser([ + 'id' => $userId, + ...$userData, + 'user_type' => match ($userType) { + UserContextInterface::USER_TYPE_INTEGRATION => 'integration', + UserContextInterface::USER_TYPE_ADMIN => 'admin', + UserContextInterface::USER_TYPE_CUSTOMER => 'customer', + UserContextInterface::USER_TYPE_GUEST => 'guest', + default => 'unknown' + }, + ]); + }); + } catch (\Throwable $e) { + // User context could not be found or added. + \Magento\Framework\Profiler::stop('SENTRY::add_user_context'); + + return; + } + \Magento\Framework\Profiler::stop('SENTRY::add_user_context'); + } + /** * Capture passed exception. * diff --git a/Model/SentryLog.php b/Model/SentryLog.php index edf19dd..54eb3ea 100755 --- a/Model/SentryLog.php +++ b/Model/SentryLog.php @@ -21,18 +21,20 @@ class SentryLog extends Monolog /** * SentryLog constructor. * - * @param string $name - * @param Data $data - * @param Session $customerSession - * @param State $appState - * @param array $handlers - * @param array $processors + * @param string $name + * @param Data $data + * @param Session $customerSession + * @param State $appState + * @param SentryInteraction $sentryInteraction + * @param array $handlers + * @param array $processors */ public function __construct( $name, protected Data $data, protected Session $customerSession, private State $appState, + private SentryInteraction $sentryInteraction, array $handlers = [], array $processors = [] ) { @@ -63,13 +65,14 @@ public function send($message, $logLevel, $context = []) \Sentry\configureScope( function (SentryScope $scope) use ($context, $customTags): void { $this->setTags($scope, $customTags); - $this->setUser($scope); if (false === empty($context)) { $scope->setContext('Custom context', $context); } } ); + $this->sentryInteraction->addUserContext(); + if ($message instanceof \Throwable) { $lastEventId = \Sentry\captureException($message); } else { @@ -86,31 +89,6 @@ function (SentryScope $scope) use ($context, $customTags): void { } } - /** - * Attempt to add user information based on customerSession. - * - * @param SentryScope $scope - */ - private function setUser(SentryScope $scope): void - { - try { - if (!$this->canGetCustomerData() - || !$this->customerSession->isLoggedIn()) { - return; - } - - $customerData = $this->customerSession->getCustomer(); - $scope->setUser([ - 'id' => $customerData->getEntityId(), - 'email' => $customerData->getEmail(), - 'website_id' => $customerData->getWebsiteId(), - 'store_id' => $customerData->getStoreId(), - ]); - } catch (SessionException $e) { - return; - } - } - /** * Check if we can retrieve customer data. * diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index 50c243b..5e93eee 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -87,6 +87,7 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) } catch (\Throwable $ex) { try { if ($this->sentryHelper->shouldCaptureException($ex)) { + $this->sentryInteraction->addUserContext(); $this->sentryInteraction->captureException($ex); } } catch (\Throwable $bigProblem) {