diff --git a/.gitignore b/.gitignore index 24df6ff..00f8568 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea composer.lock /vendor +vendor diff --git a/Helper/Data.php b/Helper/Data.php index a3f48ac..28252bb 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -3,7 +3,6 @@ namespace JustBetter\Sentry\Helper; use ErrorException; -use InvalidArgumentException; use JustBetter\Sentry\Block\SentryScript; use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -25,6 +24,35 @@ class Data extends AbstractHelper public const XML_PATH_SRS = 'sentry/general/'; public const XML_PATH_SRS_ISSUE_GROUPING = 'sentry/issue_grouping/'; + public const NATIVE_SENTRY_CONFIG_KEYS = [ + // https://docs.sentry.io/platforms/php/configuration/options/#core-options + 'dsn' => ['type' => 'string'], + 'environment' => ['type' => 'string'], + 'max_breadcrumbs' => ['type' => 'int'], + 'attach_stacktrace' => ['type' => 'bool'], + 'send_default_pii' => ['type' => 'bool'], + 'server_name' => ['type' => 'string'], + 'in_app_include' => ['type' => 'array'], + 'in_app_exclude' => ['type' => 'array'], + 'max_request_body_size' => ['type' => 'string'], + 'max_value_length' => ['type' => 'int'], + // https://docs.sentry.io/platforms/php/configuration/options/#error-monitoring-options + 'sample_rate' => ['type' => 'float'], + 'ignore_exceptions' => ['type' => 'array'], + 'error_types' => ['type' => 'int'], + 'context_lines' => ['type' => 'int'], + // https://docs.sentry.io/platforms/php/configuration/options/#tracing-options + 'traces_sample_rate' => ['type' => 'float'], + 'ignore_transactions' => ['type' => 'array'], + 'trace_propagation_targets' => ['type' => 'array'], + // https://docs.sentry.io/platforms/php/profiling/#enabling-profiling + 'profiles_sample_rate' => ['type' => 'float'], + // https://docs.sentry.io/platforms/php/configuration/options/#transport-options + 'http_proxy' => ['type' => 'string'], + 'http_connect_timeout' => ['type' => 'int'], + 'http_timeout' => ['type' => 'int'], + ]; + /** * @var ScopeConfigInterface */ @@ -39,22 +67,19 @@ class Data extends AbstractHelper * @var array */ protected $configKeys = [ - 'dsn', - 'logrocket_key', - 'log_level', - 'errorexception_reporting', - 'ignore_exceptions', - 'mage_mode_development', - 'environment', - 'js_sdk_version', - 'tracing_enabled', - 'tracing_sample_rate', - 'performance_tracking_enabled', - 'performance_tracking_excluded_areas', - 'profiles_sample_rate', - 'ignore_js_errors', - 'disable_default_integrations', - 'clean_stacktrace', + ...self::NATIVE_SENTRY_CONFIG_KEYS, + 'logrocket_key' => ['type' => 'array'], + 'log_level' => ['type' => 'int'], + 'errorexception_reporting' => ['type' => 'int'], /* @deprecated by @see: error_types https://docs.sentry.io/platforms/php/configuration/options/#error_types */ + 'mage_mode_development' => ['type' => 'bool'], + 'js_sdk_version' => ['type' => 'string'], + 'tracing_enabled' => ['type' => 'bool'], + 'tracing_sample_rate' => ['type' => 'float'], /* @deprecated by @see: traces_sample_rate https://docs.sentry.io/platforms/php/configuration/options/#error_types */ + 'performance_tracking_enabled' => ['type' => 'bool'], + 'performance_tracking_excluded_areas' => ['type' => 'array'], + 'ignore_js_errors' => ['type' => 'array'], + 'disable_default_integrations' => ['type' => 'array'], + 'clean_stacktrace' => ['type' => 'bool'], ]; /** @@ -91,16 +116,6 @@ public function getDSN() return $this->collectModuleConfig()['dsn']; } - /** - * Get sample rate for php profiling. - * - * @return float - */ - public function getPhpProfileSampleRate(): float - { - return (float) ($this->collectModuleConfig()['profiles_sample_rate'] ?? 0); - } - /** * Whether tracing is enabled. */ @@ -114,7 +129,7 @@ public function isTracingEnabled(): bool */ public function getTracingSampleRate(): float { - return (float) ($this->collectModuleConfig()['tracing_sample_rate'] ?? 0.2); + return (float) ($this->collectModuleConfig()['traces_sample_rate'] ?? $this->collectModuleConfig()['tracing_sample_rate'] ?? 0.2); } /** @@ -132,25 +147,7 @@ public function getDisabledDefaultIntegrations(): array */ public function getIgnoreJsErrors() { - $list = $this->collectModuleConfig()['ignore_js_errors']; - - if ($list === null) { - return null; - } - - try { - $config = $this->collectModuleConfig(); - $list = is_array($config['ignore_js_errors']) - ? $config['ignore_js_errors'] - : $this->serializer->unserialize($config['ignore_js_errors']); - } catch (InvalidArgumentException $e) { - throw new RuntimeException( - __('Sentry configuration error: `ignore_js_errors` has to be an array or `null`. Given type: %s', gettype($list)), // phpcs:ignore - $e - ); - } - - return $list; + return $this->collectModuleConfig()['ignore_js_errors']; } /** @@ -229,13 +226,16 @@ public function collectModuleConfig(): array $this->config[$storeId]['enabled'] = $this->scopeConfig->getValue('sentry/environment/enabled', ScopeInterface::SCOPE_STORE) ?? $this->deploymentConfig->get('sentry') !== null; } catch (TableNotFoundException|FileSystemException|RuntimeException $e) { - $this->config[$storeId]['enabled'] = null; + $this->config[$storeId]['enabled'] = $this->deploymentConfig->get('sentry') !== null; } - foreach ($this->configKeys as $value) { + foreach ($this->configKeys as $value => $config) { try { - $this->config[$storeId][$value] = $this->scopeConfig->getValue('sentry/environment/'.$value, ScopeInterface::SCOPE_STORE) - ?? $this->deploymentConfig->get('sentry/'.$value); + $this->config[$storeId][$value] = $this->processConfigValue( + $this->scopeConfig->getValue('sentry/environment/'.$value, ScopeInterface::SCOPE_STORE) + ?? $this->deploymentConfig->get('sentry/'.$value), + $config + ); } catch (TableNotFoundException|FileSystemException|RuntimeException $e) { $this->config[$storeId][$value] = null; } @@ -244,6 +244,30 @@ public function collectModuleConfig(): array return $this->config[$storeId]; } + /** + * Parse the config value to the type defined in the config. + * + * @param mixed $value + * @param array $config + * + * @return mixed + */ + public function processConfigValue(mixed $value, array $config): mixed + { + if ($value === null) { + return null; + } + + return match ($config['type']) { + 'array' => is_array($value) ? $value : $this->serializer->unserialize($value), + 'int' => (int) $value, + 'float' => (float) $value, + 'bool' => (bool) $value, + 'string' => (string) $value, + default => $value, + }; + } + /** * Whether Sentry is active. * @@ -510,9 +534,9 @@ public function stripStoreCode(): bool * * @return int */ - public function getErrorExceptionReporting(): int + public function getErrorTypes(): int { - return (int) ($this->collectModuleConfig()['errorexception_reporting'] ?? error_reporting()); + return (int) ($this->collectModuleConfig()['error_types'] ?? $this->collectModuleConfig()['errorexception_reporting'] ?? error_reporting()); } /** @@ -522,16 +546,7 @@ public function getErrorExceptionReporting(): int */ public function getIgnoreExceptions(): array { - $config = $this->collectModuleConfig(); - if (is_array($config['ignore_exceptions'])) { - return $config['ignore_exceptions']; - } - - try { - return $this->serializer->unserialize($config['ignore_exceptions']); - } catch (InvalidArgumentException $e) { - return []; - } + return $this->collectModuleConfig()['ignore_exceptions'] ?? []; } /** @@ -543,7 +558,7 @@ public function getIgnoreExceptions(): array */ public function shouldCaptureException(Throwable $ex): bool { - if ($ex instanceof ErrorException && !($ex->getSeverity() & $this->getErrorExceptionReporting())) { + if ($ex instanceof ErrorException && !($ex->getSeverity() & $this->getErrorTypes())) { return false; } diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index eaaa87d..6bc787d 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -51,8 +51,32 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) return $proceed(); } + $config = $this->prepareConfig(); + + $this->sentryInteraction->initialize(array_filter($config->getData())); + $this->sentryPerformance->startTransaction($subject); + + try { + return $response = $proceed(); + } catch (Throwable $exception) { + $this->sentryInteraction->captureException($exception); + + throw $exception; + } finally { + $this->sentryPerformance->finishTransaction($response ?? 500); + } + } + + /** + * Prepare all the config passed to sentry. + * + * @return DataObject + */ + public function prepareConfig(): DataObject + { /** @var DataObject $config */ $config = $this->dataObjectFactory->create(); + $config->setData(array_intersect_key($this->sentryHelper->collectModuleConfig(), SentryHelper::NATIVE_SENTRY_CONFIG_KEYS)); $config->setDsn($this->sentryHelper->getDSN()); if ($release = $this->releaseIdentifier->getReleaseId()) { @@ -63,6 +87,36 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) $config->setEnvironment($environment); } + $config->setBeforeBreadcrumb(function (\Sentry\Breadcrumb $breadcrumb): ?\Sentry\Breadcrumb { + $data = $this->dataObjectFactory->create(); + $data->setBreadcrumb($breadcrumb); + $this->eventManager->dispatch('sentry_before_breadcrumb', [ + 'sentry_breadcrumb' => $data, + ]); + + return $data->getBreadcrumb(); + }); + + $config->setBeforeSendTransaction(function (\Sentry\Event $transaction): ?\Sentry\Event { + $data = $this->dataObjectFactory->create(); + $data->setTransaction($transaction); + $this->eventManager->dispatch('sentry_before_send_transaction', [ + 'sentry_transaction' => $data, + ]); + + return $data->getTransaction(); + }); + + $config->setBeforeSendCheckIn(function (\Sentry\Event $checkIn): ?\Sentry\Event { + $data = $this->dataObjectFactory->create(); + $data->setCheckIn($checkIn); + $this->eventManager->dispatch('sentry_before_send_check_in', [ + 'sentry_check_in' => $data, + ]); + + return $data->getCheckIn(); + }); + $config->setBeforeSend(function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event { $data = $this->dataObjectFactory->create(); $data->setEvent($event); @@ -80,32 +134,18 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) static fn (IntegrationInterface $integration) => !in_array(get_class($integration), $disabledDefaultIntegrations) )); - $config->setIgnoreExceptions($this->sentryHelper->getIgnoreExceptions()); - $config->setErrorTypes($this->sentryHelper->getErrorExceptionReporting()); + $config->setErrorTypes($this->sentryHelper->getErrorTypes()); if ($this->sentryHelper->isPerformanceTrackingEnabled()) { $config->setTracesSampleRate($this->sentryHelper->getTracingSampleRate()); - } - - if ($rate = $this->sentryHelper->getPhpProfileSampleRate()) { - $config->setData('profiles_sample_rate', $rate); + } else { + $config->unsetTracesSampleRate(null); } $this->eventManager->dispatch('sentry_before_init', [ 'config' => $config, ]); - $this->sentryInteraction->initialize($config->getData()); - $this->sentryPerformance->startTransaction($subject); - - try { - return $response = $proceed(); - } catch (Throwable $exception) { - $this->sentryInteraction->captureException($exception); - - throw $exception; - } finally { - $this->sentryPerformance->finishTransaction($response ?? 500); - } + return $config; } } diff --git a/README.md b/README.md index 9c4bc69..f3f90f1 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ This module uses the [Magento Deployment Configuration](https://devdocs.magento. 'logrocket_key' => 'example/example', 'environment' => null, 'log_level' => \Monolog\Logger::WARNING, - 'errorexception_reporting' => E_ALL, + 'error_types' => E_ALL, 'ignore_exceptions' => [], 'mage_mode_development' => false, 'js_sdk_version' => \JustBetter\Sentry\Block\SentryScript::CURRENT_VERSION, 'tracing_enabled' => true, - 'tracing_sample_rate' => 0.5, + 'traces_sample_rate' => 0.5, 'disable_default_integrations' => [ \Sentry\Integration\ModulesIntegration::class, ] @@ -64,13 +64,13 @@ Next to that there are some configuration options under Stores > Configuration > | `dsn` | — | The DSN you got from Sentry for your project. You can find the DSN in the project settings under "Client Key (DSN)" | | `environment` | — | Specify the environment under which the deployed version is running. Common values: production, staging, development. Helps differentiate errors between environments. | | `log_level` | `\Monolog\Logger::WARNING` | Specify from which logging level on Sentry should get the messages. | -| `errorexception_reporting` | `E_ALL` | If the Exception is an instance of [ErrorException](https://www.php.net/manual/en/class.errorexception.php), send the error to Sentry if it matches the error reporting. Uses the same syntax as [Error Reporting](https://www.php.net/manual/en/function.error-reporting.php), e.g., `E_ERROR` | E_WARNING`. | +| `error_types` | `E_ALL` | If the Exception is an instance of [ErrorException](https://www.php.net/manual/en/class.errorexception.php), send the error to Sentry if it matches the error reporting. Uses the same syntax as [Error Reporting](https://www.php.net/manual/en/function.error-reporting.php), e.g., `E_ERROR` | E_WARNING`. | | `ignore_exceptions` | `[]` | If the class being thrown matches any in this list, do not send it to Sentry, e.g., `[\Magento\Framework\Exception\NoSuchEntityException::class]` | | `clean_stacktrace` | `true` | Whether unnecessary files (like Interceptor.php, Proxy.php, and Factory.php) should be removed from the stacktrace. (They will not be removed if they threw the error.) | | `mage_mode_development` | `false` | If set to true, you will receive issues in Sentry even if Magento is running in develop mode. | | `js_sdk_version` | `\JustBetter\Sentry\Block\SentryScript::CURRENT_VERSION` | If set, loads the explicit version of the JavaScript SDK of Sentry. | | `tracing_enabled` | `false` | If set to true, tracing is enabled (bundle file is loaded automatically). | -| `tracing_sample_rate` | `0.2` | If tracing is enabled, set the sample rate. | +| `traces_sample_rate` | `0.2` | If tracing is enabled, set the sample rate. | | `performance_tracking_enabled` | `false` | if performance tracking is enabled, a performance report got generated for the request. | | `performance_tracking_excluded_areas` | `['adminhtml', 'crontab']` | if `performance_tracking_enabled` is enabled, we recommend to exclude the `adminhtml` & `crontab` area. | | `profiles_sample_rate` | `0` (disabled) | if this option is larger than 0 (zero), the module will create a profile of the request. Please note that you have to install [Excimer](https://www.mediawiki.org/wiki/Excimer) on your server to use profiling. [Sentry documentation](https://docs.sentry.io/platforms/php/profiling/). You have to enable tracing too. | @@ -88,9 +88,9 @@ using the "Variables" in Adobe Commerce using the following variables: | `CONFIG__SENTRY__ENVIRONMENT__LOGROCKET_KEY` | string | | `CONFIG__SENTRY__ENVIRONMENT__ENVIRONMENT` | string | | `CONFIG__SENTRY__ENVIRONMENT__LOG_LEVEL` | integer | -| `CONFIG__SENTRY__ENVIRONMENT__ERROREXCEPTION_REPORTING` | integer | +| `CONFIG__SENTRY__ENVIRONMENT__ERROR_TYPES` | integer | | `CONFIG__SENTRY__ENVIRONMENT__IGNORE_EXCEPTIONS` | JSON array of classes | -| `CONFIG__SENTRY__ENVIRONMENT__CLEAN_STACKTRACE` | boolean | +| `CONFIG__SENTRY__ENVIRONMENT__CLEAN_STACKTRACE` | boolean | | `CONFIG__SENTRY__ENVIRONMENT__MAGE_MODE_DEVELOPMENT` | string | | `CONFIG__SENTRY__ENVIRONMENT__JS_SDK_VERSION` | string | | `CONFIG__SENTRY__ENVIRONMENT__TRACING_ENABLED` | boolean | @@ -122,6 +122,12 @@ public function execute(\Magento\Framework\Event\Observer $observer) Example: https://github.com/justbetter/magento2-sentry-filter-events +This same thing is the case for +| sentry_before_send | https://docs.sentry.io/platforms/php/configuration/options/#before_send | +| sentry_before_send_transaction | https://docs.sentry.io/platforms/php/configuration/options/#before_send_transaction | +| sentry_before_send_check_in | https://docs.sentry.io/platforms/php/configuration/options/#before_send_check_in | +| sentry_before_breadcrumb | https://docs.sentry.io/platforms/php/configuration/options/#before_breadcrumb | + ## Compatibility The module is tested on Magento version 2.4.x with sentry sdk version 3.x. feel free to fork this project or make a pull request. diff --git a/composer.json b/composer.json index b299e15..71633f3 100755 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "Performance" ], "type": "magento2-module", - "version": "4.1.0", "license": "MIT", "require": { "php": ">=8.0", diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 4bc574d..e2ec49d 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -125,7 +125,7 @@ 1 - + validate-not-negative-number validate-number diff --git a/etc/config.xml b/etc/config.xml index 088648c..c620671 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -19,7 +19,7 @@ - + diff --git a/view/adminhtml/templates/system/config/deployment-config-info.phtml b/view/adminhtml/templates/system/config/deployment-config-info.phtml index ddf53e8..411be22 100644 --- a/view/adminhtml/templates/system/config/deployment-config-info.phtml +++ b/view/adminhtml/templates/system/config/deployment-config-info.phtml @@ -21,7 +21,7 @@ 'logrocket_key' => 'example/example', 'environment' => null, 'log_level' => \Monolog\Logger::WARNING, - 'errorexception_reporting' => E_ALL, + 'error_types' => E_ALL, 'ignore_exceptions' => [], 'mage_mode_development' => false, ]"); ?>