Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added user context whenever possible #133

Merged
merged 12 commits into from
Nov 6, 2024
Merged
134 changes: 134 additions & 0 deletions Model/SentryInteraction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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.
*
Expand Down
42 changes: 10 additions & 32 deletions Model/SentryLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
*
Expand Down
1 change: 1 addition & 0 deletions Plugin/GlobalExceptionCatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading