diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 58684a8..9990d42 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -22,4 +22,4 @@ **Checklist** - [ ] I've ran `composer run codestyle` -- [ ] I've ran `composer run phpstan` \ No newline at end of file +- [ ] I've ran `composer run analyse` \ No newline at end of file diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml index 7a4cc0b..ebe69ad 100644 --- a/.github/workflows/analyse.yml +++ b/.github/workflows/analyse.yml @@ -9,7 +9,13 @@ on: jobs: test: runs-on: ubuntu-latest - name: analyse + strategy: + fail-fast: true + matrix: + php: [8.2, 8.3] + stability: [prefer-lowest, prefer-stable] + + name: PHPStan - P${{ matrix.php }} - ${{ matrix.stability }} steps: - name: Checkout code @@ -18,12 +24,12 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo coverage: none - name: Install dependencies - run: composer install --no-interaction + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Analyse run: composer run analyse \ No newline at end of file diff --git a/.github/workflows/php.yml b/.github/workflows/phpcs.yml similarity index 62% rename from .github/workflows/php.yml rename to .github/workflows/phpcs.yml index d6d2045..bb6bcb9 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/phpcs.yml @@ -9,7 +9,13 @@ on: jobs: test: runs-on: ubuntu-latest - name: PHPCS + strategy: + fail-fast: true + matrix: + php: [8.2, 8.3] + stability: [prefer-lowest, prefer-stable] + + name: PHPCS - P${{ matrix.php }} - ${{ matrix.stability }} steps: - name: Checkout code @@ -18,12 +24,12 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo coverage: none - name: Install dependencies - run: composer install --no-interaction + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Analyse run: composer run phpcs \ No newline at end of file diff --git a/Helper/Version.php b/Helper/Version.php index b2cb3ba..82c3c8b 100644 --- a/Helper/Version.php +++ b/Helper/Version.php @@ -36,7 +36,7 @@ class Version extends AbstractHelper public function __construct( private \Magento\Framework\App\State $appState, private \Magento\Framework\App\View\Deployment\Version\StorageInterface $versionStorage, - DeploymentConfig $deploymentConfig = null + ?DeploymentConfig $deploymentConfig = null ) { $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class); } diff --git a/Logger/Handler/Sentry.php b/Logger/Handler/Sentry.php new file mode 100644 index 0000000..dc9a6ec --- /dev/null +++ b/Logger/Handler/Sentry.php @@ -0,0 +1,105 @@ +sentryHelper->collectModuleConfig(); + if ($config['log_level']) { + $this->setLevel($config['log_level']); + } + + return parent::isHandling($record) && $this->deploymentConfig->isAvailable() && $this->sentryHelper->isActive(); + } + + /** + * @inheritDoc + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + $this->sentryLog->send($record['message'], $record['level'], $record['context']); + + return false; + } + } +} else { + class Sentry extends AbstractHandler + { + /** + * Construct. + * + * @param Data $sentryHelper + * @param SentryLog $sentryLog + * @param DeploymentConfig $deploymentConfig + */ + public function __construct( + protected Data $sentryHelper, + protected SentryLog $sentryLog, + protected DeploymentConfig $deploymentConfig, + ) { + parent::__construct(); + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + $config = $this->sentryHelper->collectModuleConfig(); + if ($config['log_level']) { + $this->setLevel($config['log_level']); + } + + return parent::isHandling($record) && $this->deploymentConfig->isAvailable() && $this->sentryHelper->isActive(); + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + $this->sentryLog->send($record['message'], $record['level'], $record['context']); + + return false; + } + } +} +// phpcs:enable Generic.Classes.DuplicateClassName,PSR2.Classes.ClassDeclaration,PSR1.Classes.ClassDeclaration.MultipleClasses diff --git a/Model/SentryLog.php b/Model/SentryLog.php index 54eb3ea..ba58f95 100755 --- a/Model/SentryLog.php +++ b/Model/SentryLog.php @@ -8,10 +8,12 @@ use Magento\Framework\App\State; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\SessionException; -use Magento\Framework\Logger\Monolog; +use Sentry\EventHint; +use Sentry\ExceptionMechanism; +use Sentry\Stacktrace; use Sentry\State\Scope as SentryScope; -class SentryLog extends Monolog +class SentryLog { /** * @var array @@ -21,24 +23,17 @@ class SentryLog extends Monolog /** * SentryLog constructor. * - * @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 = [] ) { - parent::__construct($name, $handlers, $processors); } /** @@ -76,7 +71,11 @@ function (SentryScope $scope) use ($context, $customTags): void { if ($message instanceof \Throwable) { $lastEventId = \Sentry\captureException($message); } else { - $lastEventId = \Sentry\captureMessage($message, \Sentry\Severity::fromError($logLevel)); + $lastEventId = \Sentry\captureMessage( + $message, + \Sentry\Severity::fromError($logLevel), + $this->monologContextToSentryHint($context) + ); } /// when using JS SDK you can use this for custom error page printing @@ -89,6 +88,29 @@ function (SentryScope $scope) use ($context, $customTags): void { } } + /** + * Turn the monolog context into a format Sentrys EventHint can deal with. + * + * @param array $context + * + * @return EventHint|null + */ + public function monologContextToSentryHint(array $context): ?EventHint + { + return EventHint::fromArray( + [ + 'exception' => ($context['exception'] ?? null) instanceof \Throwable ? $context['exception'] : null, + 'mechanism' => ($context['mechanism'] ?? null) instanceof ExceptionMechanism ? $context['mechanism'] : null, + 'stacktrace' => ($context['stacktrace'] ?? null) instanceof Stacktrace ? $context['stacktrace'] : null, + 'extra' => array_filter( + $context, + fn ($key) => !in_array($key, ['exception', 'mechanism', 'stacktrace']), + ARRAY_FILTER_USE_KEY + ) ?: [], + ] + ); + } + /** * Check if we can retrieve customer data. * diff --git a/Plugin/MonologPlugin.php b/Plugin/MonologPlugin.php old mode 100755 new mode 100644 index 9c36b5c..01de5f1 --- a/Plugin/MonologPlugin.php +++ b/Plugin/MonologPlugin.php @@ -2,56 +2,31 @@ namespace JustBetter\Sentry\Plugin; -use JustBetter\Sentry\Helper\Data; -use JustBetter\Sentry\Model\SentryLog; -use Magento\Framework\App\DeploymentConfig; +use JustBetter\Sentry\Logger\Handler\Sentry; use Magento\Framework\Logger\Monolog; -use Monolog\DateTimeImmutable; +use Monolog\LogRecord; +use Monolog\Processor\ProcessorInterface; class MonologPlugin extends Monolog { /** * @psalm-param array $processors * - * @param string $name The logging channel, a simple descriptive name that is attached to all log records - * @param Data $sentryHelper - * @param SentryLog $sentryLog - * @param DeploymentConfig $deploymentConfig - * @param \Monolog\Handler\HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. - * @param callable[] $processors Optional array of processors + * @param string $name The logging channel, a simple descriptive name that is attached to all log records + * @param Sentry $sentryHandler + * @param \Monolog\Handler\HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + * + * @phpstan-param array<(callable(LogRecord|array): LogRecord|array)|ProcessorInterface> $processors */ public function __construct( $name, - protected Data $sentryHelper, - protected SentryLog $sentryLog, - protected DeploymentConfig $deploymentConfig, + Sentry $sentryHandler, array $handlers = [], array $processors = [] ) { - parent::__construct($name, $handlers, $processors); - } + $handlers['sentry'] = $sentryHandler; - /** - * Adds a log record to Sentry. - * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - * @param DateTimeImmutable $datetime Datetime of log - * - * @return bool Whether the record has been processed - */ - public function addRecord( - int $level, - string $message, - array $context = [], - DateTimeImmutable $datetime = null - ): bool { - if ($this->deploymentConfig->isAvailable() && $this->sentryHelper->isActive()) { - $this->sentryLog->send($message, $level, $context); - } - - // @phpstan-ignore argument.type - return parent::addRecord($level, $message, $context, $datetime); + parent::__construct($name, $handlers, $processors); } } diff --git a/composer.json b/composer.json index 0ad7260..60db70a 100755 --- a/composer.json +++ b/composer.json @@ -7,8 +7,9 @@ "require": { "php": ">=8.0", "sentry/sdk": "^4.0", - "monolog/monolog": ">=2.7.0", + "monolog/monolog": ">=2.7.0|^3.0", "magento/framework": "*", + "magento/module-csp": "*", "nyholm/psr7": "^1.2", "magento/module-config": "^101.2" }, @@ -51,7 +52,7 @@ }, "scripts": { "analyse": "vendor/bin/phpstan analyse --memory-limit='1G'", - "phpcs": "vendor/bin/phpcs --colors --standard=vendor/magento/magento-coding-standard/Magento2 --exclude=Generic.Files.LineLength --report=full,summary,gitblame --extensions=php,phtml --ignore=./vendor ./", + "phpcs": "vendor/bin/phpcs --colors --standard=vendor/magento/magento-coding-standard/Magento2 -s --exclude=Generic.Files.LineLength --report=full,summary,gitblame --extensions=php,phtml --ignore=./vendor ./", "phpcbf": "vendor/bin/phpcbf --colors --standard=vendor/magento/magento-coding-standard/Magento2 --exclude=Generic.Files.LineLength --extensions=php,phtml --ignore=./vendor ./ || exit 0", "codestyle": [ "@phpcbf", diff --git a/etc/di.xml b/etc/di.xml index bcb078c..aebae13 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -1,28 +1,6 @@ - - - SentryLog - - Magento\Framework\Logger\Handler\System - - - Magento\Customer\Model\Session\Proxy - - - - - - - JustBetter\Sentry\Helper\Data\Proxy - JustBetter\Sentry\Model\SentryLog\Proxy - Magento\Framework\App\DeploymentConfig\Proxy - - - - - @@ -42,5 +20,4 @@ - diff --git a/phpstan.neon b/phpstan.neon index 3d73f2b..1fdbebf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,4 +6,5 @@ parameters: excludePaths: - vendor - Test/* - level: 5 \ No newline at end of file + - Logger/Handler/Sentry.php + level: 5