├── .gitignore
├── view
├── adminhtml
│ ├── requirejs-config.js
│ ├── templates
│ │ └── system
│ │ │ └── config
│ │ │ ├── button.phtml
│ │ │ └── deployment-config-info.phtml
│ └── web
│ │ └── js
│ │ └── testsentry.js
└── frontend
│ ├── layout
│ └── default.xml
│ └── templates
│ └── script
│ ├── logrocket_init.phtml
│ ├── sentry.phtml
│ └── sentry_init.phtml
├── .github
├── SECURITY.md
├── PULL_REQUEST_TEMPLATE.md
├── workflows
│ ├── analyse.yml
│ └── phpcs.yml
├── issue_template.md
├── ISSUE_TEMPLATE
│ ├── feature.yml
│ └── bug.yml
└── CONTRIBUTING.md
├── registration.php
├── etc
├── module.xml
├── adminhtml
│ ├── routes.xml
│ └── system.xml
├── events.xml
├── frontend
│ └── di.xml
├── acl.xml
├── config.xml
└── di.xml
├── phpstan.neon
├── i18n
└── en_US.csv
├── Model
├── Config
│ └── Source
│ │ ├── ScriptTagPlacement.php
│ │ └── LogLevel.php
├── ReleaseIdentifier.php
├── PerformanceTracingDto.php
├── Collector
│ └── SentryRelatedCspCollector.php
├── SentryCron.php
├── SentryLog.php
├── SentryInteraction.php
└── SentryPerformance.php
├── Plugin
├── SampleRequest.php
├── CronScheduleCheckIn.php
├── LogrocketCustomerInfo.php
├── MonologPlugin.php
├── Profiling
│ ├── TemplatePlugin.php
│ ├── DbQueryLoggerPlugin.php
│ ├── EventManagerPlugin.php
│ ├── ExchangePlugin.php
│ ├── Cache.php
│ └── QueuePlugin.php
├── CspModeConfigManagerPlugin.php
└── GlobalExceptionCatcher.php
├── Cache
└── Frontend
│ └── Factory.php
├── LICENSE
├── Block
├── Adminhtml
│ └── System
│ │ └── Config
│ │ ├── Button.php
│ │ └── DeploymentConfigInfo.php
└── SentryScript.php
├── Observer
└── StripUnnecessaryFrames.php
├── composer.json
├── Controller
└── Adminhtml
│ └── Test
│ └── Sentry.php
├── Logger
└── Handler
│ └── Sentry.php
├── Helper
├── Version.php
└── Data.php
├── Test
└── Unit
│ └── Plugin
│ └── GlobalExceptionCatcherTest.php
├── README.md
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | composer.lock
4 | /vendor
5 | vendor
6 |
--------------------------------------------------------------------------------
/view/adminhtml/requirejs-config.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | paths: {
3 | 'justbetter/testsentry': 'JustBetter_Sentry/js/testsentry'
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | If you discover any security related issues, please email security@justbetter.nl instead of using the issue tracker.
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/etc/adminhtml/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/etc/events.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/bitexpert/phpstan-magento/extension.neon
3 | parameters:
4 | paths:
5 | - .
6 | excludePaths:
7 | - vendor
8 | - Test/*
9 | - Logger/Handler/Sentry.php
10 | level: 6
11 | ignoreErrors:
12 | -
13 | identifier: missingType.iterableValue
14 | treatPhpDocTypesAsCertain: false
15 |
--------------------------------------------------------------------------------
/etc/frontend/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/i18n/en_US.csv:
--------------------------------------------------------------------------------
1 | How to configure Sentry?,How to configure Sentry?
2 | "This module uses the Magento Deployment Configuration for most it's configuration. This means that you need to add this array to your `app/etc/env.php`:","This module uses the Magento Deployment Configuration for most it's configuration. This means that you need to add this array to your `app/etc/env.php`:"
3 |
--------------------------------------------------------------------------------
/Model/Config/Source/ScriptTagPlacement.php:
--------------------------------------------------------------------------------
1 | 'head.additional', 'label' => __('head.additional')],
18 | ['value' => 'before.body.end', 'label' => __('before.body.end')],
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Model/ReleaseIdentifier.php:
--------------------------------------------------------------------------------
1 | version->getValue();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | **Summary**
7 |
8 |
13 |
14 | **Result**
15 |
16 |
21 |
22 | **Checklist**
23 |
24 | - [ ] I've ran `composer run codestyle`
25 | - [ ] I've ran `composer run analyse`
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/view/frontend/layout/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Model/Config/Source/LogLevel.php:
--------------------------------------------------------------------------------
1 | Logger::NOTICE, 'label' => __('Notice')],
19 | ['value' => Logger::WARNING, 'label' => __('Warning')],
20 | ['value' => Logger::CRITICAL, 'label' => __('Critical')],
21 | ['value' => Logger::ALERT, 'label' => __('Alert')],
22 | ['value' => Logger::ALERT, 'label' => __('Emergency')],
23 | ];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/system/config/button.phtml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
24 |
--------------------------------------------------------------------------------
/Plugin/SampleRequest.php:
--------------------------------------------------------------------------------
1 | sentryPerformance = $sentryPerformance;
22 | }
23 |
24 | /**
25 | * Add our toolbar to the response.
26 | *
27 | * @param ResponseInterface $response
28 | */
29 | public function beforeSendResponse(ResponseInterface $response): void
30 | {
31 | $this->sentryPerformance->finishTransaction($response);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/analyse.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | php: [8.2, 8.3]
16 | stability: [prefer-lowest, prefer-stable]
17 |
18 | name: PHPStan - P${{ matrix.php }} - ${{ matrix.stability }}
19 |
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v2
23 |
24 | - name: Setup PHP
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ matrix.php }}
28 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
29 | coverage: none
30 |
31 | - name: Install dependencies
32 | run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
33 |
34 | - name: Analyse
35 | run: composer run analyse
36 |
--------------------------------------------------------------------------------
/.github/workflows/phpcs.yml:
--------------------------------------------------------------------------------
1 | name: Code Style
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | php: [8.2, 8.3]
16 | stability: [prefer-lowest, prefer-stable]
17 |
18 | name: PHPCS - P${{ matrix.php }} - ${{ matrix.stability }}
19 |
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v2
23 |
24 | - name: Setup PHP
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ matrix.php }}
28 | extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
29 | coverage: none
30 |
31 | - name: Install dependencies
32 | run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
33 |
34 | - name: Analyse
35 | run: composer run phpcs
36 |
--------------------------------------------------------------------------------
/view/adminhtml/web/js/testsentry.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jquery",
3 | "jquery/ui",
4 | "mage/translate",
5 | "Magento_Ui/js/modal/alert"
6 | ], function ($, validation, $t, alert) {
7 | "use strict";
8 |
9 | $.widget('justbetter.testSentry', {
10 | options: {
11 | ajaxUrl: '',
12 | testSentry: '#sentry_general_sent',
13 | domainSentry: '#sentry_general_domain'
14 | },
15 | _create: function () {
16 | var self = this;
17 | self.element.addClass('required-entry');
18 |
19 | self.element.click(function (e) {
20 | e.preventDefault();
21 | self._ajaxSubmit();
22 | });
23 | },
24 |
25 | _ajaxSubmit: function () {
26 | $.get({
27 | url: this.options.ajaxUrl,
28 | dataType: 'json',
29 | showLoader: true,
30 | success: function (result) {
31 | alert({
32 | title: result.status ? $t('Success') : $t('Error'),
33 | content: result.content ? result.content : result.message
34 | });
35 | }
36 | });
37 | }
38 | });
39 |
40 | return $.justbetter.testSentry;
41 | });
42 |
--------------------------------------------------------------------------------
/Plugin/CronScheduleCheckIn.php:
--------------------------------------------------------------------------------
1 | sentryCron->sendScheduleStatus($subject);
38 |
39 | return $result;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Cache/Frontend/Factory.php:
--------------------------------------------------------------------------------
1 | \JustBetter\Sentry\Plugin\Profiling\Cache::class,
27 | ];
28 |
29 | parent::__construct($objectManager, $filesystem, $resource, $enforcedOptions, $decorators);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 JustBetter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Model/PerformanceTracingDto.php:
--------------------------------------------------------------------------------
1 | scope;
34 | }
35 |
36 | /**
37 | * Get parent span.
38 | *
39 | * @return Span|null
40 | */
41 | public function getParentSpan(): ?Span
42 | {
43 | return $this->parentSpan;
44 | }
45 |
46 | /**
47 | * Get span.
48 | *
49 | * @return Span|null
50 | */
51 | public function getSpan(): ?Span
52 | {
53 | return $this->span;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/system/config/deployment-config-info.phtml:
--------------------------------------------------------------------------------
1 |
7 | |
8 |
9 | = $escaper->escapeHtml(__('How to configure Sentry?')); ?>
10 | = $escaper->escapeHtml(__('This module uses the')); ?>
11 |
12 | = $escaper->escapeHtml(__('Magento Deployment Configuration')); ?>
13 |
14 | = $escaper->escapeHtml(__('for most it\'s configuration. This means that you need to add this array to your `app/etc/env.php`:')); ?>
15 |
16 |
17 |
18 | = /* @noEscape */ htmlentities("
19 | 'sentry' => [
20 | 'dsn' => 'example.com',
21 | 'logrocket_key' => 'example/example',
22 | 'environment' => null,
23 | 'log_level' => \Monolog\Level::Warning,
24 | 'error_types' => E_ALL,
25 | 'ignore_exceptions' => [],
26 | 'mage_mode_development' => false,
27 | ]"); ?>
28 |
29 |
30 | = $escaper->escapeHtml(__("Release ID: %1", $block->getVersion())); ?>
31 | |
32 |
--------------------------------------------------------------------------------
/Plugin/LogrocketCustomerInfo.php:
--------------------------------------------------------------------------------
1 | customerSession->isLoggedIn()) {
34 | return $result;
35 | }
36 |
37 | $customer = $this->currentCustomer->getCustomer();
38 |
39 | $result['email'] = $customer->getEmail();
40 | $result['fullname'] = $customer->getFirstname().' '.$customer->getLastname();
41 |
42 | return $result;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 1
7 | 0
8 | 0
9 | before.body.end
10 | 0
11 | 0
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Button.php:
--------------------------------------------------------------------------------
1 | unsScope();
25 |
26 | return parent::render($element);
27 | }
28 |
29 | /**
30 | * Get the button and scripts contents.
31 | *
32 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
33 | *
34 | * @return string
35 | */
36 | protected function _getElementHtml(AbstractElement $element)
37 | {
38 | $originalData = $element->getOriginalData();
39 | $this->addData(
40 | [
41 | 'button_label' => $originalData['button_label'],
42 | 'button_url' => $this->getUrl($originalData['button_url']),
43 | 'html_id' => $element->getHtmlId(),
44 | ]
45 | );
46 |
47 | return $this->_toHtml();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/DeploymentConfigInfo.php:
--------------------------------------------------------------------------------
1 | _toHtml();
42 | }
43 |
44 | /**
45 | * Get static version.
46 | *
47 | * @return string
48 | */
49 | public function getVersion()
50 | {
51 | return $this->version->getValue();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Plugin/MonologPlugin.php:
--------------------------------------------------------------------------------
1 | containsHandler($handlers)) {
31 | array_unshift($handlers, $this->sentryHandler);
32 | }
33 |
34 | return [$handlers];
35 | }
36 |
37 | /**
38 | * Check if the Sentry handler is already in the list of handlers.
39 | *
40 | * @param array $handlers
41 | *
42 | * @return bool
43 | */
44 | public function containsHandler(array $handlers): bool
45 | {
46 | foreach ($handlers as $handler) {
47 | if ($handler instanceof Sentry) {
48 | return true;
49 | }
50 | }
51 |
52 | return false;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | **Do you want to request a *feature* or report a *bug*?**
15 |
16 |
24 |
25 | **Bug: What is the current behavior?**
26 |
27 | **Bug: What is the expected behavior?**
28 |
29 | **Bug: What is the proposed solution?**
30 |
31 | **Feature: What is your use case for such a feature?**
32 |
33 | **Feature: What is your proposed configuration entry? The new option to add? What is the behavior?**
34 |
35 | **What is the version of Magento and of Sentry extension you are using? Always use the latest version of the extension one before opening a bug issue.**
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Plugin/Profiling/TemplatePlugin.php:
--------------------------------------------------------------------------------
1 | getModuleName())) {
26 | $tags['magento.module'] = $subject->getModuleName();
27 | }
28 |
29 | $context = SpanContext::make()
30 | ->setOp('template.render')
31 | ->setDescription($subject->getNameInLayout() ?: $fileName)
32 | ->setTags($tags)
33 | ->setData([
34 | 'block_name' => $subject->getNameInLayout(),
35 | 'block_class' => get_class($subject),
36 | 'module' => $subject->getModuleName(),
37 | 'template' => $fileName,
38 | ])
39 | ->setOrigin('auto.template');
40 |
41 | $tracingDto = SentryPerformance::traceStart($context);
42 |
43 | try {
44 | return $callable($fileName);
45 | } finally {
46 | SentryPerformance::traceEnd($tracingDto);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Plugin/Profiling/DbQueryLoggerPlugin.php:
--------------------------------------------------------------------------------
1 | tracingDto = SentryPerformance::traceStart(
29 | SpanContext::make()
30 | ->setOp('db.sql.query')
31 | ->setStartTimestamp(microtime(true))
32 | ->setOrigin('auto.db')
33 | );
34 | }
35 |
36 | /**
37 | * Stops the previously create span (span created in `beforeStartTimer`).
38 | *
39 | * @param LoggerInterface $subject
40 | * @param string $type
41 | * @param string $sql
42 | * @param array $bind
43 | * @param mixed $result
44 | *
45 | * @return void
46 | */
47 | public function beforeLogStats(LoggerInterface $subject, $type, $sql, $bind = [], $result = null): void
48 | {
49 | if ($this->tracingDto === null) {
50 | return;
51 | }
52 |
53 | $this->tracingDto->getSpan()?->setDescription($sql);
54 | SentryPerformance::traceEnd($this->tracingDto);
55 | $this->tracingDto = null;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Create a feature request.
3 | title: "[⭐]: "
4 | labels: ["enhancement"]
5 | type: feature
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Thanks for participating in this project!
11 | Please fill out the following sections to help us understand what you want.
12 |
13 | In any case,
14 | - make sure you are using the latest version of the extension;
15 | - do at least one search in current issues or feature requests, there might be a similar request already where you could help out;
16 | - do include as many details as possible;
17 | - type: textarea
18 | id: requested-feature
19 | attributes:
20 | label: What feature would you like to see added?
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: current-behavior
25 | attributes:
26 | label: If it is related to current behavior, what is the current behavior?
27 | - type: textarea
28 | id: additional-information
29 | attributes:
30 | label: do you have any additional information for us?
31 | - type: checkboxes
32 | attributes:
33 | label: Is there an existing Feature Request/PR for this?
34 | description: Please search to see if a feature request or PR already exists for the feature you would like.
35 | options:
36 | - label: I have searched the existing issues and pull requests
37 | required: true
38 | - type: checkboxes
39 | attributes:
40 | label: Are you willing to create a pull request for this?
41 | description: Thank you so much for your willingness to help us out! We really appreciate it.
42 | options:
43 | - label: I would be willing to create a pull request for this feature
44 |
--------------------------------------------------------------------------------
/view/frontend/templates/script/logrocket_init.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | useLogRocketIdentify()): ?>
5 |
6 | define('customerData',
7 | ['jquery', 'Magento_Customer/js/customer-data'],
8 | function ($, customerData) {
9 | 'use strict';
10 |
11 | var getCustomerInfo = function () {
12 | var customer = customerData.get('customer');
13 |
14 | return customer();
15 | };
16 |
17 | var isLoggedIn = function (customerInfo) {
18 | return customerInfo && customerInfo.firstname;
19 | };
20 |
21 | return function () {
22 | var deferred = $.Deferred();
23 | var customerInfo = getCustomerInfo();
24 |
25 | if (customerInfo && customerInfo.data_id) {
26 | deferred.resolve(isLoggedIn(customerInfo), customerInfo);
27 | } else {
28 | customerData.reload(['customer'], false)
29 | .done(function () {
30 | customerInfo = getCustomerInfo()
31 | deferred.resolve(isLoggedIn(customerInfo), customerInfo);
32 | })
33 | .fail(function () {
34 | deferred.reject();
35 | });
36 | }
37 |
38 | return deferred;
39 | };
40 |
41 | }
42 | );
43 |
44 | require(["customerData"], function (customerData) {
45 |
46 | customerData().then(function (loggedIn, data) {
47 | if (!loggedIn) {
48 | return;
49 | }
50 |
51 | LogRocket.identify(data.websiteId, {
52 | name: data.fullname,
53 | email: data.email
54 | });
55 |
56 | });
57 | });
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Model/Collector/SentryRelatedCspCollector.php:
--------------------------------------------------------------------------------
1 | dataHelper->isActive()) {
27 | return $policies;
28 | }
29 |
30 | if ($this->dataHelper->useScriptTag()) {
31 | $policies[] = new FetchPolicy(
32 | 'script-src',
33 | false,
34 | ['https://browser.sentry-cdn.com']
35 | );
36 |
37 | $dsn = $this->dataHelper->getDsn();
38 | $dsnHost = is_string($dsn) ? UriFactory::factory($dsn)->getHost() : null;
39 | if (!empty($dsnHost)) {
40 | $policies[] = new FetchPolicy(
41 | 'connect-src',
42 | false,
43 | [$dsnHost]
44 | );
45 | }
46 | }
47 |
48 | if ($this->dataHelper->isSpotlightEnabled()) {
49 | $policies[] = new FetchPolicy(
50 | 'script-src',
51 | false,
52 | ['https://unpkg.com/@spotlightjs/']
53 | );
54 | }
55 |
56 | if ($this->dataHelper->useLogrocket()) {
57 | $policies[] = new FetchPolicy(
58 | 'script-src',
59 | false,
60 | ['https://cdn.lr-ingest.io']
61 | );
62 | }
63 |
64 | return $policies;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report.
3 | title: "[🐛]: "
4 | labels: ["bug"]
5 | type: bug
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Thanks for participating in this project!
11 | Please fill out the following sections to help us understand the issue you're experiencing.
12 |
13 | In any case,
14 | - make sure you are using the latest version of the extension;
15 | - do at least one search in current issues or questions, your question might already be answered;
16 | - do include as many details as possible - site URL with the issue, screenshots when it's a visual issue, console errors, ...;
17 | - type: textarea
18 | id: current-behavior
19 | attributes:
20 | label: What is the current behavior?
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: expected-behavior
25 | attributes:
26 | label: What is the expected behavior?
27 | validations:
28 | required: true
29 | - type: textarea
30 | id: steps-to-reproduce
31 | attributes:
32 | label: How can we reproduce the issue?
33 | - type: textarea
34 | id: proposed-solution
35 | attributes:
36 | label: What is the proposed solution?
37 | - type: textarea
38 | id: additional-information
39 | attributes:
40 | label: do you have any additional information for us?
41 | description: Please include any additional information that may be helpful in resolving the issue. Like logs, screenshots, or any other relevant details.
42 | - type: checkboxes
43 | attributes:
44 | label: Is there an existing issue for this?
45 | description: Please search to see if an issue already exists for the bug you encountered.
46 | options:
47 | - label: I have searched the existing issues
48 | required: true
49 | - type: checkboxes
50 | attributes:
51 | label: Are you willing to create a pull request for this?
52 | description: Thank you so much for your willingness to help us out! We really appreciate it.
53 | options:
54 | - label: If i have a fix i would be willing to create a pull request
--------------------------------------------------------------------------------
/Observer/StripUnnecessaryFrames.php:
--------------------------------------------------------------------------------
1 | sentryHelper->getCleanStacktrace()) {
31 | return;
32 | }
33 |
34 | /** @var Event $event */
35 | $event = $observer->getEvent()->getSentryEvent()->getEvent();
36 |
37 | foreach ($event->getExceptions() as $exception) {
38 | $stacktrace = $exception->getStacktrace();
39 | if (!$stacktrace) {
40 | continue;
41 | }
42 |
43 | $stacktraceLength = count($stacktrace->getFrames()) - 1;
44 | // Get the last frame in the stacktrace and make sure it's never removed.
45 | $lastFrame = $stacktrace->getFrame($stacktraceLength--);
46 | for ($i = $stacktraceLength; $i >= 0; $i--) {
47 | $frame = $stacktrace->getFrame($i);
48 | $file = $frame->getFile();
49 | if ($file === $lastFrame->getFile()) {
50 | // Anything in the file that threw the exception is relevant.
51 | continue;
52 | }
53 |
54 | if ($this->shouldSkipFrame($frame)) {
55 | $stacktrace->removeFrame($i);
56 | }
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * Check if the frame should be skipped.
63 | *
64 | * @param Frame $frame
65 | *
66 | * @return bool
67 | */
68 | public function shouldSkipFrame(Frame $frame): bool
69 | {
70 | $file = $frame->getFile();
71 |
72 | return str_ends_with($file, 'Interceptor.php')
73 | || str_ends_with($file, 'Proxy.php')
74 | || str_ends_with($file, 'Factory.php');
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/view/frontend/templates/script/sentry.phtml:
--------------------------------------------------------------------------------
1 | canUseScriptTag($block->getNameInLayout())) {
6 | return;
7 | }
8 | ?>
9 |
10 | useScriptTag()): ?>
11 | isTracingEnabled()) {
15 | $bundleFile .= '.tracing';
16 | }
17 |
18 | if ($block->useSessionReplay()) {
19 | $bundleFile .= '.replay';
20 | }
21 |
22 | $bundleFile .= '.min.js';
23 |
24 | $remoteFile = sprintf(
25 | 'https://browser.sentry-cdn.com/%s/%s',
26 | $escaper->escapeHtmlAttr($block->getJsSdkVersion()),
27 | $bundleFile
28 | );
29 | ?>
30 | = /* @noEscape */ $secureRenderer->renderTag('script', ['src' => $remoteFile, 'crossorigin' => 'anonymous']) ?>
31 | = /* @noEscape */ $secureRenderer->renderTag('script', [], $block->getLayout()->createBlock(\JustBetter\Sentry\Block\SentryScript::class)
32 | ->setTemplate('JustBetter_Sentry::script/sentry_init.phtml')
33 | ->toHtml(), false); ?>
34 |
35 | useLogRocket()): ?>
36 | = /* @noEscape */ $secureRenderer->renderTag('script', ['src' => 'https://cdn.lr-ingest.io/LogRocket.min.js', 'crossorigin' => 'anonymous']) ?>
37 | = /* @noEscape */ $secureRenderer->renderTag(
38 | 'script',
39 | [],
40 | "window.LogRocket && window.LogRocket.init('" . /* @noEscape */ trim($block->getLogrocketKey()) . "');",
41 | false
42 | ); ?>
43 | = /* @noEscape */ $secureRenderer->renderTag(
44 | 'script',
45 | [],
46 | 'LogRocket.getSessionURL(sessionURL => {
47 | Sentry.configureScope(scope => {
48 | scope.setExtra("sessionURL", sessionURL);
49 | });
50 | });',
51 | false
52 | ); ?>
53 | = /* @noEscape */ $secureRenderer->renderTag('script', [], $block->getLayout()->createBlock(\JustBetter\Sentry\Block\SentryScript::class)
54 | ->setTemplate('JustBetter_Sentry::script/logrocket_init.phtml')
55 | ->toHtml(), false); ?>
56 |
57 |
58 | isSpotlightEnabled()): ?>
59 | = /* @noEscape */ $secureRenderer->renderTag('script', ['src' => 'https://unpkg.com/@spotlightjs/overlay@latest/dist/sentry-spotlight.iife.js', 'crossorigin' => 'anonymous']) ?>
60 |
--------------------------------------------------------------------------------
/view/frontend/templates/script/sentry_init.phtml:
--------------------------------------------------------------------------------
1 |
2 | if (typeof Sentry !== 'undefined') {
3 | Sentry.init({
4 | dsn: '= $escaper->escapeUrl(trim($block->getDSN())) ?>',
5 | release: '= $escaper->escapeHtml(trim($block->getVersion())) ?>',
6 | environment: '= $escaper->escapeHtml(trim($block->getEnvironment())) ?>',
7 | integrations: [
8 | isTracingEnabled()): ?>
9 | Sentry.browserTracingIntegration({
10 | enableInp: true,
11 | }),
12 |
13 | useSessionReplay()): ?>
14 | Sentry.replayIntegration({
15 | blockAllMedia: = $escaper->escapeHtml($block->getReplayBlockMedia() ? 'true' : 'false') ?>,
16 | maskAllText: = $escaper->escapeHtml($block->getReplayMaskText() ? 'true' : 'false') ?>,
17 | })
18 |
19 | ],
20 | isTracingEnabled()): ?>
21 | tracesSampleRate: = $escaper->escapeHtml($block->getTracingSampleRate()) ?>,
22 |
23 | useSessionReplay()): ?>
24 | replaysSessionSampleRate: = $escaper->escapeHtml($block->getReplaySessionSampleRate()) ?>,
25 | replaysOnErrorSampleRate: = $escaper->escapeHtml($block->getReplayErrorSampleRate()) ?>,
26 |
27 | ignoreErrors: = /** @noEscape */ $block->getIgnoreJsErrors() ?>,
28 | stripStaticContentVersion() || $block->stripStoreCode()): ?>
29 | beforeSend: function(event) {
30 | event.exception.values.map(function (value) {
31 | if (value.stacktrace === undefined || ! value.stacktrace) {
32 | return value;
33 | }
34 |
35 | stripStaticContentVersion()): ?>
36 | value.stacktrace.frames.map(function (frame) {
37 | frame.filename = frame.filename.replace(/version[0-9]{10}\//, '');
38 | return frame;
39 | });
40 |
41 |
42 | stripStoreCode()): ?>
43 | value.stacktrace.frames.map(function (frame) {
44 |
45 | frame.filename = frame.filename.replace('/= $escaper->escapeHtml($block->getStoreCode()); ?>/', '/');
46 |
47 | return frame;
48 | });
49 |
50 |
51 | return value;
52 | });
53 | return event;
54 | }
55 |
56 | });
57 | }
58 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "justbetter/magento2-sentry",
3 | "description": "Magento 2 Logger for Sentry",
4 | "keywords": [
5 | "PHP",
6 | "Magento",
7 | "Magento 2",
8 | "javascript",
9 | "Sentry",
10 | "Logger",
11 | "Logging",
12 | "Error",
13 | "Error Reporting",
14 | "Error Tracking",
15 | "Exception",
16 | "Exception Reporting",
17 | "Exception Tracking",
18 | "Session Replay",
19 | "Logrocket",
20 | "Tracing",
21 | "Performance"
22 | ],
23 | "type": "magento2-module",
24 | "license": "MIT",
25 | "require": {
26 | "php": ">=8.0",
27 | "sentry/sentry": "^4.13",
28 | "monolog/monolog": ">=2.7.0|^3.0",
29 | "magento/framework": ">=103.0.7",
30 | "magento/module-csp": "*",
31 | "nyholm/psr7": "^1.2",
32 | "magento/module-config": ">=101.2"
33 | },
34 | "repositories": {
35 | "magento": {
36 | "type": "composer",
37 | "url": "https://repo-magento-mirror.fooman.co.nz/"
38 | }
39 | },
40 | "authors": [
41 | {
42 | "name": "Indy Koning",
43 | "email": "indy@justbetter.nl",
44 | "homepage": "https://justbetter.nl",
45 | "role": "Developer"
46 | },
47 | {
48 | "name": "Rakhal Imming",
49 | "email": "rakhal@justbetter.nl",
50 | "homepage": "https://justbetter.nl",
51 | "role": "Developer"
52 | },
53 | {
54 | "name": "Joseph Maxwell",
55 | "email": "joseph@swiftotter.com",
56 | "homepage": "https://swiftotter.com",
57 | "role": "Developer"
58 | }
59 | ],
60 | "autoload": {
61 | "psr-4": { "JustBetter\\Sentry\\": "" },
62 | "files": [ "registration.php" ]
63 | },
64 | "config": {
65 | "allow-plugins": {
66 | "php-http/discovery": true,
67 | "magento/composer-dependency-version-audit-plugin": true,
68 | "dealerdirect/phpcodesniffer-composer-installer": true
69 | }
70 | },
71 | "scripts": {
72 | "analyse": "vendor/bin/phpstan analyse --memory-limit='1G'",
73 | "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 ./",
74 | "phpcbf": "vendor/bin/phpcbf --colors --standard=vendor/magento/magento-coding-standard/Magento2 --exclude=Generic.Files.LineLength --extensions=php,phtml --ignore=./vendor ./ || exit 0",
75 | "codestyle": [
76 | "@phpcbf",
77 | "@phpcs"
78 | ]
79 | },
80 | "require-dev": {
81 | "bitexpert/phpstan-magento": "^0.32.0",
82 | "magento/magento-coding-standard": "^34"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Plugin/CspModeConfigManagerPlugin.php:
--------------------------------------------------------------------------------
1 | getReportUri() !== null || !$this->config->isActive() || !$this->config->isEnableCspReportUrl()) {
39 | return $result;
40 | }
41 |
42 | $dsn = $this->config->getDSN();
43 | if (!is_string($dsn) || empty($dsn)) {
44 | return $result;
45 | }
46 |
47 | // DSN
48 | // https://@.ingest..sentry.io/
49 | // https://@example.com/
50 |
51 | // security-url
52 | // https://.ingest..sentry.io/api//security/?sentry_key=
53 | // https://example.com/api//security/?sentry_key=
54 |
55 | $uriParsed = UriFactory::factory($dsn);
56 |
57 | $dsnPaths = explode('/', $uriParsed->getPath()); // the last one is the project-id
58 | $reportUri = sprintf('https://%s/api/%s/security', $uriParsed->getHost(), $dsnPaths[count($dsnPaths) - 1]);
59 |
60 | $params = [
61 | 'sentry_key' => $uriParsed->getUserInfo(),
62 | 'sentry_release' => $this->version->getValue(),
63 | 'sentry_environment' => $this->config->getEnvironment(),
64 | ];
65 |
66 | $params = array_filter($params);
67 | if ($params !== []) {
68 | $reportUri .= '&'.http_build_query($params);
69 | }
70 |
71 | return new ModeConfigured($result->isReportOnly(), $reportUri);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Test/Sentry.php:
--------------------------------------------------------------------------------
1 | false];
51 |
52 | $activeWithReason = $this->helperSentry->isActiveWithReason();
53 |
54 | if ($activeWithReason['active']) {
55 | try {
56 | if ($this->helperSentry->isPhpTrackingEnabled()) {
57 | $this->monolog->addRecord(\Monolog\Logger::ALERT, 'TEST message from Magento 2', []);
58 | $result['status'] = true;
59 | $result['content'] = __('Check sentry.io which should hold an alert');
60 | } else {
61 | $result['content'] = __('Php error tracking must be enabled for testing');
62 | }
63 | } catch (\Exception $e) {
64 | $result['content'] = $e->getMessage();
65 | $this->logger->critical($e);
66 | }
67 | } else {
68 | $result['content'] = implode(PHP_EOL, $activeWithReason['reasons']);
69 | }
70 |
71 | /** @var \Magento\Framework\App\Response\Http $response */
72 | $response = $this->getResponse();
73 |
74 | return $response->representJson(
75 | $this->jsonSerializer->serialize($result)
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Plugin/Profiling/EventManagerPlugin.php:
--------------------------------------------------------------------------------
1 | excludePatterns = array_merge([
25 | '^model_load_',
26 | '_load_before$',
27 | '_load_after$',
28 | '_$',
29 | '^view_block_abstract_',
30 | '^core_layout_render_e',
31 | ], $excludePatterns);
32 | }
33 |
34 | /**
35 | * Method checks if the block is excluded for php profiling.
36 | *
37 | * @param string|null $eventName
38 | *
39 | * @return bool
40 | */
41 | public function canTrace(?string $eventName): bool
42 | {
43 | if ($eventName === null) {
44 | return false;
45 | }
46 |
47 | foreach ($this->excludePatterns as $excludePattern) {
48 | if (preg_match('/'.$excludePattern.'/i', $eventName)) {
49 | return false;
50 | }
51 | }
52 |
53 | if ($this->config->getObservers(mb_strtolower($eventName)) === []) {
54 | return false;
55 | }
56 |
57 | return true;
58 | }
59 |
60 | /**
61 | * Method creates a Sentry span for php profiling to profile event handling.
62 | *
63 | * @param ManagerInterface $subject
64 | * @param callable $callable
65 | * @param string $eventName
66 | * @param array $data
67 | *
68 | * @return mixed
69 | */
70 | public function aroundDispatch(ManagerInterface $subject, callable $callable, string $eventName, array $data = []): mixed
71 | {
72 | if (!$this->canTrace($eventName)) {
73 | return $callable($eventName, $data);
74 | }
75 |
76 | $context = SpanContext::make()
77 | ->setOp('event')
78 | ->setDescription($eventName)
79 | ->setData([
80 | 'event.name' => $eventName,
81 | ])
82 | ->setOrigin('auto.event');
83 |
84 | $tracingDto = SentryPerformance::traceStart($context);
85 |
86 | try {
87 | return $callable($eventName, $data);
88 | } finally {
89 | SentryPerformance::traceEnd($tracingDto);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or pull request.
6 |
7 | ## Etiquette
8 |
9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code
10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be
11 | extremely unfair for them to suffer abuse or anger for their hard work.
12 |
13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
14 | world that developers are civilized and selfless people.
15 |
16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
18 |
19 | ## Viability
20 |
21 | When requesting or submitting new features, first consider whether it might be useful to others. Open
22 | source projects are used by many developers, who may have entirely different needs to your own. Think about
23 | whether or not your feature is likely to be used by other users of the project.
24 |
25 | ## Procedure
26 |
27 | Before filing an issue:
28 |
29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
30 | - Check to make sure your feature suggestion isn't already present within the project.
31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
32 | - Check the pull requests tab to ensure that the feature isn't already in progress.
33 |
34 | Before submitting a pull request:
35 |
36 | - Check the codebase to ensure that your feature doesn't already exist.
37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
38 |
39 | ## Requirements
40 |
41 | If the project maintainer has any additional requirements, you will find them listed here.
42 |
43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)**
44 |
45 | To save time on codestyle feedback, please run
46 | - `composer install`
47 | - `composer run codestyle`
48 | - `composer run analyse`
49 |
50 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
51 |
52 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
53 |
54 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
55 |
56 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
57 |
58 | **Happy coding**!
--------------------------------------------------------------------------------
/Logger/Handler/Sentry.php:
--------------------------------------------------------------------------------
1 | deploymentConfig->isAvailable() && $this->sentryHelper->isActive();
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | public function handle(array $record): bool
44 | {
45 | if (!$this->isHandling($record)) {
46 | return false;
47 | }
48 |
49 | $this->sentryLog->send($record['message'], $record['level'], $record['context']);
50 |
51 | return false;
52 | }
53 | }
54 | } else {
55 | class Sentry extends AbstractHandler
56 | {
57 | /**
58 | * Construct.
59 | *
60 | * @param Data $sentryHelper
61 | * @param SentryLog $sentryLog
62 | * @param DeploymentConfig $deploymentConfig
63 | */
64 | public function __construct(
65 | protected Data $sentryHelper,
66 | protected SentryLog $sentryLog,
67 | protected DeploymentConfig $deploymentConfig,
68 | ) {
69 | parent::__construct();
70 | }
71 |
72 | /**
73 | * @inheritDoc
74 | */
75 | public function isHandling(LogRecord $record): bool
76 | {
77 | return $this->deploymentConfig->isAvailable() && $this->sentryHelper->isActive();
78 | }
79 |
80 | /**
81 | * @inheritDoc
82 | */
83 | public function handle(LogRecord $record): bool
84 | {
85 | if (!$this->isHandling($record)) {
86 | return false;
87 | }
88 |
89 | $this->sentryLog->send($record['message'], $record['level'], $record['context']);
90 |
91 | return false;
92 | }
93 | }
94 | }
95 | // phpcs:enable Generic.Classes.DuplicateClassName,PSR2.Classes.ClassDeclaration,PSR1.Classes.ClassDeclaration.MultipleClasses
96 |
--------------------------------------------------------------------------------
/Plugin/Profiling/ExchangePlugin.php:
--------------------------------------------------------------------------------
1 | getSpan();
25 | if ($parentSpan === null) {
26 | return [$topic, $envelopes];
27 | }
28 |
29 | $isMultipleEnvelopes = is_array($envelopes);
30 | $context = \Sentry\Tracing\SpanContext::make()
31 | ->setOp('queue.publish')
32 | ->setDescription($topic);
33 |
34 | $envelopes = array_map(function (EnvelopeInterface $envelope) use ($parentSpan, $context, $topic) {
35 | $properties = $envelope->getProperties();
36 | $span = $parentSpan->startChild($context);
37 | \Sentry\SentrySdk::getCurrentHub()->setSpan($span);
38 |
39 | $body = json_decode($envelope->getBody(), true);
40 | $envelope = $this->setBody(
41 | $envelope,
42 | json_encode([
43 | ...$body,
44 | 'sentry_trace' => \Sentry\getTraceparent(),
45 | 'sentry_baggage' => \Sentry\getBaggage(),
46 | ])
47 | );
48 |
49 | $span
50 | ->setData([
51 | 'messaging.message.id' => $properties['message_id'] ?? null,
52 | 'messaging.destination.name' => $topic,
53 | 'messaging.message.body.size' => strlen($envelope->getBody()),
54 | ])
55 | ->finish();
56 |
57 | \Sentry\SentrySdk::getCurrentHub()->setSpan($parentSpan);
58 |
59 | return $envelope;
60 | }, $isMultipleEnvelopes ? $envelopes : [$envelopes]);
61 |
62 | $envelopes = $isMultipleEnvelopes ? $envelopes : $envelopes[0];
63 |
64 | return [$topic, $envelopes];
65 | }
66 |
67 | /**
68 | * Attempt to set the body to the private body variable.
69 | *
70 | * @param EnvelopeInterface $envelope
71 | * @param string $body
72 | *
73 | * @return EnvelopeInterface
74 | */
75 | protected function setBody(EnvelopeInterface $envelope, string $body): EnvelopeInterface
76 | {
77 | $reflectedEnvelope = new \ReflectionObject($envelope);
78 | // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedWhile
79 | while (!$reflectedEnvelope->hasProperty('body') && $reflectedEnvelope = $reflectedEnvelope->getParentClass()) {
80 | }
81 |
82 | if ($reflectedEnvelope && $reflectedEnvelope->hasProperty('body')) {
83 | $prop = $reflectedEnvelope->getProperty('body');
84 | $prop->setAccessible(true);
85 | $prop->setValue($envelope, $body);
86 | }
87 |
88 | return $envelope;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Helper/Version.php:
--------------------------------------------------------------------------------
1 | deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class);
44 | }
45 |
46 | /**
47 | * Retrieve deployment version of static files.
48 | *
49 | * @return string|null
50 | */
51 | public function getValue(): ?string
52 | {
53 | if (!$this->cachedValue) {
54 | $this->cachedValue = $this->readValue($this->appState->getMode());
55 | }
56 |
57 | return $this->cachedValue;
58 | }
59 |
60 | /**
61 | * Load or generate deployment version of static files depending on the application mode.
62 | *
63 | * @param string $appMode
64 | *
65 | * @return string|null
66 | */
67 | protected function readValue($appMode): ?string
68 | {
69 | if ($version = $this->sentryHelper->getRelease()) {
70 | return $version;
71 | }
72 |
73 | $result = $this->versionStorage->load();
74 | if (!$result) {
75 | if ($appMode == \Magento\Framework\App\State::MODE_PRODUCTION
76 | && !$this->deploymentConfig->getConfigData(
77 | ConfigOptionsListConstants::CONFIG_PATH_SCD_ON_DEMAND_IN_PRODUCTION
78 | )
79 | ) {
80 | $this->getLogger()->critical('Can not load static content version.');
81 |
82 | return null;
83 | }
84 | $result = $this->generateVersion();
85 | $this->versionStorage->save((string) $result);
86 | }
87 |
88 | return $result;
89 | }
90 |
91 | /**
92 | * Generate version of static content.
93 | *
94 | * @return int
95 | */
96 | private function generateVersion()
97 | {
98 | return time();
99 | }
100 |
101 | /**
102 | * Get logger.
103 | *
104 | * @return LoggerInterface
105 | */
106 | private function getLogger()
107 | {
108 | if ($this->logger == null) {
109 | $this->logger = \Magento\Framework\App\ObjectManager::getInstance()
110 | ->get(LoggerInterface::class);
111 | }
112 |
113 | return $this->logger;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Model/SentryCron.php:
--------------------------------------------------------------------------------
1 | data->isActive() ||
39 | !$this->data->isCronMonitoringEnabled() ||
40 | !array_reduce(
41 | $this->data->getTrackCrons(),
42 | fn ($trackCron, $expression) => $trackCron || (
43 | preg_match('/^\/.*\/[imsu]*$/', $expression) ?
44 | preg_match($expression, $schedule->getJobCode()) :
45 | $schedule->getJobCode() === $expression
46 | ),
47 | false
48 | )
49 | ) {
50 | return;
51 | }
52 |
53 | $status = $schedule->getStatus();
54 | if (!in_array($status, [
55 | Schedule::STATUS_RUNNING,
56 | Schedule::STATUS_SUCCESS,
57 | Schedule::STATUS_ERROR,
58 | ])) {
59 | return;
60 | }
61 |
62 | /** @var array|null $cronExpressionArr */
63 | $cronExpressionArr = $schedule->getCronExprArr();
64 | $monitorConfig = null;
65 | if (!empty($cronExpressionArr)) {
66 | $cronExpression = implode(' ', $cronExpressionArr);
67 | $monitorConfig = new MonitorConfig(MonitorSchedule::crontab($cronExpression));
68 | }
69 |
70 | if ($status === Schedule::STATUS_RUNNING) {
71 | if (!isset($this->runningCheckins[$schedule->getId()])) {
72 | $this->startCheckin($schedule, $monitorConfig);
73 | }
74 |
75 | return;
76 | } else {
77 | $this->finishCheckin($schedule, $monitorConfig);
78 | }
79 | }
80 |
81 | /**
82 | * Start the check-in for a given schedule.
83 | *
84 | * @param Schedule $schedule
85 | * @param MonitorConfig|null $monitorConfig
86 | */
87 | public function startCheckin(Schedule $schedule, ?MonitorConfig $monitorConfig = null): void
88 | {
89 | $this->runningCheckins[$schedule->getId()] = [
90 | 'started_at' => microtime(true),
91 | 'check_in_id' => \Sentry\captureCheckIn(
92 | slug: $schedule->getJobCode(),
93 | status: CheckInStatus::inProgress(),
94 | monitorConfig: $monitorConfig,
95 | ),
96 | ];
97 | }
98 |
99 | /**
100 | * Finish the check-in for a given schedule.
101 | *
102 | * @param Schedule $schedule
103 | * @param MonitorConfig|null $monitorConfig
104 | */
105 | public function finishCheckin(Schedule $schedule, ?MonitorConfig $monitorConfig = null): void
106 | {
107 | if (!isset($this->runningCheckins[$schedule->getId()])) {
108 | return;
109 | }
110 |
111 | \Sentry\captureCheckIn(
112 | slug: $schedule->getJobCode(),
113 | status: $schedule->getStatus() === Schedule::STATUS_SUCCESS ? CheckInStatus::ok() : CheckInStatus::error(),
114 | duration: microtime(true) - $this->runningCheckins[$schedule->getId()]['started_at'],
115 | monitorConfig: $monitorConfig,
116 | checkInId: $this->runningCheckins[$schedule->getId()]['check_in_id'],
117 | );
118 |
119 | unset($this->runningCheckins[$schedule->getId()]);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | JustBetter\Sentry\Logger\Handler\Sentry\Proxy
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 | Magento\Customer\Model\Session\Proxy
34 |
35 |
36 |
37 |
38 |
39 | JustBetter\Sentry\Helper\Data\Proxy
40 | JustBetter\Sentry\Model\SentryLog\Proxy
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | - JustBetter\Sentry\Model\Collector\SentryRelatedCspCollector\Proxy
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Plugin/Profiling/Cache.php:
--------------------------------------------------------------------------------
1 | getSpan();
25 | if ($parentSpan === null) {
26 | return parent::load($identifier);
27 | }
28 |
29 | $context = SpanContext::make()
30 | ->setOp('cache.get')
31 | ->setData([
32 | 'cache.key' => $identifier,
33 | ])
34 | ->setDescription((string) $identifier)
35 | ->setOrigin('auto.cache');
36 | $span = $parentSpan->startChild($context);
37 | SentrySdk::getCurrentHub()->setSpan($span);
38 |
39 | $result = parent::load($identifier);
40 |
41 | if ($result === null || $result === false) {
42 | $span->setData([
43 | 'cache.hit' => false,
44 | ]);
45 | } else {
46 | $span->setData([
47 | 'cache.hit' => true,
48 | 'cache.item_size' => is_string($result) ? strlen($result) : null,
49 | ]);
50 | }
51 |
52 | $span->finish();
53 | SentrySdk::getCurrentHub()->setSpan($parentSpan);
54 |
55 | return $result;
56 | }
57 |
58 | /**
59 | * Instrument the cache.put operation around the save.
60 | *
61 | * @param string $data
62 | * @param string $identifier
63 | * @param array $tags
64 | * @param int|bool|null $lifeTime
65 | *
66 | * @return bool
67 | */
68 | public function save($data, $identifier, array $tags = [], $lifeTime = null)
69 | {
70 | $parentSpan = SentrySdk::getCurrentHub()->getSpan();
71 | if ($parentSpan === null) {
72 | return parent::save($data, $identifier, $tags, $lifeTime);
73 | }
74 |
75 | $context = SpanContext::make()
76 | ->setOp('cache.put')
77 | ->setData([
78 | 'cache.key' => $identifier,
79 | 'cache.tags' => $tags,
80 | 'cache.ttl' => $lifeTime,
81 | ])
82 | ->setDescription($identifier)
83 | ->setOrigin('auto.cache');
84 |
85 | $span = $parentSpan->startChild($context);
86 | SentrySdk::getCurrentHub()->setSpan($span);
87 |
88 | $result = parent::save($data, $identifier, $tags, $lifeTime);
89 |
90 | $span->finish();
91 | SentrySdk::getCurrentHub()->setSpan($parentSpan);
92 |
93 | return $result;
94 | }
95 |
96 | /**
97 | * Instrument the cache.remove operation around the remove.
98 | *
99 | * @param string $identifier
100 | *
101 | * @return bool
102 | */
103 | public function remove($identifier)
104 | {
105 | $parentSpan = SentrySdk::getCurrentHub()->getSpan();
106 | if ($parentSpan === null) {
107 | return parent::remove($identifier);
108 | }
109 |
110 | $context = SpanContext::make()
111 | ->setOp('cache.remove')
112 | ->setData([
113 | 'cache.key' => $identifier,
114 | ])
115 | ->setDescription($identifier)
116 | ->setOrigin('auto.cache');
117 |
118 | $span = $parentSpan->startChild($context);
119 | SentrySdk::getCurrentHub()->setSpan($span);
120 |
121 | $result = parent::remove($identifier);
122 |
123 | $span->finish();
124 | SentrySdk::getCurrentHub()->setSpan($parentSpan);
125 |
126 | return $result;
127 | }
128 |
129 | /**
130 | * Instrument the cache.remove operation around the clean.
131 | *
132 | * @param string $mode
133 | * @param array $tags
134 | *
135 | * @return bool
136 | */
137 | public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, array $tags = [])
138 | {
139 | $parentSpan = SentrySdk::getCurrentHub()->getSpan();
140 | if ($parentSpan === null) {
141 | return parent::clean($mode, $tags);
142 | }
143 |
144 | $context = SpanContext::make()
145 | ->setOp('cache.remove')
146 | ->setData([
147 | 'cache.mode' => $mode,
148 | 'cache.tags' => $tags,
149 | ])
150 | ->setDescription($mode.' '.implode(',', $tags))
151 | ->setOrigin('auto.cache');
152 |
153 | $span = $parentSpan->startChild($context);
154 | SentrySdk::getCurrentHub()->setSpan($span);
155 |
156 | $result = parent::clean($mode, $tags);
157 |
158 | $span->finish();
159 | SentrySdk::getCurrentHub()->setSpan($parentSpan);
160 |
161 | return $result;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/Test/Unit/Plugin/GlobalExceptionCatcherTest.php:
--------------------------------------------------------------------------------
1 | getSentryHelperMock(false);
25 | $helper->expects($this->never())
26 | ->method('getDSN');
27 |
28 | /** @var GlobalExceptionCatcher $plugin */
29 | $plugin = (new ObjectManager($this))->getObject(
30 | GlobalExceptionCatcher::class,
31 | [
32 | 'sentryHelper' => $helper,
33 | 'releaseIdentifier' => $this->getReleaseIdentifierMock(),
34 | 'sentryInteraction' => $this->getSentryInteractionMock(false, false),
35 | ]
36 | );
37 |
38 | $called = false;
39 | $plugin->aroundLaunch(
40 | $this->getAppInterfaceMock(),
41 | function () use (&$called) {
42 | $called = true;
43 | }
44 | );
45 |
46 | $this->assertTrue($called);
47 | }
48 |
49 | public function testInitializeIsCalledWithCorrectCredentials()
50 | {
51 | $helper = $this->getSentryHelperMock(true, 'test');
52 | $helper->expects($this->once())
53 | ->method('getDSN');
54 |
55 | $helper->expects($this->once())
56 | ->method('getEnvironment');
57 |
58 | /** @var GlobalExceptionCatcher $plugin */
59 | $plugin = (new ObjectManager($this))->getObject(
60 | GlobalExceptionCatcher::class,
61 | [
62 | 'sentryHelper' => $helper,
63 | 'releaseIdentifier' => $this->getReleaseIdentifierMock(),
64 | 'sentryInteraction' => $this->getSentryInteractionMock(true, false),
65 | ]
66 | );
67 |
68 | $called = false;
69 | $plugin->aroundLaunch(
70 | $this->getAppInterfaceMock(),
71 | function () use (&$called) {
72 | $called = true;
73 | }
74 | );
75 |
76 | $this->assertTrue($called);
77 | }
78 |
79 | public function testCaptureExceptionIsCalled()
80 | {
81 | $helper = $this->getSentryHelperMock(true, 'test');
82 |
83 | /** @var GlobalExceptionCatcher $plugin */
84 | $plugin = (new ObjectManager($this))->getObject(
85 | GlobalExceptionCatcher::class,
86 | [
87 | 'sentryHelper' => $helper,
88 | 'releaseIdentifier' => $this->getReleaseIdentifierMock(),
89 | 'sentryInteraction' => $this->getSentryInteractionMock(true, true),
90 | ]
91 | );
92 |
93 | $called = false;
94 | $exceptionMessage = 'Big problem';
95 | $this->expectExceptionMessage($exceptionMessage);
96 |
97 | $plugin->aroundLaunch(
98 | $this->getAppInterfaceMock(),
99 | function () use (&$called, $exceptionMessage) {
100 | $called = true;
101 |
102 | throw new \Exception($exceptionMessage);
103 | }
104 | );
105 |
106 | $this->assertTrue($called);
107 | }
108 |
109 | private function getAppInterfaceMock()
110 | {
111 | return $this->getMockForAbstractClass(AppInterface::class);
112 | }
113 |
114 | private function getSentryInteractionMock(bool $expectsInitialize, bool $expectsCaptureException)
115 | {
116 | $mock = $this->createConfiguredMock(
117 | SentryInteraction::class,
118 | [
119 | 'initialize' => '',
120 | 'captureException' => '',
121 | ]
122 | );
123 |
124 | $mock->expects($expectsInitialize ? $this->atLeastOnce() : $this->never())
125 | ->method('initialize');
126 |
127 | $mock->expects($expectsCaptureException ? $this->atLeastOnce() : $this->never())
128 | ->method('captureException');
129 |
130 | return $mock;
131 | }
132 |
133 | private function getReleaseIdentifierMock()
134 | {
135 | $mock = $this->createConfiguredMock(
136 | ReleaseIdentifier::class,
137 | [
138 | 'getReleaseId' => 1,
139 | ]
140 | );
141 |
142 | return $mock;
143 | }
144 |
145 | private function getSentryHelperMock(bool $isActive = true, $environment = null)
146 | {
147 | $mock = $this->createConfiguredMock(Data::class, [
148 | 'isActive' => $isActive,
149 | 'getDSN' => 'dsn',
150 | 'getEnvironment' => $environment,
151 | ]);
152 |
153 | $mock->expects($this->once())
154 | ->method('isActive');
155 |
156 | return $mock;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Plugin/Profiling/QueuePlugin.php:
--------------------------------------------------------------------------------
1 | getProperties();
34 | $body = json_decode($envelope->getBody(), true);
35 | if (!isset($body['sentry_trace']) && !isset($body['sentry_baggage'])) {
36 | return $envelope;
37 | }
38 |
39 | $this->parentSpan ??= \Sentry\SentrySdk::getCurrentHub()->getSpan();
40 |
41 | $context = \Sentry\continueTrace(
42 | $body['sentry_trace'],
43 | $body['sentry_baggage']
44 | )
45 | ->setOp('queue.process')
46 | ->setName($properties['topic_name']);
47 |
48 | $this->transactions[$properties['message_id']] = \Sentry\startTransaction($context);
49 | $this->transactions[$properties['message_id']]
50 | ->setData([
51 | 'messaging.message.id' => $properties['message_id'],
52 | 'messaging.destination.name' => $properties['topic_name'],
53 | 'messaging.queue.name' => $properties['queue_name'] ?? null,
54 | 'messaging.message.body.size' => strlen($envelope->getBody()),
55 | 'messaging.message.retry.count' => $properties['retries'] ?? null,
56 | ]);
57 | \Sentry\SentrySdk::getCurrentHub()->setSpan($this->transactions[$properties['message_id']]);
58 |
59 | unset($body['sentry_trace']);
60 | unset($body['sentry_baggage']);
61 |
62 | return $this->setBody($envelope, json_encode($body));
63 | }
64 |
65 | /**
66 | * Finish transaction for failed job.
67 | *
68 | * @param QueueInterface $queue
69 | * @param EnvelopeInterface $envelope
70 | * @param bool $requeue
71 | * @param string $rejectionMessage
72 | *
73 | * @return array
74 | */
75 | public function beforeReject(QueueInterface $queue, EnvelopeInterface $envelope, $requeue = true, $rejectionMessage = null): array
76 | {
77 | $properties = $envelope->getProperties();
78 | $transaction = $this->transactions[$properties['message_id']] ?? null;
79 | if (!$transaction) {
80 | return [$envelope, $requeue, $rejectionMessage];
81 | }
82 |
83 | $transaction->setStatus(\Sentry\Tracing\SpanStatus::internalError());
84 |
85 | $transaction->finish();
86 | unset($this->transactions[$properties['message_id']]);
87 | if ($this->parentSpan) {
88 | \Sentry\SentrySdk::getCurrentHub()->setSpan($this->parentSpan);
89 | }
90 |
91 | return [$envelope, $requeue, $rejectionMessage];
92 | }
93 |
94 | /**
95 | * Finish transaction for successfully executed job.
96 | *
97 | * @param QueueInterface $queue
98 | * @param EnvelopeInterface $envelope
99 | *
100 | * @return array
101 | */
102 | public function beforeAcknowledge(QueueInterface $queue, EnvelopeInterface $envelope): array
103 | {
104 | $properties = $envelope->getProperties();
105 | $transaction = $this->transactions[$properties['message_id']] ?? null;
106 | if (!$transaction) {
107 | return [$envelope];
108 | }
109 |
110 | $transaction->setStatus(\Sentry\Tracing\SpanStatus::ok());
111 |
112 | $transaction->finish();
113 | unset($this->transactions[$properties['message_id']]);
114 | if ($this->parentSpan) {
115 | \Sentry\SentrySdk::getCurrentHub()->setSpan($this->parentSpan);
116 | }
117 |
118 | return [$envelope];
119 | }
120 |
121 | /**
122 | * Attempt to set the body to the private body variable.
123 | *
124 | * @param EnvelopeInterface $envelope
125 | * @param string $body
126 | *
127 | * @return EnvelopeInterface
128 | */
129 | protected function setBody(EnvelopeInterface $envelope, string $body): EnvelopeInterface
130 | {
131 | $reflectedEnvelope = new \ReflectionObject($envelope);
132 | // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedWhile
133 | while (!$reflectedEnvelope->hasProperty('body') && $reflectedEnvelope = $reflectedEnvelope->getParentClass()) {
134 | }
135 |
136 | if ($reflectedEnvelope && $reflectedEnvelope->hasProperty('body')) {
137 | $prop = $reflectedEnvelope->getProperty('body');
138 | $prop->setAccessible(true);
139 | $prop->setValue($envelope, $body);
140 | }
141 |
142 | return $envelope;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/Block/SentryScript.php:
--------------------------------------------------------------------------------
1 | dataHelper->isActive() || !$this->dataHelper->showScriptTagInThisBlock($blockName)) {
43 | return false;
44 | }
45 |
46 | if ($this->useScriptTag()) {
47 | return true;
48 | }
49 |
50 | if ($this->isSpotlightEnabled()) {
51 | return true;
52 | }
53 |
54 | return false;
55 | }
56 |
57 | /**
58 | * Get the DSN of Sentry.
59 | *
60 | * @return string
61 | */
62 | public function getDSN()
63 | {
64 | return (string) $this->dataHelper->getDSN();
65 | }
66 |
67 | /**
68 | * Get the version of the JS-SDK of Sentry.
69 | *
70 | * @return string
71 | */
72 | public function getJsSdkVersion()
73 | {
74 | return $this->dataHelper->getJsSdkVersion();
75 | }
76 |
77 | /**
78 | * Get the current version of the Magento application.
79 | *
80 | * @return int|string
81 | */
82 | public function getVersion()
83 | {
84 | return $this->version->getValue();
85 | }
86 |
87 | /**
88 | * Get the current environment of Sentry.
89 | *
90 | * @return mixed
91 | */
92 | public function getEnvironment()
93 | {
94 | return $this->dataHelper->getEnvironment();
95 | }
96 |
97 | /**
98 | * Whether to enable sentry js tracking.
99 | */
100 | public function useScriptTag(): bool
101 | {
102 | return $this->dataHelper->useScriptTag();
103 | }
104 |
105 | /**
106 | * Whether to enable session replay.
107 | */
108 | public function useSessionReplay(): bool
109 | {
110 | return $this->dataHelper->useSessionReplay();
111 | }
112 |
113 | /**
114 | * Get the session replay sample rate.
115 | */
116 | public function getReplaySessionSampleRate(): float
117 | {
118 | return $this->dataHelper->getReplaySessionSampleRate();
119 | }
120 |
121 | /**
122 | * Get the session replay error sample rate.
123 | */
124 | public function getReplayErrorSampleRate(): float
125 | {
126 | return $this->dataHelper->getReplayErrorSampleRate();
127 | }
128 |
129 | /**
130 | * Whether to block media during replay.
131 | */
132 | public function getReplayBlockMedia(): bool
133 | {
134 | return $this->dataHelper->getReplayBlockMedia();
135 | }
136 |
137 | /**
138 | * Whether to show mask text.
139 | */
140 | public function getReplayMaskText(): bool
141 | {
142 | return $this->dataHelper->getReplayMaskText();
143 | }
144 |
145 | /**
146 | * If LogRocket should be used.
147 | *
148 | * @return bool
149 | */
150 | public function useLogRocket()
151 | {
152 | return $this->dataHelper->useLogrocket();
153 | }
154 |
155 | /**
156 | * If LogRocket identify should be used.
157 | *
158 | * @return bool
159 | */
160 | public function useLogRocketIdentify()
161 | {
162 | return $this->dataHelper->useLogrocketIdentify();
163 | }
164 |
165 | /**
166 | * Gets the LogRocket key.
167 | *
168 | * @return string
169 | */
170 | public function getLogrocketKey()
171 | {
172 | return $this->dataHelper->getLogrocketKey();
173 | }
174 |
175 | /**
176 | * Whether we should strip the static content version from the URL.
177 | *
178 | * @return bool
179 | */
180 | public function stripStaticContentVersion()
181 | {
182 | return $this->dataHelper->stripStaticContentVersion();
183 | }
184 |
185 | /**
186 | * Whether we should strip the store code from the URL.
187 | *
188 | * @return bool
189 | */
190 | public function stripStoreCode()
191 | {
192 | return $this->dataHelper->stripStoreCode();
193 | }
194 |
195 | /**
196 | * Get Store code.
197 | *
198 | * @return string
199 | */
200 | public function getStoreCode()
201 | {
202 | return $this->_storeManager->getStore()->getCode();
203 | }
204 |
205 | /**
206 | * Whether tracing is enabled.
207 | */
208 | public function isTracingEnabled(): bool
209 | {
210 | return $this->dataHelper->isTracingEnabled();
211 | }
212 |
213 | /**
214 | * Whether spotlight is enabled.
215 | */
216 | public function isSpotlightEnabled(): bool
217 | {
218 | return $this->dataHelper->isSpotlightEnabled();
219 | }
220 |
221 | /**
222 | * Get sample rate for tracing.
223 | */
224 | public function getTracingSampleRate(): float
225 | {
226 | return $this->dataHelper->getTracingSampleRate();
227 | }
228 |
229 | /**
230 | * Get a list of js errors to ignore.
231 | */
232 | public function getIgnoreJsErrors(): string
233 | {
234 | return $this->json->serialize($this->dataHelper->getIgnoreJsErrors());
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/Model/SentryLog.php:
--------------------------------------------------------------------------------
1 | data->collectModuleConfig();
50 | $customTags = [];
51 |
52 | if ($config['enable_logs'] && $logLevel >= $config['logger_log_level']) {
53 | match ($logLevel) {
54 | Monolog::DEBUG => \Sentry\Logger()->debug($message, $context),
55 | Monolog::INFO => \Sentry\Logger()->info($message, $context),
56 | Monolog::WARNING => \Sentry\Logger()->warn($message, $context),
57 | Monolog::ERROR, MONOLOG::CRITICAL => \Sentry\Logger()->error($message, $context),
58 | Monolog::ALERT, MONOLOG::EMERGENCY => \Sentry\Logger()->fatal($message, $context),
59 | default => \Sentry\Logger()->info($message, $context)
60 | };
61 | }
62 |
63 | if ($logLevel < (int) $config['log_level']) {
64 | return;
65 | }
66 |
67 | if (true === isset($context['custom_tags']) && false === empty($context['custom_tags'])) {
68 | $customTags = $context['custom_tags'];
69 | unset($context['custom_tags']);
70 | }
71 |
72 | \Sentry\configureScope(
73 | function (SentryScope $scope) use ($context, $customTags): void {
74 | $this->setTags($scope, $customTags);
75 | if (false === empty($context)) {
76 | $scope->setContext('Custom context', $context);
77 | }
78 | }
79 | );
80 |
81 | $this->sentryInteraction->addUserContext();
82 |
83 | if ($message instanceof \Throwable) {
84 | $lastEventId = \Sentry\captureException($message);
85 | } else {
86 | $lastEventId = \Sentry\captureMessage(
87 | $message,
88 | \Sentry\Severity::fromError($logLevel),
89 | $this->monologContextToSentryHint($context)
90 | );
91 | }
92 |
93 | /// when using JS SDK you can use this for custom error page printing
94 | try {
95 | if (true === $this->canGetCustomerData()) {
96 | $this->customerSession->setSentryEventId($lastEventId);
97 | }
98 | } catch (SessionException $e) {
99 | return;
100 | }
101 | }
102 |
103 | /**
104 | * Turn the monolog context into a format Sentrys EventHint can deal with.
105 | *
106 | * @param array $context
107 | *
108 | * @return EventHint|null
109 | */
110 | public function monologContextToSentryHint(array $context): ?EventHint
111 | {
112 | return EventHint::fromArray(
113 | [
114 | 'exception' => ($context['exception'] ?? null) instanceof \Throwable ? $context['exception'] : null,
115 | 'mechanism' => ($context['mechanism'] ?? null) instanceof ExceptionMechanism ? $context['mechanism'] : null,
116 | 'stacktrace' => ($context['stacktrace'] ?? null) instanceof Stacktrace ? $context['stacktrace'] : null,
117 | 'extra' => array_filter(
118 | $context,
119 | fn ($key) => !in_array($key, ['exception', 'mechanism', 'stacktrace']),
120 | ARRAY_FILTER_USE_KEY
121 | ) ?: [],
122 | ]
123 | );
124 | }
125 |
126 | /**
127 | * Check if we can retrieve customer data.
128 | *
129 | * @return bool
130 | */
131 | private function canGetCustomerData()
132 | {
133 | try {
134 | return $this->appState->getAreaCode() === Area::AREA_FRONTEND;
135 | } catch (LocalizedException $ex) {
136 | return false;
137 | }
138 | }
139 |
140 | /**
141 | * Add additional tags to the scope.
142 | *
143 | * @param SentryScope $scope
144 | * @param array $customTags
145 | */
146 | private function setTags(SentryScope $scope, $customTags): void
147 | {
148 | $store = $this->data->getStore();
149 |
150 | $scope->setTag('mage_mode', $this->data->getAppState());
151 | $scope->setTag('version', $this->data->getMagentoVersion());
152 | $scope->setTag('website_id', (string) $store?->getWebsiteId());
153 | $scope->setTag('store_id', (string) $store?->getId());
154 | $scope->setTag('store_code', (string) $store?->getCode());
155 |
156 | if (false === empty($customTags)) {
157 | foreach ($customTags as $tag => $value) {
158 | $scope->setTag($tag, $value);
159 | }
160 | }
161 | }
162 |
163 | /**
164 | * Send the logs to Sentry if there are any.
165 | */
166 | public function __destruct()
167 | {
168 | \Sentry\Logger()->flush();
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Plugin/GlobalExceptionCatcher.php:
--------------------------------------------------------------------------------
1 | globalCatcher(
61 | $subject,
62 | $proceed,
63 | ...$args
64 | );
65 | }
66 |
67 | /**
68 | * Wrap command run, start watching for exceptions.
69 | *
70 | * @param Command $subject
71 | * @param callable $proceed
72 | * @param mixed $args
73 | *
74 | * @return int
75 | */
76 | public function aroundRun(Command $subject, callable $proceed, ...$args)
77 | {
78 | return $this->globalCatcher(
79 | $subject,
80 | $proceed,
81 | ...$args
82 | );
83 | }
84 |
85 | /**
86 | * Catch anything coming out of the proceed function.
87 | *
88 | * @param mixed $subject
89 | * @param callable $proceed
90 | * @param mixed $args
91 | *
92 | * @return mixed
93 | */
94 | public function globalCatcher($subject, $proceed, ...$args)
95 | {
96 | if ($this->booted) {
97 | return $proceed(...$args);
98 | }
99 |
100 | $this->booted = true;
101 |
102 | if ((!$this->sentryHelper->isActive()) || (!$this->sentryHelper->isPhpTrackingEnabled())) {
103 | return $proceed(...$args);
104 | }
105 |
106 | $config = $this->prepareConfig();
107 |
108 | $this->sentryInteraction->initialize(array_filter($config->getData()));
109 | $this->sentryPerformance->startTransaction($subject, ...$args);
110 |
111 | try {
112 | return $response = $proceed(...$args);
113 | } catch (Throwable $exception) {
114 | $this->sentryInteraction->captureException($exception);
115 |
116 | throw $exception;
117 | } finally {
118 | $this->sentryPerformance->finishTransaction($response ?? 500);
119 | }
120 | }
121 |
122 | /**
123 | * Prepare all the config passed to sentry.
124 | *
125 | * @return DataObject
126 | */
127 | public function prepareConfig(): DataObject
128 | {
129 | /** @var DataObject $config */
130 | $config = $this->dataObjectFactory->create();
131 | $config->setData(array_intersect_key($this->sentryHelper->collectModuleConfig(), SentryHelper::NATIVE_SENTRY_CONFIG_KEYS));
132 |
133 | $config->setSpotlight($this->sentryHelper->isSpotlightEnabled());
134 | $config->setDsn($this->sentryHelper->getDSN());
135 | if ($release = $this->releaseIdentifier->getReleaseId()) {
136 | $config->setRelease((string) $release);
137 | }
138 |
139 | if ($environment = $this->sentryHelper->getEnvironment()) {
140 | $config->setEnvironment($environment);
141 | }
142 |
143 | if ($prefixes = $this->sentryHelper->getPrefixes()) {
144 | $config->setPrefixes($prefixes);
145 | }
146 |
147 | $config->setBeforeBreadcrumb(function (\Sentry\Breadcrumb $breadcrumb): ?\Sentry\Breadcrumb {
148 | $data = $this->dataObjectFactory->create();
149 | $data->setBreadcrumb($breadcrumb);
150 | $this->eventManager->dispatch('sentry_before_breadcrumb', [
151 | 'sentry_breadcrumb' => $data,
152 | ]);
153 |
154 | return $data->getBreadcrumb();
155 | });
156 |
157 | $config->setBeforeSendTransaction(function (\Sentry\Event $transaction): ?\Sentry\Event {
158 | $data = $this->dataObjectFactory->create();
159 | $data->setTransaction($transaction);
160 | $this->eventManager->dispatch('sentry_before_send_transaction', [
161 | 'sentry_transaction' => $data,
162 | ]);
163 |
164 | return $data->getTransaction();
165 | });
166 |
167 | $config->setBeforeSendCheckIn(function (\Sentry\Event $checkIn): ?\Sentry\Event {
168 | $data = $this->dataObjectFactory->create();
169 | $data->setCheckIn($checkIn);
170 | $this->eventManager->dispatch('sentry_before_send_check_in', [
171 | 'sentry_check_in' => $data,
172 | ]);
173 |
174 | return $data->getCheckIn();
175 | });
176 |
177 | $config->setBeforeSendLog(function (\Sentry\Logs\Log $log): ?\Sentry\Logs\Log {
178 | $data = $this->dataObjectFactory->create();
179 | $data->setLog($log);
180 | $this->eventManager->dispatch('sentry_before_send_log', [
181 | 'sentry_log' => $data,
182 | ]);
183 |
184 | return $data->getLog();
185 | });
186 |
187 | $config->setBeforeSend(function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event {
188 | $data = $this->dataObjectFactory->create();
189 | $data->setEvent($event);
190 | $data->setHint($hint);
191 | $this->eventManager->dispatch('sentry_before_send', [
192 | 'sentry_event' => $data,
193 | ]);
194 |
195 | return $data->getEvent();
196 | });
197 |
198 | $disabledDefaultIntegrations = $this->sentryHelper->getDisabledDefaultIntegrations();
199 | $config->setData('integrations', static fn (array $integrations) => array_filter(
200 | $integrations,
201 | static fn (IntegrationInterface $integration) => !in_array(get_class($integration), $disabledDefaultIntegrations)
202 | ));
203 |
204 | $config->setErrorTypes($this->sentryHelper->getErrorTypes());
205 |
206 | if ($this->sentryHelper->isPerformanceTrackingEnabled()) {
207 | $config->setTracesSampleRate(
208 | php_sapi_name() === 'cli' ?
209 | $this->sentryHelper->getTracingSampleRateCli() :
210 | $this->sentryHelper->getTracingSampleRate()
211 | );
212 | } else {
213 | $config->unsetTracesSampleRate(null);
214 | }
215 |
216 | $config->setInAppExclude([
217 | ...($config->getInAppExclude() ?? []),
218 | 'cron.php',
219 | 'get.php',
220 | 'health_check.php',
221 | 'index.php',
222 | 'static.php',
223 | 'bin/magento',
224 | 'app/autoload.php',
225 | 'app/bootstrap.php',
226 | 'app/functions.php',
227 | ]);
228 |
229 | $this->eventManager->dispatch('sentry_before_init', [
230 | 'config' => $config,
231 | ]);
232 |
233 | return $config;
234 | }
235 |
236 | /**
237 | * Ensure transactions are finished and cleaned up when unexpectedly exiting.
238 | */
239 | public function __destruct()
240 | {
241 | $this->sentryPerformance->finishTransaction();
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/Model/SentryInteraction.php:
--------------------------------------------------------------------------------
1 | setSdkIdentifier(static::SDK_IDENTIFIER);
63 |
64 | SentrySdk::init()->bindClient($client->getClient());
65 | }
66 |
67 | /**
68 | * Check if we might be able to get user context.
69 | */
70 | public function canGetUserContext(): bool
71 | {
72 | try {
73 | // @phpcs:ignore Generic.PHP.NoSilencedErrors
74 | return in_array(@$this->appState->getAreaCode(), [Area::AREA_ADMINHTML, Area::AREA_FRONTEND, Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP, Area::AREA_GRAPHQL]);
75 | } catch (LocalizedException $ex) {
76 | return false;
77 | }
78 | }
79 |
80 | /**
81 | * Get a class instance, but only if it is already available within the objectManager.
82 | *
83 | * @param string $class The classname of the type you want from the objectManager.
84 | */
85 | public function getObjectIfInitialized($class): ?object
86 | {
87 | $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
88 | $reflectionClass = new ReflectionClass($objectManager);
89 | $sharedInstances = $reflectionClass->getProperty('_sharedInstances');
90 | $sharedInstances->setAccessible(true);
91 | $class = $this->omConfigInterface->getPreference($class);
92 |
93 | if (!array_key_exists(ltrim($class, '\\'), $sharedInstances->getValue($objectManager))) {
94 | return null;
95 | }
96 |
97 | return $objectManager->get($class);
98 | }
99 |
100 | /**
101 | * Attempt to get userContext from the objectManager, so we don't request it too early.
102 | */
103 | public function getUserContext(): ?UserContextInterface
104 | {
105 | if ($this->userContext) {
106 | return $this->userContext;
107 | }
108 |
109 | return $this->userContext = $this->getObjectIfInitialized(UserContextInterface::class);
110 | }
111 |
112 | /**
113 | * Check if we might be able to get the user data.
114 | */
115 | public function canGetUserData(): bool
116 | {
117 | try {
118 | // @phpcs:ignore Generic.PHP.NoSilencedErrors
119 | return in_array(@$this->appState->getAreaCode(), [Area::AREA_ADMINHTML, Area::AREA_FRONTEND]);
120 | } catch (LocalizedException $ex) {
121 | return false;
122 | }
123 | }
124 |
125 | /**
126 | * Attempt to get userdata from the current session.
127 | */
128 | private function getSessionUserData(): array
129 | {
130 | if (!$this->canGetUserData()) {
131 | return [];
132 | }
133 |
134 | if ($this->appState->getAreaCode() === Area::AREA_ADMINHTML) {
135 | $adminSession = $this->getObjectIfInitialized(AdminSession::class);
136 | if ($adminSession === null) {
137 | return [];
138 | }
139 |
140 | if ($adminSession->isLoggedIn()) {
141 | return [
142 | 'id' => $adminSession->getUser()->getId(),
143 | 'email' => $adminSession->getUser()->getEmail(),
144 | 'user_type' => UserContextInterface::USER_TYPE_ADMIN,
145 | ];
146 | }
147 | }
148 |
149 | if ($this->appState->getAreaCode() === Area::AREA_FRONTEND) {
150 | $customerSession = $this->getObjectIfInitialized(CustomerSession::class);
151 | if ($customerSession === null) {
152 | return [];
153 | }
154 |
155 | if ($customerSession->isLoggedIn()) {
156 | return [
157 | 'id' => $customerSession->getCustomer()->getId(),
158 | 'email' => $customerSession->getCustomer()->getEmail(),
159 | 'website_id' => $customerSession->getCustomer()->getWebsiteId(),
160 | 'store_id' => $customerSession->getCustomer()->getStoreId(),
161 | 'user_type' => UserContextInterface::USER_TYPE_CUSTOMER,
162 | ];
163 | }
164 | }
165 |
166 | return [];
167 | }
168 |
169 | /**
170 | * Attempt to add the user context to the exception.
171 | */
172 | public function addUserContext(): void
173 | {
174 | $userId = null;
175 | $userType = null;
176 | $userData = [];
177 |
178 | \Magento\Framework\Profiler::start('SENTRY::add_user_context');
179 |
180 | try {
181 | $ip = $this->remoteAddress->getRemoteAddress();
182 | if ($ip) {
183 | configureScope(function (Scope $scope) use ($ip) {
184 | $scope->setUser([
185 | 'ip_address' => $ip,
186 | ]);
187 | });
188 | }
189 |
190 | if ($this->canGetUserContext()) {
191 | $userId = $this->getUserContext()->getUserId();
192 | if ($userId) {
193 | $userType = $this->getUserContext()->getUserType();
194 | }
195 | }
196 |
197 | if (count($userData = $this->getSessionUserData())) {
198 | $userId = $userData['id'] ?? $userId;
199 | $userType = $userData['user_type'] ?? $userType;
200 | unset($userData['user_type']);
201 | }
202 |
203 | if (!$userId) {
204 | return;
205 | }
206 |
207 | configureScope(function (Scope $scope) use ($userType, $userId, $userData) {
208 | $scope->setUser([
209 | 'id' => $userId,
210 | ...$userData,
211 | 'user_type' => match ($userType) {
212 | UserContextInterface::USER_TYPE_INTEGRATION => 'integration',
213 | UserContextInterface::USER_TYPE_ADMIN => 'admin',
214 | UserContextInterface::USER_TYPE_CUSTOMER => 'customer',
215 | UserContextInterface::USER_TYPE_GUEST => 'guest',
216 | default => 'unknown'
217 | },
218 | ]);
219 | });
220 | } catch (\Throwable $e) {
221 | // User context could not be found or added.
222 | \Magento\Framework\Profiler::stop('SENTRY::add_user_context');
223 |
224 | return;
225 | }
226 | \Magento\Framework\Profiler::stop('SENTRY::add_user_context');
227 | }
228 |
229 | /**
230 | * Capture passed exception.
231 | *
232 | * @param \Throwable $ex
233 | *
234 | * @return void
235 | */
236 | public function captureException(\Throwable $ex): void
237 | {
238 | try {
239 | if (!$this->sentryHelper->shouldCaptureException($ex)) {
240 | return;
241 | }
242 |
243 | $this->addUserContext();
244 |
245 | ob_start();
246 | captureException($ex);
247 | ob_end_clean();
248 | } catch (Throwable) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/Model/SentryPerformance.php:
--------------------------------------------------------------------------------
1 | transaction !== null) {
63 | // Do not start a transaction if one is already runnning.
64 | return;
65 | }
66 |
67 | if ($app instanceof Http) {
68 | $this->startHttpTransaction($app, ...$args);
69 |
70 | return;
71 | }
72 | if ($app instanceof Command) {
73 | $this->startCommandTransaction($app, ...$args);
74 |
75 | return;
76 | }
77 | }
78 |
79 | /**
80 | * Starts a new HTTP transaction.
81 | *
82 | * @param Http $app
83 | *
84 | * @return void
85 | */
86 | public function startHttpTransaction(Http $app): void
87 | {
88 | $requestStartTime = $this->request->getServer('REQUEST_TIME_FLOAT', microtime(true));
89 |
90 | $context = TransactionContext::fromHeaders(
91 | $this->request->getHeader('sentry-trace') ?: '',
92 | $this->request->getHeader('baggage') ?: ''
93 | );
94 |
95 | $requestPath = '/'.ltrim($this->request->getRequestUri(), '/');
96 |
97 | $context->setName($requestPath);
98 | $context->setSource(TransactionSource::url());
99 | $context->setStartTimestamp($requestStartTime);
100 |
101 | $context->setData([
102 | 'url' => $requestPath,
103 | 'method' => strtoupper($this->request->getMethod()),
104 | ]);
105 |
106 | // Start the transaction
107 | $transaction = startTransaction($context);
108 |
109 | // If this transaction is not sampled, don't set it either and stop doing work from this point on
110 | if (!$transaction->getSampled()) {
111 | return;
112 | }
113 |
114 | $transaction->setOrigin('auto.http.server');
115 |
116 | $this->transaction = $transaction;
117 | SentrySdk::getCurrentHub()->setSpan($transaction);
118 | }
119 |
120 | /**
121 | * Starts a new Command transaction.
122 | *
123 | * @param Command $command
124 | * @param InputInterface $input
125 | * @param OutputInterface $output
126 | *
127 | * @return void
128 | */
129 | public function startCommandTransaction(Command $command, InputInterface $input, OutputInterface $output): void
130 | {
131 | $requestStartTime = microtime(true);
132 | $context = TransactionContext::make();
133 | $context->setName('bin/magento '.($input->__toString() ?: $command->getName()));
134 | $context->setSource(TransactionSource::task());
135 | $context->setStartTimestamp($requestStartTime);
136 |
137 | $context->setData([
138 | 'command' => $command->getName(),
139 | 'arguments' => $input->getArguments(),
140 | 'options' => $input->getOptions(),
141 | ]);
142 |
143 | // Start the transaction
144 | $transaction = startTransaction($context);
145 |
146 | // Do not sample long running tasks, individual jobs are sampled if the initiator was sampled.
147 | if (in_array($command->getName(), ['queue:consumers:start'])) {
148 | $transaction->setSampled(false);
149 | }
150 |
151 | // If this transaction is not sampled, don't set it either and stop doing work from this point on
152 | if (!$transaction->getSampled()) {
153 | return;
154 | }
155 |
156 | $this->transaction = $transaction;
157 | SentrySdk::getCurrentHub()->setSpan($transaction);
158 | }
159 |
160 | /**
161 | * Finish the transaction. this will send the transaction (and the profile) to Sentry.
162 | *
163 | * @param ResponseInterface|int|null $statusCode
164 | *
165 | * @throws LocalizedException
166 | *
167 | * @return void
168 | */
169 | public function finishTransaction(ResponseInterface|int|null $statusCode = null): void
170 | {
171 | if ($this->transaction === null) {
172 | return;
173 | }
174 |
175 | try {
176 | $state = $this->objectManager->get(State::class);
177 | $areaCode = $state->getAreaCode();
178 | } catch (LocalizedException $e) {
179 | // Default area is global.
180 | $areaCode = Area::AREA_GLOBAL;
181 | }
182 |
183 | if (in_array($areaCode, $this->helper->getPerformanceTrackingExcludedAreas())) {
184 | return;
185 | }
186 |
187 | if ($statusCode instanceof Response) {
188 | $statusCode = (int) $statusCode->getStatusCode();
189 | }
190 |
191 | if (is_numeric($statusCode)) {
192 | $this->transaction->setHttpStatus($statusCode);
193 | }
194 |
195 | if (in_array($areaCode, [Area::AREA_FRONTEND, Area::AREA_ADMINHTML, Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP, Area::AREA_GRAPHQL])) {
196 | if (!empty($this->request->getFullActionName())) {
197 | $this->transaction->setName(strtoupper($this->request->getMethod()).' '.$this->request->getFullActionName('/'));
198 | }
199 |
200 | $this->transaction->setOp('http');
201 |
202 | $this->transaction->setData(array_merge(
203 | $this->transaction->getData(),
204 | $this->request->__debugInfo(),
205 | [
206 | 'module' => $this->request->getModuleName(),
207 | 'action' => $this->request->getFullActionName(),
208 | ]
209 | ));
210 | } else {
211 | $this->transaction->setOp($areaCode);
212 | }
213 |
214 | try {
215 | // Finish the transaction, this submits the transaction and it's span to Sentry
216 | $this->transaction->finish();
217 | } catch (Throwable) { // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
218 | }
219 |
220 | $this->transaction = null;
221 | }
222 |
223 | /**
224 | * Helper function to create a new span. returns a DTO which holds the important information about the span, and the span itself.
225 | *
226 | * @param SpanContext $context
227 | *
228 | * @return PerformanceTracingDto
229 | */
230 | public static function traceStart(SpanContext $context): PerformanceTracingDto // phpcs:ignore Magento2.Functions.StaticFunction.StaticFunction
231 | {
232 | $scope = SentrySdk::getCurrentHub()->pushScope();
233 | $span = null;
234 |
235 | $parentSpan = $scope->getSpan();
236 | if ($parentSpan !== null && $parentSpan->getSampled()) {
237 | $span = $parentSpan->startChild($context);
238 | $scope->setSpan($span);
239 | }
240 |
241 | return new PerformanceTracingDto($scope, $parentSpan, $span);
242 | }
243 |
244 | /**
245 | * Method close the given span. a DTO object, which has been created by `::traceStart` must be passed.
246 | *
247 | * @param PerformanceTracingDto $context
248 | *
249 | * @return void
250 | */
251 | public static function traceEnd(PerformanceTracingDto $context): void // phpcs:ignore Magento2.Functions.StaticFunction.StaticFunction
252 | {
253 | if ($context->getSpan()) {
254 | $context->getSpan()->finish();
255 | $context->getScope()->setSpan($context->getParentSpan());
256 | }
257 | SentrySdk::getCurrentHub()->popScope();
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | separator-top
9 |
10 | justbetter
11 | JustBetter_Sentry::configuration
12 |
13 |
14 |
15 |
16 | JustBetter\Sentry\Block\Adminhtml\System\Config\DeploymentConfigInfo
17 |
18 |
19 |
20 | Magento\Config\Model\Config\Source\Yesno
21 |
22 |
23 |
24 | Magento\Config\Model\Config\Source\Yesno
25 |
26 |
27 |
28 | Magento\Config\Model\Config\Source\Yesno
29 |
30 |
31 |
32 | JustBetter\Sentry\Model\Config\Source\ScriptTagPlacement
33 |
34 | 1
35 |
36 |
37 |
38 |
39 | Magento\Config\Model\Config\Source\Yesno
40 |
41 | 1
42 |
43 |
44 |
45 |
46 |
47 | 1
48 |
49 |
50 |
51 |
52 |
53 | 1
54 |
55 |
56 |
57 |
58 | Magento\Config\Model\Config\Source\Yesno
59 |
60 | 1
61 |
62 |
63 |
64 |
65 | Magento\Config\Model\Config\Source\Yesno
66 |
67 | 1
68 |
69 |
70 |
71 |
72 | Magento\Config\Model\Config\Source\Yesno
73 |
74 | 1
75 |
76 |
77 |
78 |
79 | Include user ID, name and email to identify logged in users in LogRocket
80 | Magento\Config\Model\Config\Source\Yesno
81 |
82 | 1
83 | 1
84 |
85 |
86 |
87 | Send Sentry test event
88 | justbetter_sentry/test/sentry
89 | JustBetter\Sentry\Block\Adminhtml\System\Config\Button
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Magento\Config\Model\Config\Source\Yesno
98 |
99 |
100 |
101 |
102 | 1
103 |
104 |
105 |
106 |
107 |
108 | 1
109 |
110 |
111 |
112 |
113 |
114 | 1
115 |
116 |
117 |
118 |
119 | JustBetter\Sentry\Model\Config\Source\LogLevel
120 |
121 | 1
122 |
123 |
124 |
125 |
126 | Magento\Config\Model\Config\Source\Yesno
127 |
128 | 1
129 |
130 |
131 |
132 |
133 | validate-not-negative-number validate-number
134 |
135 | 1
136 |
137 |
138 |
139 |
140 | validate-not-negative-number validate-number
141 |
142 | 1
143 |
144 |
145 |
146 |
147 | validate-not-negative-number validate-number
148 |
149 | 1
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | Magento\Config\Model\Config\Source\Yesno
159 |
160 |
161 |
162 |
163 | Magento\Config\Model\Config\Source\Yesno
164 | 1
165 |
166 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # Magento 2 Sentry Logger
7 | [](https://packagist.org/packages/justbetter/magento2-sentry)
8 | [](https://packagist.org/packages/justbetter/magento2-sentry)
9 | 
10 | [](https://github.com/justbetter/magento2-sentry/actions/workflows/analyse.yml)
11 |
12 | This Magento 2 module integrates [Sentry](https://github.com/getsentry/sentry-php) into magento 2.
13 | Depending on the log level configured in the backend of magento 2, notifications and errors can be sent to sentry.
14 |
15 | ## Features
16 |
17 | - Send exceptions and logs to Sentry
18 | - Show detailed context on thrown exceptions (Like Magento user/api consumer id)
19 | - Easily control which events get sent to Sentry
20 | - Backend and frontend error reporting
21 | - Performance sampling ([tracing](https://docs.sentry.io/platforms/php/tracing/) & [profiling](https://docs.sentry.io/platforms/php/profiling/))
22 | - Database Queries (Detect N+1 queries)
23 | - Events
24 | - Template rendering
25 | - [Cache monitoring](https://docs.sentry.io/platforms/php/tracing/instrumentation/caches-module/)
26 | - [Queue monitoring](https://docs.sentry.io/platforms/php/tracing/instrumentation/queues-module/)
27 | - Cron Monitoring
28 | - Session replay
29 | - Logrocket support
30 | - Sentry feedback form after an error
31 |
32 | ## Installation
33 | - `composer require justbetter/magento2-sentry`
34 | - `bin/magento module:enable JustBetter_Sentry`
35 | - `bin/magento setup:upgrade`
36 | - `bin/magento setup:di:compile`
37 | - `bin/magento setup:static-content:deploy`
38 |
39 | ## Configuration
40 | For configuration with Adobe Cloud, [check below](#configuration-for-adobe-cloud).
41 |
42 | This module uses the [Magento Deployment Configuration](https://devdocs.magento.com/guides/v2.3/config-guide/config/config-php.html) for most it's configuration. This means that you need to add this array to your `app/etc/env.php`:
43 |
44 | ```
45 | 'sentry' => [
46 | 'dsn' => 'example.com',
47 | 'logrocket_key' => 'example/example',
48 | 'environment' => null,
49 | 'log_level' => \Monolog\Level::Warning,
50 | 'error_types' => E_ALL,
51 | 'ignore_exceptions' => [],
52 | 'mage_mode_development' => false,
53 | 'js_sdk_version' => \JustBetter\Sentry\Block\SentryScript::CURRENT_VERSION,
54 | 'tracing_enabled' => true,
55 | 'traces_sample_rate' => 0.5,
56 | 'disable_default_integrations' => [
57 | \Sentry\Integration\ModulesIntegration::class,
58 | ],
59 | 'performance_tracking_enabled' => true,
60 | 'performance_tracking_excluded_areas' => [\Magento\Framework\App\Area::AREA_ADMINHTML, \Magento\Framework\App\Area::AREA_CRONTAB],
61 | 'profiles_sample_rate' => 0.5,
62 | 'ignore_js_errors' => [],
63 | 'enable_csp_report_url' => true,
64 | ]
65 | ```
66 |
67 | Next to that there are some configuration options under Stores > Configuration > JustBetter > Sentry.
68 |
69 | ### Configuration values
70 | | Name | Default | Description |
71 | |---|---|---|
72 | | `dsn` | — | The DSN you got from Sentry for your project. You can find the DSN in the project settings under "Client Key (DSN)" |
73 | | `release` | the current deployed version in `deployed_version.txt` | Specify the current release version. Example with dynamic git hash: `trim(exec('git --git-dir ' . BP . '/.git' . ' log --pretty="%h" -n1 HEAD'))` |
74 | | `mage_mode_development` | `false` | If set to true, you will receive issues in Sentry even if Magento is running in develop mode. |
75 | | `environment` | — | Specify the environment under which the deployed version is running. Common values: production, staging, development. Helps differentiate errors between environments. |
76 | | `max_breadcrumbs` | `100` | This variable controls the total amount of breadcrumbs that should be captured. |
77 | | `attach_stacktrace` | `false` | When enabled, stack traces are automatically attached to all messages logged. Even if they are not exceptions. |
78 | | `prefixes` | [[BP](https://github.com/magento/magento2/blob/9a62604c5a7ab70db1386d307b0dbfe596611102/app/autoload.php#L16)] | A list of prefixes that should be stripped from the filenames of captured stacktraces to make them relative. |
79 | | `sample_rate` | `1.0` | Configures the sample rate for error events, in the range of 0.0 to 1.0. |
80 | | `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]` |
81 | | `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`. |
82 | | `log_level` | `\Monolog\Level::Warning` | Specify from which logging level on Sentry should get the [messages](https://docs.sentry.io/platforms/php/usage/#capturing-messages). |
83 | | `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.) |
84 | | `tracing_enabled` | `false` | If set to true, tracing is enabled (bundle file is loaded automatically). |
85 | | `traces_sample_rate` | `0.2` | If tracing is enabled, set the sample rate. A number between 0 and 1, controlling the percentage chance a given transaction will be sent to Sentry. |
86 | | `traces_sample_rate_cli` | The value of `traces_sample_rate` | If tracing is enabled, set the sample rate for CLI. A number between 0 and 1, controlling the percentage chance a given transaction will be sent to Sentry. |
87 | | `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 | | `performance_tracking_enabled` | `false` | if performance tracking is enabled, a performance report gets generated for the request. |
89 | | `performance_tracking_excluded_areas` | `['adminhtml', 'crontab']` | if `performance_tracking_enabled` is enabled, we recommend to exclude the `adminhtml` & `crontab` area. |
90 | | `enable_logs` | `false` | This option enables the [logging integration](https://sentry.io/product/logs/), which allows the SDK to capture logs and send them to Sentry. |
91 | | `logger_log_level` | `\Monolog\Level::Notice` | If the logging integration is enabled, specify from which logging level the logger should log |
92 | | `js_sdk_version` | `\JustBetter\Sentry\Block\SentryScript::CURRENT_VERSION` | If set, loads the explicit version of the JavaScript SDK of Sentry. |
93 | | `ignore_js_errors` | `[]` | Array of JavaScript error messages which should not be sent to Sentry. (See also `ignoreErrors` in [Sentry documentation](https://docs.sentry.io/clients/javascript/config/)) |
94 | | `disable_default_integrations` | `[]` | Provide a list of FQCN of default integrations you do not want to use. [List of default integrations](https://github.com/getsentry/sentry-php/tree/master/src/Integration).|
95 | | `cron_monitoring_enabled` | `false` | Wether to enable [cron check ins](https://docs.sentry.io/platforms/php/crons/#upserting-cron-monitors) |
96 | | `track_crons` | `[]` | Cron handles of crons to track with cron monitoring, [Sentry only supports 6 check-ins per minute](https://docs.sentry.io/platforms/php/crons/#rate-limits) Magento does many more. |
97 | | `spotlight` | `false` | Enable [Spotlight](https://spotlightjs.com/) on the page |
98 | | `spotlight_url` | - | Override the [Sidecar url](https://spotlightjs.com/sidecar/) |
99 | | `enable_csp_report_url` | `false` | If set to true, the report-uri will be automatically added based on the DSN. |
100 |
101 | ### Configuration for Adobe Cloud
102 | Since Adobe Cloud doesn't allow you to add manually add content to the `env.php` file, the configuration can be done
103 | using the "Variables" in Adobe Commerce using the following variables:
104 |
105 | | Name | Type |
106 | |--------------------------------------------------|---------|
107 | | `CONFIG__SENTRY__ENVIRONMENT__ENABLED` | boolean |
108 | | `CONFIG__SENTRY__ENVIRONMENT__DSN` | string |
109 | | `CONFIG__SENTRY__ENVIRONMENT__MAGE_MODE_DEVELOPMENT` | string |
110 | | `CONFIG__SENTRY__ENVIRONMENT__ENVIRONMENT` | string |
111 | | `CONFIG__SENTRY__ENVIRONMENT__MAX_BREADCRUMBS` | integer |
112 | | `CONFIG__SENTRY__ENVIRONMENT__ATTACH_STACKTRACE` | boolean |
113 | | `CONFIG__SENTRY__ENVIRONMENT__SAMPLE_RATE` | float |
114 | | `CONFIG__SENTRY__ENVIRONMENT__IGNORE_EXCEPTIONS` | JSON array of classes |
115 | | `CONFIG__SENTRY__ENVIRONMENT__ERROR_TYPES` | integer |
116 | | `CONFIG__SENTRY__ENVIRONMENT__LOG_LEVEL` | integer |
117 | | `CONFIG__SENTRY__ENVIRONMENT__CLEAN_STACKTRACE` | boolean |
118 | | `CONFIG__SENTRY__ENVIRONMENT__TRACING_ENABLED` | boolean |
119 | | `CONFIG__SENTRY__ENVIRONMENT__TRACES_SAMPLE_RATE`| float |
120 | | `CONFIG__SENTRY__ENVIRONMENT__PROFILES_SAMPLE_RATE`| float |
121 | | `CONFIG__SENTRY__ENVIRONMENT__PERFORMANCE_TRACKING_ENABLED` | boolean |
122 | | `CONFIG__SENTRY__ENVIRONMENT__PERFORMANCE_TRACKING_EXCLUDED_AREAS` | boolean |
123 | | `CONFIG__SENTRY__ENVIRONMENT__ENABLE_LOGS` | boolean |
124 | | `CONFIG__SENTRY__ENVIRONMENT__LOGGER_LOG_LEVEL` | boolean |
125 | | `CONFIG__SENTRY__ENVIRONMENT__JS_SDK_VERSION` | string |
126 | | `CONFIG__SENTRY__ENVIRONMENT__IGNORE_JS_ERRORS` | JSON array of error messages |
127 |
128 | The following configuration settings can be overridden in the Magento admin. This is limited to ensure that changes to
129 | particular config settings can only be done on server level and can't be broken by changes in the admin.
130 |
131 | Please note, that it is not possible to use profiling within the Adobe Cloud.
132 |
133 | ## Optional error page configuration
134 | - Optional you can configure custom error pages in pub/errors. You can use the sentry feedback form and insert here the sentry log ID. The Sentry Log Id is captured in de customer session and can be retrieved in `processor.php`.
135 |
136 | ## Sending additional data to Sentry when logging errors
137 | - When calling any function from the [Psr\Log\LoggerInterface](https://github.com/php-fig/log/blob/master/src/LoggerInterface.php) you can pass any data to the parameter $context and it will be send to Sentry as 'Custom context'.
138 |
139 | ## Change / Filter events
140 | This module has an event called `sentry_before_send` that is dispatched before setting the config [before_send](https://docs.sentry.io/platforms/php/configuration/filtering/#using-platformidentifier-namebefore-send-). This provides the means to edit / filter events. You could for example add extra criteria to determine if the exception should be captured to Sentry. To prevent the Exception from being captured you can set the event to `null` or unset it completly.
141 |
142 | ```PHP
143 | public function execute(\Magento\Framework\Event\Observer $observer)
144 | {
145 | $observer->getEvent()->getSentryEvent()->unsEvent();
146 | }
147 | ```
148 |
149 | Example: https://github.com/justbetter/magento2-sentry-filter-events
150 |
151 | This same thing is the case for
152 | | | |
153 | |--------------------------------|-------------------------------------------------------------------------------------|
154 | | sentry_before_send | https://docs.sentry.io/platforms/php/configuration/options/#before_send |
155 | | sentry_before_send_transaction | https://docs.sentry.io/platforms/php/configuration/options/#before_send_transaction |
156 | | sentry_before_send_check_in | https://docs.sentry.io/platforms/php/configuration/options/#before_send_check_in |
157 | | sentry_before_breadcrumb | https://docs.sentry.io/platforms/php/configuration/options/#before_breadcrumb |
158 | | sentry_before_send_log | https://docs.sentry.io/platforms/php/configuration/options/#before_send_log |
159 |
160 | ## Compatibility
161 | The module is tested on Magento version 2.4.x with sentry sdk version 4.x. feel free to fork this project or make a pull request.
162 |
163 | ## Ideas, bugs or suggestions?
164 | Please create a [issue](https://github.com/justbetter/magento2-sentry/issues) or a [pull request](https://github.com/justbetter/magento2-sentry/pulls).
165 |
166 | ## Contributing
167 | Contributing? Awesome! Thank you for your help improving the module!
168 |
169 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
170 |
171 | Most importantly:
172 | - When making a PR please add a description what you've added, and if relevant why.
173 | - To save time on codestyle feedback, please run
174 | - `composer install`
175 | - `composer run codestyle`
176 | - `composer run analyse`
177 |
178 | ## Security Vulnerabilities
179 |
180 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
181 |
182 | ## License
183 |
184 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
185 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | ## [4.5.2] - 2025-11-27
3 | ### Fixed
4 | * Fixed undefined message properties on queues (https://github.com/justbetter/magento2-sentry/pull/221)
5 | * Fixed error thrown on new setup:install (https://github.com/justbetter/magento2-sentry/pull/223)
6 | * Fixed activating sentry with invalid DSN (https://github.com/justbetter/magento2-sentry/pull/222)
7 | ## [4.5.1] - 2025-11-11
8 | ### Added
9 | * Set Magento BP as default prefix
10 | ## [4.5.0] - 2025-11-04
11 | ### Added
12 | * Added Logger support (https://github.com/justbetter/magento2-sentry/pull/201)
13 | * Added support for manual release defnition (https://github.com/justbetter/magento2-sentry/pull/204)
14 | * Added custom SDK identifier to module (https://github.com/justbetter/magento2-sentry/pull/208)
15 | * Added Cache instrumentation (https://github.com/justbetter/magento2-sentry/pull/206)
16 | * Added Queue instrumentation (https://github.com/justbetter/magento2-sentry/pull/210)
17 | * add csp report-uri if none is configured (https://github.com/justbetter/magento2-sentry/pull/216) thanks to https://github.com/rommelfreddy
18 | ### Changed
19 | * Replaced deprecated \Monolog\Logger::Warning (https://github.com/justbetter/magento2-sentry/pull/214) thanks to https://github.com/GeKVe
20 | * use configured dsn-hostname for csp connect-src policy (https://github.com/justbetter/magento2-sentry/pull/215) thanks to https://github.com/rommelfreddy
21 | ## [4.4.0] - 2025-07-14
22 | ### Fixed
23 | * Catch zend db adapter errors when database is missing (https://github.com/justbetter/magento2-sentry/pull/193)
24 | * Fix logrocket key type
25 | ### Added
26 | * Add the ability to select job codes using regex (https://github.com/justbetter/magento2-sentry/pull/197)
27 | * Added support for [spotlight](https://spotlightjs.com/) (https://github.com/justbetter/magento2-sentry/pull/195)
28 | ## [4.3.0] - 2025-06-24
29 | ### Fixed
30 | * Catch The default website isnt defined when getting store (https://github.com/justbetter/magento2-sentry/pull/188)
31 | ### Added
32 | * Add sentry logger by hooking into monolog setHandlers (https://github.com/justbetter/magento2-sentry/pull/184)
33 | * Added support for cron check-ins (https://github.com/justbetter/magento2-sentry/pull/182)
34 | ## [4.2.0] - 2025-06-11
35 | ### Added
36 | * Add performance sampling by (https://github.com/justbetter/magento2-sentry/pull/178) thanks to https://github.com/indykoning, https://github.com/barryvdh and https://github.com/rommelfreddy
37 | * Automatically pass all supported sentry config (https://github.com/justbetter/magento2-sentry/pull/177)
38 | ## [4.1.0] - 2025-06-04
39 | ### Added
40 | * Clean up the Magento stacktrace before sending it to Sentry (https://github.com/justbetter/magento2-sentry/pull/171)
41 | * Pass config also relevant to sentry (https://github.com/justbetter/magento2-sentry/pull/172)
42 | ### Fixed
43 | * Gather ip from Magento instead of server variable (https://github.com/justbetter/magento2-sentry/pull/174)
44 | ## [4.0.1] - 2025-05-06
45 | ### Fixed
46 | * Load customerSession in SentryLog via proxy (Fixing https://github.com/justbetter/magento2-sentry/issues/160) (https://github.com/justbetter/magento2-sentry/pull/169) thanks to https://github.com/brosenberger
47 | ## [4.0.0] - 2025-04-23
48 | ### Added
49 | * Moved sentry logging to a monolog handler (https://github.com/justbetter/magento2-sentry/pull/165)
50 | * Added support for Magento 2.4.8
51 | * Added support for Monolog V3
52 | ### Changed
53 | * Dynamically add sentry and logrocket domains to CSP whitelist (https://github.com/justbetter/magento2-sentry/pull/156) thanks to https://github.com/brosenberger
54 | * Use secureRenderer for including sentry frontend js (https://github.com/justbetter/magento2-sentry/pull/157) thanks to https://github.com/brosenberger
55 | ## [3.9.0] - 2025-01-31
56 | ### Added
57 | * Raised PHPStan level to 5 (https://github.com/justbetter/magento2-sentry/pull/149)
58 | * Added user context to logging and errors when possible (https://github.com/justbetter/magento2-sentry/pull/133 & https://github.com/justbetter/magento2-sentry/pull/152)
59 | * Allow overriding some config per store (https://github.com/justbetter/magento2-sentry/pull/155) thanks to https://github.com/bruno-blackbird
60 | ## [3.8.0] - 2024-08-09
61 | ### Added
62 | * Added option to exclude default-integrations (https://github.com/justbetter/magento2-sentry/pull/146) thanks to https://github.com/rommelfreddy
63 | ### Removed
64 | * Removed promise-polyfill (https://github.com/justbetter/magento2-sentry/pull/142) thanks to https://github.com/rommelfreddy
65 | ## [3.7.2] - 2024-07-04
66 | ### Fixed
67 | * fix initialisation of replay integration (https://github.com/justbetter/magento2-sentry/pull/143) thanks to https://github.com/rommelfreddy
68 | ## [3.7.1] - 2024-06-25
69 | ### Fixed
70 | * Fix CSP after Polyfill changes (https://github.com/justbetter/magento2-sentry/pull/141) thanks to https://github.com/sprankhub
71 | ## [3.7.0] - 2024-06-25
72 | ### Fixed
73 | * Replace polyfill CDN with fastly (https://github.com/justbetter/magento2-sentry/pull/140) thanks to https://github.com/barryvdh
74 | ### Added
75 | * Enable INP when tracing is enabled (https://github.com/justbetter/magento2-sentry/pull/137) thanks to https://github.com/amenk
76 | ## [3.6.0] - 2024-03-29
77 | This release drops support for php 7.4 as it has been completely EOL for over a year. [https://www.php.net/supported-versions.php](https://www.php.net/supported-versions.php)
78 | ### Fixed
79 | * Check if Sentry is defined before running init (https://github.com/justbetter/magento2-sentry/pull/129) thanks to https://github.com/netzkollektiv
80 | ### Changed
81 | * Use property promotions (https://github.com/justbetter/magento2-sentry/pull/130) thanks to https://github.com/cirolosapio
82 | * Raised sentry/sdk version to 4.0+
83 | ## [3.5.2] - 2024-03-18
84 | ### Fixed
85 | * Fix start errors without database connection (https://github.com/justbetter/magento2-sentry/pull/125) thanks to https://github.com/fredden
86 | ## [3.5.1] - 2023-12-21
87 | ### Fixed
88 | * Fix getting SCOPE_STORES from incorrect ScopeInterface (https://github.com/justbetter/magento2-sentry/pull/123) thanks to https://github.com/peterjaap
89 | * Improve extendibility by changing self to static (https://github.com/justbetter/magento2-sentry/pull/122) thanks to https://github.com/peterjaap
90 | ## [3.5.0] - 2023-12-20
91 | ### Added
92 | * Added support for configuration using System Configuration (https://github.com/justbetter/magento2-sentry/pull/121) thanks to https://github.com/ArjenMiedema
93 | ## [3.4.0] - 2023-06-14
94 | ### Added
95 | * Added `sentry_before_send` event (https://github.com/justbetter/magento2-sentry/pull/117) thanks to https://github.com/rbnmulder
96 | * Added support to ignore javascript errors (https://github.com/justbetter/magento2-sentry/pull/102) thanks to https://github.com/rommelfreddy
97 | ## [3.3.1] - 2023-03-15
98 | ### Fixed
99 | * Fixed PHP 8.2 deprecation errors (https://github.com/justbetter/magento2-sentry/pull/114) thanks to https://github.com/peterjaap
100 | ## [3.3.0] - 2023-03-01
101 | ### Added
102 | * Added Session Replay functionality
103 | ## [3.2.0] - 2022-07-07
104 | ### Fixed
105 | * Changed addAlert to addRecord for Test error (https://github.com/justbetter/magento2-sentry/pull/98) thanks to https://github.com/peterjaap
106 | ### Added
107 | * Send context data top Sentry as Custom Data (https://github.com/justbetter/magento2-sentry/pull/97) thanks to https://github.com/oneserv-heuser
108 | ## [3.1.0] - 2022-06-14
109 | ### Fixed
110 | * Update addRecord function for Monolog 2.7+ (https://github.com/justbetter/magento2-sentry/pull/92) thanks to https://github.com/torhoehn
111 | ## [3.0.2] - 2022-06-14
112 | ### Added
113 | * Added sentry_before_init event to GlobalExceptionCatcher (https://github.com/justbetter/magento2-sentry/pull/90) thanks to https://github.com/peterjaap
114 | ## [3.0.1] - 2022-05-10
115 | ### Fixed
116 | * Fix problems with logout during product edit in admin panel when Chrome DevTools is open (https://github.com/justbetter/magento2-sentry/pull/86) thanks to https://github.com/trungpq2711
117 | ## [3.0.0] - 2022-04-13
118 | Breaking: New version (2.0.0+) for Monolog will be required.
119 | ### Fixed
120 | * Fixed PHP 8.1 support with monolog 2.0.0+ (https://github.com/justbetter/magento2-sentry/pull/85) thanks to https://github.com/peterjaap
121 | ## [2.6.1] - 2022-04-13
122 | ### Changed
123 | * Updated constraints for monolog versions lower than 2.0.0
124 | ## [2.6.0] - 2021-04-08
125 | ### Added
126 | * Add option to filter the severity of ErrorExceptions being sent to Sentry
127 | * Add option to ignore specific Exception classes
128 | ## [2.5.4] - 2021-02-10
129 | ### Fixed
130 | * Fixed capital in dataHelper (https://github.com/justbetter/magento2-sentry/pull/74) thanks to https://github.com/peterjaap
131 | ## [2.5.3] - 2021-02-03
132 | ### Added
133 | * Add option to strip static content version and store code from urls that get sent to Sentry (https://github.com/justbetter/magento2-sentry/pull/73) thanks to https://github.com/peterjaap
134 | ## [2.5.2] - 2021-01-18
135 | ### Fixed
136 | * Fix monolog plugin still adding monolog as argument
137 | ## [2.5.1] - 2021-01-18
138 | ### Fixed
139 | * Merged PR - Use isset to check for custom_tags in context (https://github.com/justbetter/magento2-sentry/pull/71) thanks to https://github.com/matthiashamacher
140 | ## [2.5.0] - 2021-01-15
141 | ### Changed
142 | * Merged PR - Remove monolog class in function, add custom tag functionality (https://github.com/justbetter/magento2-sentry/pull/66) thanks to https://github.com/matthiashamacher
143 | This is a breaking change on SentryLog::send(), the function required monolog before, this has been removed
144 | ## [2.4.0] - 2020-12-07
145 | ### Added
146 | * Merged PR - Add csp_whitelist.xml (https://github.com/justbetter/magento2-sentry/pull/65) thanks to https://github.com/matthiashamacher
147 | ### Changed
148 | * Merged PR - Update Sentry version (https://github.com/justbetter/magento2-sentry/pull/64) thanks to https://github.com/matthiashamacher
149 | ## [2.3.2] - 2020-11-30
150 | ### Added
151 | * Merged PR - Adding environment to js integration (https://github.com/justbetter/magento2-sentry/pull/62) thanks to https://github.com/matthiashamacher
152 | ## [2.3.1] - 2020-07-31
153 | ### Added
154 | * Merged PR - Add option to enable or disable Php error tracking (https://github.com/justbetter/magento2-sentry/pull/59) thanks to https://github.com/t9toqwerty
155 | ## [2.3.0] - 2020-06-29
156 | ### Added
157 | * Merged PR - Added version tagging to JavaScript (https://github.com/justbetter/magento2-sentry/pull/54) thanks to https://github.com/JKetelaar
158 | * Added LogRocket support
159 | ### Changed
160 | * Better feedback if configuration prevents the module from running
161 |
162 | ## [2.2.3] - 2020-02-25
163 | ### Fixed
164 | * Merged PR - Release version should be string (https://github.com/justbetter/magento2-sentry/pull/49) thanks to https://github.com/DominicWatts
165 |
166 | ## [2.2.2] - 2020-02-11
167 | ### Changed
168 | * Merged PR - Change behavior to check of script tag can be used (https://github.com/justbetter/magento2-sentry/pull/48) thanks to https://github.com/adamj88
169 | * Updated changelog format to folow more closely to https://keepachangelog.com/
170 |
171 | ## [2.2.1] - 2019-12-12
172 | ### Changed
173 | * Merged PR - fix for requirements sdk (https://github.com/justbetter/magento2-sentry/pull/42) thanks to https://github.com/jupiterhs
174 |
175 | ## [2.2.0] - 2019-12-12
176 | ### Changed
177 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/37) thanks to https://github.com/DominicWatts
178 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/38) thanks to https://github.com/matthiashamacher
179 |
180 | ## [2.1.0] - 2019-11-22
181 | ### Changed
182 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/33) thanks to https://github.com/DominicWatts
183 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/35) thanks to https://github.com/peterjaap
184 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/36) thanks to https://github.com/peterjaap
185 | * Refactor of proxy classes in di.xml
186 | ### Fixed
187 | * Fixed all PHPcs warnings and errors
188 |
189 | ## [2.0.0] - 2019-11-19
190 | ### Changed
191 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/29) thanks to https://github.com/michielgerritsen
192 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/26) thanks to https://github.com/JosephMaxwell
193 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/27) thanks to https://github.com/DominicWatts
194 |
195 | ## [0.8.0] - 2019-08-29
196 | ### Changed
197 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/24) thanks to https://github.com/kyriog
198 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/22) thanks to https://github.com/fredden
199 | * Merged PR (https://github.com/justbetter/magento2-sentry/pull/21) thanks to https://github.com/erikhansen
200 |
201 | ## [0.7.2] - 2019-06-19
202 | ### Changed
203 | * Reverted async attribute
204 |
205 | ## [0.7.1] - 2019-06-19
206 | ### Added
207 | * Added async and crossorigin attribute to script tags
208 |
209 | ## [0.7.0] - 2019-04-23
210 | ### Fixed
211 | * Merged pull request avoiding magento crash (https://github.com/justbetter/magento2-sentry/pull/19)
212 |
213 | ## [0.6.2] - 2019-04-05
214 | ### Fixed
215 | * Fixed issues useing referenceBlock when adding scripts (https://github.com/justbetter/magento2-sentry/issues/18)
216 |
217 | ## [0.6.1] - 2019-02-14
218 | ### Added
219 | * Added missing files of PR 13 (https://github.com/justbetter/magento2-sentry/pull/16)
220 |
221 | ## [0.6.0] - 2019-02-14
222 | ### Added
223 | * Added Sentry script tag to catch javascript errors (https://github.com/justbetter/magento2-sentry/pull/13)
224 |
225 | ## [0.5.1] - 2019-02-13
226 | ### Added
227 | * Support for setting environment (https://github.com/justbetter/magento2-sentry/pull/12)
228 |
229 | ## [0.5.0] - 2018-12-07
230 | ### Added
231 | * Send extra parameters to sentry (https://github.com/justbetter/magento2-sentry/issues/11)
232 | * Added Magento 2.3.x support & dropped 2.1.x support
233 | ### Fixed
234 | * Fixed area code not set or already set
235 |
236 | ## [0.4.2] - 2018-10-17
237 | ### Fixed
238 | * Bugfix with area code already set - removed area code from constructor with causes problems at random cases. (https://github.com/justbetter/magento2-sentry/issues/10)
239 | ### Changed
240 | * Removed info level - this log level is not useable in sentry.
241 |
242 | ## [0.4.1] - 2018-08-15
243 | ### Fixed
244 | * Removed plugin in di.xml to prevent fatal crash on storefrontend
245 | * Restored preference in di.xml
246 |
247 | ## [0.4.0] - 2018-08-10
248 | ### Added
249 | * Added user context
250 | * Added message context
251 | * Added extra magento store parameters
252 | ### Changed
253 | * Refactor of overwrite to plugin with monolog
254 | * Refactor of ExceptionCatcher to use same SentryLogger
255 | * Refactor a lot of code
256 | * PSR2 compliance
257 | ### Fixed
258 | * Bugfix area code not set
259 |
260 | ## [0.2.1] - 2018-04-30
261 | ### Changed
262 | * downgraded requirement monolog for magento 2.1.x
263 |
264 | ## [0.2.0] - 2018-04-30
265 | ### Added
266 | * Feature request to test sentry in development mode (https://github.com/justbetter/magento2-sentry/issues/4)
267 | * Added mage deploy mode to sentry in context_tags
268 |
269 | ## [0.1.0] - 2018-04-17
270 | ### Added
271 | * Feature request for sending test events (https://github.com/justbetter/magento2-sentry/issues/3)
272 | * Added ACL roles for editing sentry config
273 |
274 | ## [0.0.6] - 2018-03-25
275 | ### Fixed
276 | * Bugfix wrong file commit
277 |
278 | ## [0.0.5] - 2018-03-17
279 | ### Added
280 | * Initial commit module - basic working module
281 |
--------------------------------------------------------------------------------
/Helper/Data.php:
--------------------------------------------------------------------------------
1 | ['type' => 'string'],
33 | 'release' => ['type' => 'string'],
34 | 'environment' => ['type' => 'string'],
35 | 'max_breadcrumbs' => ['type' => 'int'],
36 | 'attach_stacktrace' => ['type' => 'bool'],
37 | 'send_default_pii' => ['type' => 'bool'],
38 | 'server_name' => ['type' => 'string'],
39 | 'in_app_include' => ['type' => 'array'],
40 | 'in_app_exclude' => ['type' => 'array'],
41 | 'prefixes' => ['type' => 'array'],
42 | 'max_request_body_size' => ['type' => 'string'],
43 | 'max_value_length' => ['type' => 'int'],
44 | // https://docs.sentry.io/platforms/php/configuration/options/#error-monitoring-options
45 | 'sample_rate' => ['type' => 'float'],
46 | 'ignore_exceptions' => ['type' => 'array'],
47 | 'error_types' => ['type' => 'int'],
48 | 'context_lines' => ['type' => 'int'],
49 | // https://docs.sentry.io/platforms/php/configuration/options/#tracing-options
50 | 'traces_sample_rate' => ['type' => 'float'],
51 | 'ignore_transactions' => ['type' => 'array'],
52 | 'trace_propagation_targets' => ['type' => 'array'],
53 | // https://docs.sentry.io/platforms/php/profiling/#enabling-profiling
54 | 'profiles_sample_rate' => ['type' => 'float'],
55 | // https://docs.sentry.io/platforms/php/configuration/options/#logs-options
56 | 'enable_logs' => ['type' => 'bool'],
57 | // https://docs.sentry.io/platforms/php/configuration/options/#transport-options
58 | 'http_proxy' => ['type' => 'string'],
59 | 'http_connect_timeout' => ['type' => 'int'],
60 | 'http_timeout' => ['type' => 'int'],
61 | // https://spotlightjs.com/
62 | 'spotlight' => ['type' => 'bool'],
63 | 'spotlight_url' => ['type' => 'string'],
64 | ];
65 |
66 | /**
67 | * @var ScopeConfigInterface
68 | */
69 | protected $scopeConfig;
70 |
71 | /**
72 | * @var array
73 | */
74 | protected $config = [];
75 |
76 | /**
77 | * @var ?bool
78 | */
79 | protected $isActive = null;
80 |
81 | /**
82 | * @var array
83 | */
84 | protected $configKeys = [
85 | ...self::NATIVE_SENTRY_CONFIG_KEYS,
86 | 'logrocket_key' => ['type' => 'string'],
87 | 'log_level' => ['type' => 'int'],
88 | 'logger_log_level' => ['type' => 'int', 'default' => 300 /* \Monolog\Level::Warning */],
89 | 'errorexception_reporting' => ['type' => 'int'], /* @deprecated by @see: error_types https://docs.sentry.io/platforms/php/configuration/options/#error_types */
90 | 'mage_mode_development' => ['type' => 'bool'],
91 | 'js_sdk_version' => ['type' => 'string'],
92 | 'tracing_enabled' => ['type' => 'bool'],
93 | 'tracing_sample_rate' => ['type' => 'float'], /* @deprecated by @see: traces_sample_rate https://docs.sentry.io/platforms/php/configuration/options/#error_types */
94 | 'traces_sample_rate_cli' => ['type' => 'float'],
95 | 'performance_tracking_enabled' => ['type' => 'bool'],
96 | 'performance_tracking_excluded_areas' => ['type' => 'array'],
97 | 'ignore_js_errors' => ['type' => 'array'],
98 | 'disable_default_integrations' => ['type' => 'array'],
99 | 'clean_stacktrace' => ['type' => 'bool'],
100 | 'cron_monitoring_enabled' => ['type' => 'bool'],
101 | 'track_crons' => ['type' => 'array'],
102 | 'enable_csp_report_url' => ['type' => 'bool'],
103 | ];
104 |
105 | /**
106 | * Data constructor.
107 | *
108 | * @param Context $context
109 | * @param StoreManagerInterface $storeManager
110 | * @param State $appState
111 | * @param Json $serializer
112 | * @param ProductMetadataInterface $productMetadataInterface
113 | * @param DeploymentConfig $deploymentConfig
114 | */
115 | public function __construct(
116 | Context $context,
117 | protected StoreManagerInterface $storeManager,
118 | protected State $appState,
119 | private Json $serializer,
120 | protected ProductMetadataInterface $productMetadataInterface,
121 | protected DeploymentConfig $deploymentConfig
122 | ) {
123 | $this->scopeConfig = $context->getScopeConfig();
124 | $this->collectModuleConfig();
125 |
126 | parent::__construct($context);
127 | }
128 |
129 | /**
130 | * Get the sentry DSN.
131 | *
132 | * @return mixed
133 | */
134 | public function getDSN()
135 | {
136 | return $this->collectModuleConfig()['dsn'];
137 | }
138 |
139 | /**
140 | * Get the sentry release.
141 | *
142 | * @return string|null
143 | */
144 | public function getRelease(): ?string
145 | {
146 | return $this->collectModuleConfig()['release'];
147 | }
148 |
149 | /**
150 | * Get the sentry prefixes.
151 | *
152 | * @return array
153 | */
154 | public function getPrefixes(): array
155 | {
156 | $prefixes = $this->collectModuleConfig()['prefixes'] ?? [];
157 | if (defined('BP')) {
158 | $prefixes[] = BP;
159 | }
160 |
161 | return $prefixes;
162 | }
163 |
164 | /**
165 | * Whether cron monitoring (check-in events) is enabled.
166 | */
167 | public function isCronMonitoringEnabled(): bool
168 | {
169 | return $this->collectModuleConfig()['cron_monitoring_enabled'] ?? false;
170 | }
171 |
172 | /**
173 | * Get a list of crons to track.
174 | */
175 | public function getTrackCrons(): array
176 | {
177 | return $this->collectModuleConfig()['track_crons'] ?? [];
178 | }
179 |
180 | /**
181 | * Whether tracing is enabled.
182 | */
183 | public function isTracingEnabled(): bool
184 | {
185 | return $this->collectModuleConfig()['tracing_enabled'] ?? false;
186 | }
187 |
188 | /**
189 | * Whether spotlight is enabled.
190 | *
191 | * Enabling spotlight will implicitly enable override production mode.
192 | */
193 | public function isSpotlightEnabled(): bool
194 | {
195 | return ($this->collectModuleConfig()['spotlight'] ?? false) && !$this->isProductionMode();
196 | }
197 |
198 | /**
199 | * Get sample rate for tracing.
200 | */
201 | public function getTracingSampleRate(): float
202 | {
203 | return (float) ($this->collectModuleConfig()['traces_sample_rate'] ?? $this->collectModuleConfig()['tracing_sample_rate'] ?? 0.2);
204 | }
205 |
206 | /**
207 | * Get sample rate for tracing in CLI.
208 | */
209 | public function getTracingSampleRateCli(): float
210 | {
211 | return (float) ($this->collectModuleConfig()['traces_sample_rate_cli'] ?? $this->getTracingSampleRate());
212 | }
213 |
214 | /**
215 | * Get a list of integrations to disable.
216 | */
217 | public function getDisabledDefaultIntegrations(): array
218 | {
219 | return $this->config['disable_default_integrations'] ?? [];
220 | }
221 |
222 | /**
223 | * Get list of js errors to ignore.
224 | *
225 | * @return array|null
226 | */
227 | public function getIgnoreJsErrors()
228 | {
229 | return $this->collectModuleConfig()['ignore_js_errors'];
230 | }
231 |
232 | /**
233 | * Get the sdk version string.
234 | *
235 | * @return string the version of the js sdk of Sentry
236 | */
237 | public function getJsSdkVersion(): string
238 | {
239 | return $this->collectModuleConfig()['js_sdk_version'] ?: SentryScript::CURRENT_VERSION;
240 | }
241 |
242 | /**
243 | * Get the current environment.
244 | *
245 | * @return mixed
246 | */
247 | public function getEnvironment()
248 | {
249 | return $this->collectModuleConfig()['environment'] ?? 'default';
250 | }
251 |
252 | /**
253 | * Retrieve config values.
254 | *
255 | * @param string $field
256 | * @param int|string|null $storeId
257 | *
258 | * @return mixed
259 | */
260 | public function getConfigValue($field, $storeId = null)
261 | {
262 | return $this->scopeConfig->getValue(
263 | $field,
264 | ScopeInterface::SCOPE_STORE,
265 | $storeId
266 | );
267 | }
268 |
269 | /**
270 | * Retrieve Sentry General config values.
271 | *
272 | * @param string $code
273 | * @param null $storeId
274 | *
275 | * @return mixed
276 | */
277 | public function getGeneralConfig($code, $storeId = null)
278 | {
279 | return $this->getConfigValue(static::XML_PATH_SRS.$code, $storeId);
280 | }
281 |
282 | /**
283 | * Get the store id of the current store.
284 | *
285 | * @return int
286 | */
287 | public function getStoreId(): int
288 | {
289 | return $this->getStore()?->getId() ?? 0;
290 | }
291 |
292 | /**
293 | * Gather all configuration.
294 | *
295 | * @return array
296 | */
297 | public function collectModuleConfig(): array
298 | {
299 | $storeId = $this->getStoreId();
300 | if (isset($this->config[$storeId]['enabled'])) {
301 | return $this->config[$storeId];
302 | }
303 |
304 | try {
305 | $this->config[$storeId]['enabled'] = $this->scopeConfig->getValue('sentry/environment/enabled', ScopeInterface::SCOPE_STORE)
306 | ?? $this->deploymentConfig->get('sentry') !== null;
307 | } catch (TableNotFoundException|FileSystemException|RuntimeException|DomainException|Zend_Db_Adapter_Exception $e) {
308 | $this->config[$storeId]['enabled'] = $this->deploymentConfig->get('sentry') !== null;
309 | }
310 |
311 | foreach ($this->configKeys as $key => $config) {
312 | try {
313 | $value = $this->scopeConfig->getValue('sentry/environment/'.$key, ScopeInterface::SCOPE_STORE)
314 | ?? $this->deploymentConfig->get('sentry/'.$key);
315 | } catch (TableNotFoundException|FileSystemException|RuntimeException|DomainException|Zend_Db_Adapter_Exception $e) {
316 | $value = $this->deploymentConfig->get('sentry/'.$key);
317 | }
318 |
319 | $this->config[$storeId][$key] = $this->processConfigValue(
320 | $value,
321 | $config
322 | );
323 | }
324 |
325 | return $this->config[$storeId];
326 | }
327 |
328 | /**
329 | * Parse the config value to the type defined in the config.
330 | *
331 | * @param mixed $value
332 | * @param array $config
333 | *
334 | * @return mixed
335 | */
336 | public function processConfigValue(mixed $value, array $config): mixed
337 | {
338 | if ($value === null) {
339 | return $config['default'] ?? null;
340 | }
341 |
342 | return match ($config['type']) {
343 | 'array' => is_array($value) ? $value : $this->serializer->unserialize($value),
344 | 'int' => (int) $value,
345 | 'float' => (float) $value,
346 | 'bool' => (bool) $value,
347 | 'string' => (string) $value,
348 | default => $value,
349 | };
350 | }
351 |
352 | /**
353 | * Whether Sentry is active.
354 | *
355 | * @return bool
356 | */
357 | public function isActive(): bool
358 | {
359 | return $this->isActive ??= $this->isActiveWithReason()['active'];
360 | }
361 |
362 | /**
363 | * Whether sentry is active, adding a reason why not.
364 | *
365 | * @return array
366 | */
367 | public function isActiveWithReason(): array
368 | {
369 | $reasons = [];
370 | $config = $this->collectModuleConfig();
371 | $emptyConfig = empty($config);
372 | $configEnabled = isset($config['enabled']) && $config['enabled'];
373 | $dsn = $this->getDSN();
374 | $productionMode = ($this->isProductionMode() || $this->isOverwriteProductionMode());
375 |
376 | if ($emptyConfig) {
377 | $reasons[] = __('Config is empty.');
378 | }
379 | if (!$configEnabled) {
380 | $reasons[] = __('Module is not enabled in config.');
381 | }
382 | if (!$dsn && !$this->isSpotlightEnabled()) {
383 | $reasons[] = __('DSN is empty.');
384 | }
385 | if ($dsn) {
386 | try {
387 | \Sentry\Dsn::createFromString($dsn);
388 | } catch (\InvalidArgumentException $e) {
389 | $reasons[] = $e->getMessage();
390 | }
391 | }
392 | if (!$productionMode) {
393 | $reasons[] = __('Not in production and development mode is false.');
394 | }
395 |
396 | return count($reasons) > 0 ? ['active' => false, 'reasons' => $reasons] : ['active' => true];
397 | }
398 |
399 | /**
400 | * Whether the application is in production.
401 | *
402 | * @return bool
403 | */
404 | public function isProductionMode(): bool
405 | {
406 | return $this->appState->emulateAreaCode(Area::AREA_GLOBAL, [$this, 'getAppState']) === 'production';
407 | }
408 |
409 | /**
410 | * Get the current mode.
411 | *
412 | * @return string
413 | */
414 | public function getAppState(): string
415 | {
416 | return $this->appState->getMode();
417 | }
418 |
419 | /**
420 | * Is sentry enabled on development mode?
421 | *
422 | * @return bool
423 | */
424 | public function isOverwriteProductionMode(): bool
425 | {
426 | $config = $this->collectModuleConfig();
427 |
428 | return (isset($config['mage_mode_development']) && $config['mage_mode_development']) || $this->isSpotlightEnabled();
429 | }
430 |
431 | /**
432 | * Get the current magento version.
433 | *
434 | * @return string
435 | */
436 | public function getMagentoVersion(): string
437 | {
438 | return $this->productMetadataInterface->getVersion();
439 | }
440 |
441 | /**
442 | * Get the current store.
443 | */
444 | public function getStore(): ?\Magento\Store\Api\Data\StoreInterface
445 | {
446 | try {
447 | return $this->storeManager->getStore();
448 | } catch (DomainException|Zend_Db_Adapter_Exception|NoSuchEntityException $e) {
449 | // If the store is not available, return null
450 | return null;
451 | }
452 | }
453 |
454 | /**
455 | * Should the stacktrace get cleaned up?
456 | *
457 | * @return bool
458 | */
459 | public function getCleanStacktrace(): bool
460 | {
461 | return $this->collectModuleConfig()['clean_stacktrace'] ?? true;
462 | }
463 |
464 | /**
465 | * Is php tracking enabled?
466 | *
467 | * @return bool
468 | */
469 | public function isPhpTrackingEnabled(): bool
470 | {
471 | return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_php_tracking', ScopeInterface::SCOPE_STORE);
472 | }
473 |
474 | /**
475 | * Is php performance tracking enabled?
476 | *
477 | * @return bool
478 | */
479 | public function isPerformanceTrackingEnabled(): bool
480 | {
481 | return $this->isTracingEnabled() && ($this->collectModuleConfig()['performance_tracking_enabled'] ?? false);
482 | }
483 |
484 | /**
485 | * Get excluded Magento areas which should be not profiled.
486 | *
487 | * @return string[]
488 | */
489 | public function getPerformanceTrackingExcludedAreas(): array
490 | {
491 | return $this->collectModuleConfig()['performance_tracking_excluded_areas'] ?? [Area::AREA_ADMINHTML, Area::AREA_CRONTAB];
492 | }
493 |
494 | /**
495 | * Is the script tag enabled?
496 | *
497 | * @return bool
498 | */
499 | public function useScriptTag()
500 | {
501 | return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_script_tag', ScopeInterface::SCOPE_STORE);
502 | }
503 |
504 | /**
505 | * Whether to enable session replay.
506 | */
507 | public function useSessionReplay(): bool
508 | {
509 | return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_session_replay', ScopeInterface::SCOPE_STORE);
510 | }
511 |
512 | /**
513 | * Get the session replay sample rate.
514 | */
515 | public function getReplaySessionSampleRate(): float
516 | {
517 | return $this->getConfigValue(static::XML_PATH_SRS.'replay_session_sample_rate') ?? 0.1;
518 | }
519 |
520 | /**
521 | * Get the session replay error sample rate.
522 | */
523 | public function getReplayErrorSampleRate(): float
524 | {
525 | return $this->getConfigValue(static::XML_PATH_SRS.'replay_error_sample_rate') ?? 1;
526 | }
527 |
528 | /**
529 | * Whether to block media during replay.
530 | */
531 | public function getReplayBlockMedia(): bool
532 | {
533 | return $this->getConfigValue(static::XML_PATH_SRS.'replay_block_media') ?? true;
534 | }
535 |
536 | /**
537 | * Whether to show mask text.
538 | */
539 | public function getReplayMaskText(): bool
540 | {
541 | return $this->getConfigValue(static::XML_PATH_SRS.'replay_mask_text') ?? true;
542 | }
543 |
544 | /**
545 | * Should we load the script tag in the current block?
546 | *
547 | * @param string $blockName
548 | *
549 | * @return bool
550 | */
551 | public function showScriptTagInThisBlock($blockName): bool
552 | {
553 | $config = $this->getGeneralConfig('script_tag_placement');
554 |
555 | if (!$config) {
556 | return false;
557 | }
558 |
559 | $name = 'sentry.'.$config;
560 |
561 | return $name === $blockName;
562 | }
563 |
564 | /**
565 | * Get logrocket key.
566 | *
567 | * @return mixed
568 | */
569 | public function getLogrocketKey()
570 | {
571 | return $this->collectModuleConfig()['logrocket_key'];
572 | }
573 |
574 | /**
575 | * Whether to use logrocket.
576 | *
577 | * @return bool
578 | */
579 | public function useLogrocket(): bool
580 | {
581 | return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'use_logrocket') &&
582 | isset($this->collectModuleConfig()['logrocket_key']) &&
583 | $this->getLogrocketKey() !== null;
584 | }
585 |
586 | /**
587 | * Should logrocket identify.
588 | *
589 | * @return bool
590 | */
591 | public function useLogrocketIdentify(): bool
592 | {
593 | return $this->scopeConfig->isSetFlag(
594 | static::XML_PATH_SRS.'logrocket_identify',
595 | ScopeInterface::SCOPE_STORE
596 | );
597 | }
598 |
599 | /**
600 | * Whether to remove static content versioning from the url sent to sentry.
601 | *
602 | * @return bool
603 | */
604 | public function stripStaticContentVersion(): bool
605 | {
606 | return $this->scopeConfig->isSetFlag(
607 | static::XML_PATH_SRS_ISSUE_GROUPING.'strip_static_content_version',
608 | ScopeInterface::SCOPE_STORE
609 | );
610 | }
611 |
612 | /**
613 | * Whether to remove store code from the url sent to sentry.
614 | *
615 | * @return bool
616 | */
617 | public function stripStoreCode(): bool
618 | {
619 | return $this->scopeConfig->isSetFlag(
620 | static::XML_PATH_SRS_ISSUE_GROUPING.'strip_store_code',
621 | ScopeInterface::SCOPE_STORE
622 | );
623 | }
624 |
625 | /**
626 | * Get the ErrorException reporting level we will send at.
627 | *
628 | * @return int
629 | */
630 | public function getErrorTypes(): int
631 | {
632 | return (int) ($this->collectModuleConfig()['error_types'] ?? $this->collectModuleConfig()['errorexception_reporting'] ?? error_reporting());
633 | }
634 |
635 | /**
636 | * Get a list of exceptions we should never send to Sentry.
637 | *
638 | * @return array
639 | */
640 | public function getIgnoreExceptions(): array
641 | {
642 | return $this->collectModuleConfig()['ignore_exceptions'] ?? [];
643 | }
644 |
645 | /**
646 | * Whether the report-uri directive in the CSP is enabled.
647 | *
648 | * @return bool
649 | */
650 | public function isEnableCspReportUrl(): bool
651 | {
652 | return $this->collectModuleConfig()['enable_csp_report_url'] ?? false;
653 | }
654 |
655 | /**
656 | * Check whether we should capture the given exception based on severity and ignore exceptions.
657 | *
658 | * @param Throwable $ex
659 | *
660 | * @return bool
661 | */
662 | public function shouldCaptureException(Throwable $ex): bool
663 | {
664 | if ($ex instanceof ErrorException && !($ex->getSeverity() & $this->getErrorTypes())) {
665 | return false;
666 | }
667 |
668 | if (in_array(get_class($ex), $this->getIgnoreExceptions())) {
669 | return false;
670 | }
671 |
672 | if ($ex->getPrevious()) {
673 | return $this->shouldCaptureException($ex->getPrevious());
674 | }
675 |
676 | return true;
677 | }
678 | }
679 |
--------------------------------------------------------------------------------