From bd6259c6ba5b13b387bdedfce1671ebbc27ca105 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Wed, 2 Nov 2022 16:57:12 +0100 Subject: [PATCH 01/11] Add performance sampling --- Model/SentryPerformance.php | 62 +++++++++++++++++++ .../ControllerActionPostdispatchObserver.php | 28 +++++++++ .../ControllerActionPredispatchObserver.php | 27 ++++++++ Plugin/GlobalExceptionCatcher.php | 4 ++ etc/frontend/events.xml | 9 +++ 5 files changed, 130 insertions(+) create mode 100644 Model/SentryPerformance.php create mode 100644 Observer/ControllerActionPostdispatchObserver.php create mode 100644 Observer/ControllerActionPredispatchObserver.php create mode 100644 etc/frontend/events.xml diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php new file mode 100644 index 0000000..4bc2d8a --- /dev/null +++ b/Model/SentryPerformance.php @@ -0,0 +1,62 @@ +getServer('REQUEST_TIME_FLOAT', microtime(true)); + + $context = TransactionContext::fromHeaders( + $request->getHeader('sentry-trace') ?: '', + $request->getHeader('baggage') ?: '' + ); + + $requestPath = '/' . ltrim($request->getRequestUri(), '/'); + + $context->setOp('http.server'); + $context->setName($requestPath); + $context->setSource(TransactionSource::url()); + $context->setStartTimestamp($requestStartTime); + + $context->setData([ + 'url' => $requestPath, + 'method' => strtoupper($request->getMethod()), + ]); + + // Start the transaction + $transaction = \Sentry\startTransaction($context); + + // If this transaction is not sampled, don't set it either and stop doing work from this point on + if (!$transaction->getSampled()) { + return; + } + + $this->transaction = $transaction; + + // Set the current transaction as the current span so we can retrieve it later + \Sentry\SentrySdk::getCurrentHub()->setSpan($transaction); + } + + public function finishTransaction() + { + if ($this->transaction) { + // Finish the transaction, this submits the transaction and it's span to Sentry + $this->transaction->finish(); + + $this->transaction = null; + } + } +} diff --git a/Observer/ControllerActionPostdispatchObserver.php b/Observer/ControllerActionPostdispatchObserver.php new file mode 100644 index 0000000..9423872 --- /dev/null +++ b/Observer/ControllerActionPostdispatchObserver.php @@ -0,0 +1,28 @@ +sentryPerformance = $sentryPerformance; + $this->response = $response; + } + + public function execute(\Magento\Framework\Event\Observer $observer) + { + $this->sentryPerformance->finishTransaction($this->response); + } +} diff --git a/Observer/ControllerActionPredispatchObserver.php b/Observer/ControllerActionPredispatchObserver.php new file mode 100644 index 0000000..3284d3e --- /dev/null +++ b/Observer/ControllerActionPredispatchObserver.php @@ -0,0 +1,27 @@ +sentryPerformance = $sentryPerformance; + $this->request = $request; + } + + public function execute(\Magento\Framework\Event\Observer $observer) + { + $this->sentryPerformance->startTransaction($this->request); + } +} diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index fc236a5..637b0e8 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -59,6 +59,10 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) return $data->getEvent(); }); + if ($this->sentryHelper->isTracingEnabled()) { + $config->setTracesSampleRate($this->sentryHelper->getTracingSampleRate()); + } + $this->eventManager->dispatch('sentry_before_init', [ 'config' => $config, ]); diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml new file mode 100644 index 0000000..a777bdf --- /dev/null +++ b/etc/frontend/events.xml @@ -0,0 +1,9 @@ + + + + + + + + + From dceb49d4bbe06852314395f1f5b2c69e8a5675a2 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Wed, 2 Nov 2022 17:08:42 +0100 Subject: [PATCH 02/11] Add initial performance tracking --- Helper/Data.php | 7 ++++++- Model/SentryPerformance.php | 2 +- Plugin/GlobalExceptionCatcher.php | 2 +- etc/adminhtml/system.xml | 4 ++++ etc/config.xml | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 01d2e58..7b43c41 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -272,10 +272,15 @@ public function isPhpTrackingEnabled(): bool return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_php_tracking'); } + public function isPerformanceTrackingEnabled(): bool + { + return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_performance_tracking'); + } + /** * @return bool */ - public function useScriptTag(): bool + public function useScriptTag() { return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_script_tag'); } diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 4bc2d8a..53d080c 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -25,7 +25,7 @@ public function startTransaction(Http $request) ); $requestPath = '/' . ltrim($request->getRequestUri(), '/'); - + $context->setOp('http.server'); $context->setName($requestPath); $context->setSource(TransactionSource::url()); diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index 637b0e8..82660fd 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -59,7 +59,7 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) return $data->getEvent(); }); - if ($this->sentryHelper->isTracingEnabled()) { + if ($this->sentryHelper->isTracingEnabled() && $this->sentryHelper->isPerformanceTrackingEnabled()) { $config->setTracesSampleRate($this->sentryHelper->getTracingSampleRate()); } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 02a14ec..8544eb8 100755 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -19,6 +19,10 @@ Magento\Config\Model\Config\Source\Yesno + + + Magento\Config\Model\Config\Source\Yesno + Magento\Config\Model\Config\Source\Yesno diff --git a/etc/config.xml b/etc/config.xml index d315ab3..088648c 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -4,6 +4,7 @@ 1 + 0 0 before.body.end 0 From c40c523c3154ba4f00bffcebb2fd4fcaae30d83a Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Thu, 3 Nov 2022 10:18:06 +0100 Subject: [PATCH 03/11] Use Response plugin for finishing --- Model/SentryPerformance.php | 18 +++++++-- .../ControllerActionPostdispatchObserver.php | 28 -------------- Plugin/SampleRequest.php | 38 +++++++++++++++++++ etc/frontend/di.xml | 11 ++++++ etc/frontend/events.xml | 3 -- 5 files changed, 64 insertions(+), 34 deletions(-) delete mode 100644 Observer/ControllerActionPostdispatchObserver.php create mode 100644 Plugin/SampleRequest.php create mode 100644 etc/frontend/di.xml diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 53d080c..080a9e0 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -6,16 +6,20 @@ // phpcs:disable Magento2.Functions.DiscouragedFunction -use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\App\Response\Http as HttpResponse; +use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; class SentryPerformance { + /** @var Transaction|null */ private $transaction; - public function startTransaction(Http $request) + public function startTransaction(HttpRequest $request) { $requestStartTime = $request->getServer('REQUEST_TIME_FLOAT', microtime(true)); @@ -50,9 +54,17 @@ public function startTransaction(Http $request) \Sentry\SentrySdk::getCurrentHub()->setSpan($transaction); } - public function finishTransaction() + /** + * @param ResponseInterface|Response $response + */ + public function finishTransaction(ResponseInterface $response) { if ($this->transaction) { + + if ($response instanceof HttpResponse) { + $this->transaction->setHttpStatus($response->getStatusCode()); + } + // Finish the transaction, this submits the transaction and it's span to Sentry $this->transaction->finish(); diff --git a/Observer/ControllerActionPostdispatchObserver.php b/Observer/ControllerActionPostdispatchObserver.php deleted file mode 100644 index 9423872..0000000 --- a/Observer/ControllerActionPostdispatchObserver.php +++ /dev/null @@ -1,28 +0,0 @@ -sentryPerformance = $sentryPerformance; - $this->response = $response; - } - - public function execute(\Magento\Framework\Event\Observer $observer) - { - $this->sentryPerformance->finishTransaction($this->response); - } -} diff --git a/Plugin/SampleRequest.php b/Plugin/SampleRequest.php new file mode 100644 index 0000000..a53717f --- /dev/null +++ b/Plugin/SampleRequest.php @@ -0,0 +1,38 @@ +sentryPerformance = $sentryPerformance; + $this->request = $request; + } + + /** + * Add our toolbar to the response + * + * @param ResponseInterface $response + */ + public function beforeSendResponse(ResponseInterface $response) + { + $this->sentryPerformance->finishTransaction($response); + } + +} diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml new file mode 100644 index 0000000..fbcf889 --- /dev/null +++ b/etc/frontend/di.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml index a777bdf..3bbe102 100644 --- a/etc/frontend/events.xml +++ b/etc/frontend/events.xml @@ -3,7 +3,4 @@ - - - From 2205275d37e342fd6d9ba39f40b43c2a2c280790 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Thu, 3 Nov 2022 10:49:02 +0100 Subject: [PATCH 04/11] Add SQL queries --- Model/SentryPerformance.php | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 080a9e0..108a216 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -10,6 +10,8 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; use Magento\Framework\App\Response\Http as HttpResponse; +use Sentry\SentrySdk; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; @@ -19,6 +21,14 @@ class SentryPerformance /** @var Transaction|null */ private $transaction; + /** @var \Magento\Framework\App\ResourceConnection */ + private $resourceConnection; + + public function __construct(\Magento\Framework\App\ResourceConnection $resourceConnection) + { + $this->resourceConnection = $resourceConnection; + } + public function startTransaction(HttpRequest $request) { $requestStartTime = $request->getServer('REQUEST_TIME_FLOAT', microtime(true)); @@ -65,10 +75,42 @@ public function finishTransaction(ResponseInterface $response) $this->transaction->setHttpStatus($response->getStatusCode()); } + $this->addSqlQueries(); + // Finish the transaction, this submits the transaction and it's span to Sentry $this->transaction->finish(); $this->transaction = null; } } + + private function addSqlQueries() + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if (!$parentSpan) { + return; + } + + /** @var \Zend_Db_Profiler $profiler */ + $profiler = $this->resourceConnection->getConnection('read')->getProfiler(); + if (!$profiler) { + return; + } + + /** @var \Zend_Db_Profiler_Query[]|false $profiles */ + $profiles = $profiler->getQueryProfiles(); + if (!$profiles) { + return; + } + + foreach ($profiles as $profile) { + $context = new SpanContext(); + $context->setOp('db.sql.query'); + $context->setDescription($profile->getQuery()); + $context->setStartTimestamp($profile->getStartedMicrotime()); + $context->setEndTimestamp(($profile->getStartedMicrotime() + $profile->getElapsedSecs())); + + $parentSpan->startChild($context); + } + } } From 4072f6a306230584a4c764debebbf44fbe775fd1 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Thu, 3 Nov 2022 10:50:57 +0100 Subject: [PATCH 05/11] CS --- Model/SentryPerformance.php | 10 ++++------ Observer/ControllerActionPredispatchObserver.php | 6 +++--- Plugin/SampleRequest.php | 11 +++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 108a216..babff3c 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -7,9 +7,8 @@ // phpcs:disable Magento2.Functions.DiscouragedFunction use Magento\Framework\App\Request\Http as HttpRequest; -use Magento\Framework\App\RequestInterface; -use Magento\Framework\App\ResponseInterface; use Magento\Framework\App\Response\Http as HttpResponse; +use Magento\Framework\App\ResponseInterface; use Sentry\SentrySdk; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; @@ -21,7 +20,7 @@ class SentryPerformance /** @var Transaction|null */ private $transaction; - /** @var \Magento\Framework\App\ResourceConnection */ + /** @var \Magento\Framework\App\ResourceConnection */ private $resourceConnection; public function __construct(\Magento\Framework\App\ResourceConnection $resourceConnection) @@ -38,7 +37,7 @@ public function startTransaction(HttpRequest $request) $request->getHeader('baggage') ?: '' ); - $requestPath = '/' . ltrim($request->getRequestUri(), '/'); + $requestPath = '/'.ltrim($request->getRequestUri(), '/'); $context->setOp('http.server'); $context->setName($requestPath); @@ -46,7 +45,7 @@ public function startTransaction(HttpRequest $request) $context->setStartTimestamp($requestStartTime); $context->setData([ - 'url' => $requestPath, + 'url' => $requestPath, 'method' => strtoupper($request->getMethod()), ]); @@ -70,7 +69,6 @@ public function startTransaction(HttpRequest $request) public function finishTransaction(ResponseInterface $response) { if ($this->transaction) { - if ($response instanceof HttpResponse) { $this->transaction->setHttpStatus($response->getStatusCode()); } diff --git a/Observer/ControllerActionPredispatchObserver.php b/Observer/ControllerActionPredispatchObserver.php index 3284d3e..c59bd0b 100644 --- a/Observer/ControllerActionPredispatchObserver.php +++ b/Observer/ControllerActionPredispatchObserver.php @@ -3,7 +3,6 @@ namespace JustBetter\Sentry\Observer; use JustBetter\Sentry\Model\SentryPerformance; -use Magento\Framework\App\RequestInterface; use Magento\Framework\App\Request\Http; use Magento\Framework\Event\ObserverInterface; @@ -12,10 +11,11 @@ class ControllerActionPredispatchObserver implements ObserverInterface /** @var SentryPerformance */ private $sentryPerformance; - /** @var Http */ + /** @var Http */ private $request; - public function __construct(SentryPerformance $sentryPerformance, Http $request) { + public function __construct(SentryPerformance $sentryPerformance, Http $request) + { $this->sentryPerformance = $sentryPerformance; $this->request = $request; } diff --git a/Plugin/SampleRequest.php b/Plugin/SampleRequest.php index a53717f..7ac1757 100644 --- a/Plugin/SampleRequest.php +++ b/Plugin/SampleRequest.php @@ -5,28 +5,28 @@ use JustBetter\Sentry\Model\SentryPerformance; use Magento\Framework\App\Request\Http; use Magento\Framework\App\ResponseInterface; -use Magento\Framework\View\LayoutInterface; use MagentoHackathon\Toolbar\Toolbar; /** * Plugin to add Toolbar to the Response add the - * end of the body + * end of the body. */ class SampleRequest { /** @var SentryPerformance */ private $sentryPerformance; - /** @var Http */ + /** @var Http */ private $request; - public function __construct(SentryPerformance $sentryPerformance, Http $request) { + public function __construct(SentryPerformance $sentryPerformance, Http $request) + { $this->sentryPerformance = $sentryPerformance; $this->request = $request; } /** - * Add our toolbar to the response + * Add our toolbar to the response. * * @param ResponseInterface $response */ @@ -34,5 +34,4 @@ public function beforeSendResponse(ResponseInterface $response) { $this->sentryPerformance->finishTransaction($response); } - } From 831f2e2f9a94f9a70d6134403f136e40fefb40f1 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Wed, 9 Nov 2022 21:42:57 +0100 Subject: [PATCH 06/11] TWeak DB logging --- Model/SentryPerformance.php | 30 ++++++------------------ Plugin/LoggerProxyPlugin.php | 44 ++++++++++++++++++++++++++++++++++++ Plugin/SampleRequest.php | 4 +--- etc/frontend/di.xml | 7 ++++++ 4 files changed, 59 insertions(+), 26 deletions(-) create mode 100644 Plugin/LoggerProxyPlugin.php diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index babff3c..cc808e4 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -73,8 +73,6 @@ public function finishTransaction(ResponseInterface $response) $this->transaction->setHttpStatus($response->getStatusCode()); } - $this->addSqlQueries(); - // Finish the transaction, this submits the transaction and it's span to Sentry $this->transaction->finish(); @@ -82,33 +80,19 @@ public function finishTransaction(ResponseInterface $response) } } - private function addSqlQueries() + public function addSqlQuery($sql, $startTime, $endTime = null) { $parentSpan = SentrySdk::getCurrentHub()->getSpan(); if (!$parentSpan) { return; } - /** @var \Zend_Db_Profiler $profiler */ - $profiler = $this->resourceConnection->getConnection('read')->getProfiler(); - if (!$profiler) { - return; - } - - /** @var \Zend_Db_Profiler_Query[]|false $profiles */ - $profiles = $profiler->getQueryProfiles(); - if (!$profiles) { - return; - } + $context = new SpanContext(); + $context->setOp('db.sql.query'); + $context->setDescription($sql); + $context->setStartTimestamp($startTime); + $context->setEndTimestamp($endTime ?: microtime(true)); - foreach ($profiles as $profile) { - $context = new SpanContext(); - $context->setOp('db.sql.query'); - $context->setDescription($profile->getQuery()); - $context->setStartTimestamp($profile->getStartedMicrotime()); - $context->setEndTimestamp(($profile->getStartedMicrotime() + $profile->getElapsedSecs())); - - $parentSpan->startChild($context); - } + $parentSpan->startChild($context); } } diff --git a/Plugin/LoggerProxyPlugin.php b/Plugin/LoggerProxyPlugin.php new file mode 100644 index 0000000..f1293ee --- /dev/null +++ b/Plugin/LoggerProxyPlugin.php @@ -0,0 +1,44 @@ +sentryPerformance = $sentryPerformance; + } + + /** + * {@inheritdoc} + */ + public function beforeStartTimer() + { + $this->timer = microtime(true); + } + + /** + * @param LoggerProxy $subject + * @param string $type + * @param string $sql + * @param array $bind + * @param \Zend_Db_Statement_Pdo|null $result + * @return void + */ + public function beforeLogStats(LoggerProxy $subject, $type, $sql, $bind = [], $result = null) + { + $this->sentryPerformance->addSqlQuery($sql, $this->timer); + } +} diff --git a/Plugin/SampleRequest.php b/Plugin/SampleRequest.php index 7ac1757..5e45784 100644 --- a/Plugin/SampleRequest.php +++ b/Plugin/SampleRequest.php @@ -5,11 +5,9 @@ use JustBetter\Sentry\Model\SentryPerformance; use Magento\Framework\App\Request\Http; use Magento\Framework\App\ResponseInterface; -use MagentoHackathon\Toolbar\Toolbar; /** - * Plugin to add Toolbar to the Response add the - * end of the body. + * Plugin to sample request and send them to Sentry */ class SampleRequest { diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index fbcf889..0f98aa4 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -8,4 +8,11 @@ /> + + + + From b401496353f9e665689aab00bb4f9c794c0b3e5c Mon Sep 17 00:00:00 2001 From: Frederik Rommel Date: Wed, 7 Aug 2024 17:47:06 +0200 Subject: [PATCH 07/11] finalize implementation of performance tracking --- Helper/Data.php | 9 +- Model/SentryPerformance.php | 96 +++++++++++++------ .../ControllerActionPredispatchObserver.php | 27 ------ Plugin/GlobalExceptionCatcher.php | 26 ++--- Plugin/Profiling/EventManagerPlugin.php | 77 +++++++++++++++ Plugin/Profiling/TemplatePlugin.php | 51 ++++++++++ README.md | 10 +- etc/di.xml | 8 ++ etc/frontend/events.xml | 6 -- 9 files changed, 232 insertions(+), 78 deletions(-) delete mode 100644 Observer/ControllerActionPredispatchObserver.php create mode 100644 Plugin/Profiling/EventManagerPlugin.php create mode 100644 Plugin/Profiling/TemplatePlugin.php delete mode 100644 etc/frontend/events.xml diff --git a/Helper/Data.php b/Helper/Data.php index 7b43c41..0f8d2cf 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -48,6 +48,8 @@ class Data extends AbstractHelper 'js_sdk_version', 'tracing_enabled', 'tracing_sample_rate', + 'performance_tracking_enabled', + 'performance_tracking_excluded_areas', 'ignore_js_errors', ]; @@ -274,7 +276,12 @@ public function isPhpTrackingEnabled(): bool public function isPerformanceTrackingEnabled(): bool { - return $this->scopeConfig->isSetFlag(static::XML_PATH_SRS.'enable_performance_tracking'); + return $this->isTracingEnabled() && $this->config['performance_tracking_enabled'] ?? false; + } + + public function getPerformanceTrackingExcludedAreas(): array + { + return $this->config['performance_tracking_excluded_areas'] ?? ['adminhtml', 'crontab']; } /** diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index cc808e4..3de8643 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -6,51 +6,61 @@ // phpcs:disable Magento2.Functions.DiscouragedFunction +use JustBetter\Sentry\Helper\Data; +use Laminas\Http\Response; +use Magento\Framework\App\Http; use Magento\Framework\App\Request\Http as HttpRequest; -use Magento\Framework\App\Response\Http as HttpResponse; use Magento\Framework\App\ResponseInterface; +use Magento\Framework\App\State; +use Magento\Framework\AppInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\ObjectManagerInterface; use Sentry\SentrySdk; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; +use function Sentry\startTransaction; + class SentryPerformance { - /** @var Transaction|null */ - private $transaction; - - /** @var \Magento\Framework\App\ResourceConnection */ - private $resourceConnection; + private ?Transaction $transaction = null; - public function __construct(\Magento\Framework\App\ResourceConnection $resourceConnection) - { - $this->resourceConnection = $resourceConnection; + public function __construct( + private HttpRequest $request, + private ObjectManagerInterface $objectManager, + private Data $helper + ) { } - public function startTransaction(HttpRequest $request) + public function startTransaction(AppInterface $app): void { - $requestStartTime = $request->getServer('REQUEST_TIME_FLOAT', microtime(true)); + if (!$app instanceof Http) { + // actually, we only support profiling of http requests. + return; + } + + $requestStartTime = $this->request->getServer('REQUEST_TIME_FLOAT', microtime(true)); $context = TransactionContext::fromHeaders( - $request->getHeader('sentry-trace') ?: '', - $request->getHeader('baggage') ?: '' + $this->request->getHeader('sentry-trace') ?: '', + $this->request->getHeader('baggage') ?: '' ); - $requestPath = '/'.ltrim($request->getRequestUri(), '/'); + $requestPath = '/'.ltrim($this->request->getRequestUri(), '/'); - $context->setOp('http.server'); $context->setName($requestPath); $context->setSource(TransactionSource::url()); $context->setStartTimestamp($requestStartTime); $context->setData([ 'url' => $requestPath, - 'method' => strtoupper($request->getMethod()), + 'method' => strtoupper($this->request->getMethod()), ]); // Start the transaction - $transaction = \Sentry\startTransaction($context); + $transaction = startTransaction($context); // If this transaction is not sampled, don't set it either and stop doing work from this point on if (!$transaction->getSampled()) { @@ -60,27 +70,53 @@ public function startTransaction(HttpRequest $request) $this->transaction = $transaction; // Set the current transaction as the current span so we can retrieve it later - \Sentry\SentrySdk::getCurrentHub()->setSpan($transaction); + SentrySdk::getCurrentHub()->setSpan($transaction); } - /** - * @param ResponseInterface|Response $response - */ - public function finishTransaction(ResponseInterface $response) + public function finishTransaction(ResponseInterface|int $statusCode): void { - if ($this->transaction) { - if ($response instanceof HttpResponse) { - $this->transaction->setHttpStatus($response->getStatusCode()); - } + if ($this->transaction === null) { + return; + } - // Finish the transaction, this submits the transaction and it's span to Sentry - $this->transaction->finish(); + try { + $state = $this->objectManager->get(State::class); + $areaCode = $state->getAreaCode(); + } catch (LocalizedException) { + // we wont track transaction without an area + return; + } + + if (in_array($areaCode, $this->helper->getPerformanceTrackingExcludedAreas())) { + return; + } + + if ($statusCode instanceof Response) { + $statusCode = (int) $statusCode->getStatusCode(); + } - $this->transaction = null; + if (is_numeric($statusCode)) { + $this->transaction->setHttpStatus($statusCode); } + + $isHttp = in_array($state->getAreaCode(), ['frontend', 'webapi', 'graphql', 'adminhtml']); + $this->transaction->setOp(($isHttp ? 'http.' : '').$state->getAreaCode()); + $this->transaction->setData(array_merge( + $this->transaction->getData(), + $this->request->__debugInfo(), + [ + 'module' => $this->request->getModuleName(), + 'action' => $this->request->getFullActionName(), + ] + )); + + // Finish the transaction, this submits the transaction and it's span to Sentry + $this->transaction->finish(); + + $this->transaction = null; } - public function addSqlQuery($sql, $startTime, $endTime = null) + public function addSqlQuery($sql, $startTime, $endTime = null): void { $parentSpan = SentrySdk::getCurrentHub()->getSpan(); if (!$parentSpan) { diff --git a/Observer/ControllerActionPredispatchObserver.php b/Observer/ControllerActionPredispatchObserver.php deleted file mode 100644 index c59bd0b..0000000 --- a/Observer/ControllerActionPredispatchObserver.php +++ /dev/null @@ -1,27 +0,0 @@ -sentryPerformance = $sentryPerformance; - $this->request = $request; - } - - public function execute(\Magento\Framework\Event\Observer $observer) - { - $this->sentryPerformance->startTransaction($this->request); - } -} diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index 82660fd..986606c 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -7,27 +7,21 @@ use JustBetter\Sentry\Helper\Data as SenteryHelper; use JustBetter\Sentry\Model\ReleaseIdentifier; use JustBetter\Sentry\Model\SentryInteraction; +use JustBetter\Sentry\Model\SentryPerformance; use Magento\Framework\AppInterface; +use Magento\Framework\DataObject; use Magento\Framework\DataObjectFactory; use Magento\Framework\Event\ManagerInterface as EventManagerInterface; class GlobalExceptionCatcher { - /** - * ExceptionCatcher constructor. - * - * @param SenteryHelper $sentryHelper - * @param ReleaseIdentifier $releaseIdentifier - * @param SentryInteraction $sentryInteraction - * @param EventManagerInterface $eventManager - * @param DataObjectFactory $dataObjectFactory - */ public function __construct( protected SenteryHelper $sentryHelper, private ReleaseIdentifier $releaseIdentifier, private SentryInteraction $sentryInteraction, private EventManagerInterface $eventManager, - private DataObjectFactory $dataObjectFactory + private DataObjectFactory $dataObjectFactory, + private SentryPerformance $sentryPerformance ) { } @@ -37,6 +31,7 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) return $proceed(); } + /** @var DataObject $config */ $config = $this->dataObjectFactory->create(); $config->setDsn($this->sentryHelper->getDSN()); @@ -59,7 +54,7 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) return $data->getEvent(); }); - if ($this->sentryHelper->isTracingEnabled() && $this->sentryHelper->isPerformanceTrackingEnabled()) { + if ($this->sentryHelper->isPerformanceTrackingEnabled()) { $config->setTracesSampleRate($this->sentryHelper->getTracingSampleRate()); } @@ -68,9 +63,10 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) ]); $this->sentryInteraction->initialize($config->getData()); + $this->sentryPerformance->startTransaction($subject); try { - return $proceed(); + return $response = $proceed(); } catch (\Throwable $ex) { try { if ($this->sentryHelper->shouldCaptureException($ex)) { @@ -81,6 +77,12 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) } throw $ex; + } finally { + try { + $this->sentryPerformance->finishTransaction($response ?? 500); + } catch (\Throwable $bigProblem) { + // do nothing if sentry fails + } } } } diff --git a/Plugin/Profiling/EventManagerPlugin.php b/Plugin/Profiling/EventManagerPlugin.php new file mode 100644 index 0000000..eaf4d4a --- /dev/null +++ b/Plugin/Profiling/EventManagerPlugin.php @@ -0,0 +1,77 @@ +excludePatterns = array_merge([ + '^model_load_', + '_load_before$', + '_load_after$', + '_$', + '^view_block_abstract_', + ], $excludePatterns); + } + + public function beforeDispatch(ManagerInterface $subject, string|null $eventName, array $data = []): array + { + if ($eventName === null) { + return [$eventName, $data]; + } + + $parent = SentrySdk::getCurrentHub()->getSpan(); + if ($parent === null) { + // can happen if no transaction has been started + return [$eventName, $data]; + } + + foreach ($this->excludePatterns as $excludePattern) { + if (preg_match('/'.$excludePattern.'/i', $eventName)) { + return [$eventName, $data]; + } + } + + if ($this->config->getObservers(mb_strtolower($eventName)) === []) { + return [$eventName, $data]; + } + + $context = SpanContext::make() + ->setOp('event') + ->setDescription($eventName); + + $span = $parent->startChild($context); + SentrySdk::getCurrentHub()->setSpan($span); + $data[self::SPAN_STORAGE_KEY] = [$span, $parent]; + + return [$eventName, $data]; + } + + public function afterDispatch(ManagerInterface $subject, $result, string $name, array $data = []): void + { + /** @var Span[]|null $span */ + $span = $data[self::SPAN_STORAGE_KEY] ?? null; + if (!is_array($span)) { + return; + } + + if (isset($span[0])) { + $span[0]->finish(); + + SentrySdk::getCurrentHub()->setSpan($span[1]); + } + } +} diff --git a/Plugin/Profiling/TemplatePlugin.php b/Plugin/Profiling/TemplatePlugin.php new file mode 100644 index 0000000..ac7a6f1 --- /dev/null +++ b/Plugin/Profiling/TemplatePlugin.php @@ -0,0 +1,51 @@ +getSpan(); + if (!$parentSpan) { + return; + } + + $context = SpanContext::make() + ->setOp('render-view') + ->setDescription($subject->getNameInLayout() ?: $fileName) + ->setData([ + 'block_name' => $subject->getNameInLayout(), + 'block_class' => get_class($subject), + 'module' => $subject->getModuleName(), + 'template' => $fileName, + ]); + $span = $parentSpan->startChild($context); + + SentrySdk::getCurrentHub()->setSpan($span); + + $subject->setData(self::SPAN_STORAGE_KEY, [$span, $parentSpan]); + } + + public function afterFetchView(Template $subject, $result, $fileName) + { + $span = $subject->getData(self::SPAN_STORAGE_KEY) ?: []; + + if ($span[0] instanceof Span) { + $span[0]->finish(); + + SentrySdk::getCurrentHub()->setSpan($span[1]); + } + + return $result; + } +} diff --git a/README.md b/README.md index eece21b..dc30521 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,10 @@ This module uses the [Magento Deployment Configuration](https://devdocs.magento. 'ignore_exceptions' => [], 'mage_mode_development' => false, 'js_sdk_version' => \JustBetter\Sentry\Block\SentryScript::CURRENT_VERSION, - 'tracing_enabled' => true, + 'tracing_enabled' => true, 'tracing_sample_rate' => 0.5, + 'performance_tracking_enabled' => true, + 'performance_tracking_excluded_areas' => ['adminhtml', 'crontab'], 'ignore_js_errors' => [] ] ``` @@ -39,9 +41,11 @@ Next to that there are some configuration options under Stores > Configuration > * `errorexception_reporting`: If the Exception being thrown 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. This uses the same syntax as [Error Reporting](https://www.php.net/manual/en/function.error-reporting.php) eg. `E_ERROR | E_WARNING` to only log Errors and Warnings. * `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]` * `mage_mode_development`: If this option is set to true you will receive issues in Sentry even if you're Magento is running in develop mode. -* `js_sdk_version`: if this option is set, it will load the explicit version of the javascript SDK of Sentry. +* `js_sdk_version`: if this option is set, it will load the explicit version of the javascript SDK of Sentry. * `tracing_enabled` if this option is set to true, tracing got enabled (bundle file got loaded automatically). Default: `false` * `tracing_sample_rate` if tracing is enabled, you should also set the sample rate. Default: `0.2` +* `performance_tracking_enabled` if performance tracking is enabled, a performance report got generated for the request. Default: `false` +* `performance_tracking_excluded_areas`: if `performance_tracking_enabled` is enabled, we recommend to exclude the `adminhtml` & `crontab` area. Default `['adminhtml', 'crontab']` * `ignore_js_errors` array of javascript error messages, which should be not send to Sentry. (see also `ignoreErrors` in [Sentry documentation](https://docs.sentry.io/clients/javascript/config/)) ### Configuration for Adobe Cloud @@ -59,6 +63,8 @@ using the "Variables" in Adobe Commerce using the following variables: * `CONFIG__SENTRY__ENVIRONMENT__JS_SDK_VERSION`: string * `CONFIG__SENTRY__ENVIRONMENT__TRACING_ENABLED`: boolean * `CONFIG__SENTRY__ENVIRONMENT__TRACING_SAMPLE_RATE`: float +* `CONFIG__SENTRY__ENVIRONMENT__TRACING_PERFORMANCE_TRACKING_ENABLED`: boolean +* `CONFIG__SENTRY__ENVIRONMENT__TRACING_PERFORMANCE_TRACKING_EXCLUDED_AREAS`: boolean * `CONFIG__SENTRY__ENVIRONMENT__IGNORE_JS_ERRORS`: A JSON encoded array of error messages The following configuration settings can be overridden in the Magento admin. This is limited to ensure that changes to diff --git a/etc/di.xml b/etc/di.xml index 3e4c720..952ba3c 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -34,4 +34,12 @@ + + + + + + + + diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml deleted file mode 100644 index 3bbe102..0000000 --- a/etc/frontend/events.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From f8eac4e33f3e8697d65ee83e22dbaee545b035ec Mon Sep 17 00:00:00 2001 From: Frederik Rommel Date: Wed, 7 Aug 2024 17:48:10 +0200 Subject: [PATCH 08/11] implement profiling --- Helper/Data.php | 6 ++++++ Plugin/GlobalExceptionCatcher.php | 4 ++++ README.md | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/Helper/Data.php b/Helper/Data.php index 0f8d2cf..ed301b2 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -50,6 +50,7 @@ class Data extends AbstractHelper 'tracing_sample_rate', 'performance_tracking_enabled', 'performance_tracking_excluded_areas', + 'profiles_sample_rate', 'ignore_js_errors', ]; @@ -95,6 +96,11 @@ public function getTracingSampleRate(): float return (float) $this->config['tracing_sample_rate'] ?? 0.2; } + public function getPhpProfileSampleRate(): float + { + return (float) ($this->config['profiles_sample_rate'] ?? 0); + } + /** * @return array|null */ diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index 986606c..45e66b3 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -58,6 +58,10 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) $config->setTracesSampleRate($this->sentryHelper->getTracingSampleRate()); } + if ($rate = $this->sentryHelper->getPhpProfileSampleRate()) { + $config->setData('profiles_sample_rate', $rate); + } + $this->eventManager->dispatch('sentry_before_init', [ 'config' => $config, ]); diff --git a/README.md b/README.md index dc30521..275302c 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This module uses the [Magento Deployment Configuration](https://devdocs.magento. 'tracing_sample_rate' => 0.5, 'performance_tracking_enabled' => true, 'performance_tracking_excluded_areas' => ['adminhtml', 'crontab'], + 'profiles_sample_rate' => 0.5, 'ignore_js_errors' => [] ] ``` @@ -46,6 +47,7 @@ Next to that there are some configuration options under Stores > Configuration > * `tracing_sample_rate` if tracing is enabled, you should also set the sample rate. Default: `0.2` * `performance_tracking_enabled` if performance tracking is enabled, a performance report got generated for the request. Default: `false` * `performance_tracking_excluded_areas`: if `performance_tracking_enabled` is enabled, we recommend to exclude the `adminhtml` & `crontab` area. Default `['adminhtml', 'crontab']` +* `profiles_sample_rate` 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. Default `0` (disabled) * `ignore_js_errors` array of javascript error messages, which should be not send to Sentry. (see also `ignoreErrors` in [Sentry documentation](https://docs.sentry.io/clients/javascript/config/)) ### Configuration for Adobe Cloud @@ -70,6 +72,8 @@ using the "Variables" in Adobe Commerce using the following variables: The following configuration settings can be overridden in the Magento admin. This is limited to ensure that changes to particular config settings can only be done on server level and can't be broken by changes in the admin. +Please note, that it is not possible to use profiling within the Adobe Cloud. + ## Optional error page configuration - 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`. From e7dbc1866e378997898f53f96b48220ab55075eb Mon Sep 17 00:00:00 2001 From: Frederik Rommel Date: Thu, 8 Aug 2024 01:55:21 +0200 Subject: [PATCH 09/11] use more standard names (open-telemetry) --- Model/SentryPerformance.php | 26 +++++++++++++++---------- Plugin/Profiling/EventManagerPlugin.php | 5 ++++- Plugin/Profiling/TemplatePlugin.php | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 3de8643..0c8f536 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -99,16 +99,22 @@ public function finishTransaction(ResponseInterface|int $statusCode): void $this->transaction->setHttpStatus($statusCode); } - $isHttp = in_array($state->getAreaCode(), ['frontend', 'webapi', 'graphql', 'adminhtml']); - $this->transaction->setOp(($isHttp ? 'http.' : '').$state->getAreaCode()); - $this->transaction->setData(array_merge( - $this->transaction->getData(), - $this->request->__debugInfo(), - [ - 'module' => $this->request->getModuleName(), - 'action' => $this->request->getFullActionName(), - ] - )); + if (in_array($state->getAreaCode(), ['frontend', 'webapi_rest', 'adminhtml'])) { + $this->transaction->setOp('http'); + + $this->transaction->setData(array_merge( + $this->transaction->getData(), + $this->request->__debugInfo(), + [ + 'module' => $this->request->getModuleName(), + 'action' => $this->request->getFullActionName(), + ] + )); + } elseif ($state->getAreaCode() === 'graphql') { + $this->transaction->setOp('graphql'); + } else { + $this->transaction->setOp('other'); + } // Finish the transaction, this submits the transaction and it's span to Sentry $this->transaction->finish(); diff --git a/Plugin/Profiling/EventManagerPlugin.php b/Plugin/Profiling/EventManagerPlugin.php index eaf4d4a..593939b 100644 --- a/Plugin/Profiling/EventManagerPlugin.php +++ b/Plugin/Profiling/EventManagerPlugin.php @@ -51,7 +51,10 @@ public function beforeDispatch(ManagerInterface $subject, string|null $eventName $context = SpanContext::make() ->setOp('event') - ->setDescription($eventName); + ->setDescription($eventName) + ->setData([ + 'event.name' => $eventName, + ]); $span = $parent->startChild($context); SentrySdk::getCurrentHub()->setSpan($span); diff --git a/Plugin/Profiling/TemplatePlugin.php b/Plugin/Profiling/TemplatePlugin.php index ac7a6f1..831b658 100644 --- a/Plugin/Profiling/TemplatePlugin.php +++ b/Plugin/Profiling/TemplatePlugin.php @@ -21,7 +21,7 @@ public function beforeFetchView(Template $subject, $fileName): void } $context = SpanContext::make() - ->setOp('render-view') + ->setOp('template.render') ->setDescription($subject->getNameInLayout() ?: $fileName) ->setData([ 'block_name' => $subject->getNameInLayout(), From 8995a2f07d5d1847daadea9a05303b59e15de136 Mon Sep 17 00:00:00 2001 From: Frederik Rommel Date: Thu, 8 Aug 2024 19:22:54 +0200 Subject: [PATCH 10/11] improve performance tracing --- Model/PerformanceTracingDto.php | 33 +++++++++++++++ Model/SentryInteraction.php | 22 ++++++++-- Model/SentryPerformance.php | 39 +++++++++++------- Plugin/GlobalExceptionCatcher.php | 25 ++++-------- Plugin/LoggerProxyPlugin.php | 44 -------------------- Plugin/Profiling/DbQueryLoggerPlugin.php | 35 ++++++++++++++++ Plugin/Profiling/EventManagerPlugin.php | 52 +++++++++--------------- Plugin/Profiling/TemplatePlugin.php | 34 +++++----------- Plugin/SampleRequest.php | 2 +- etc/di.xml | 5 ++- etc/frontend/di.xml | 7 ---- 11 files changed, 155 insertions(+), 143 deletions(-) create mode 100644 Model/PerformanceTracingDto.php delete mode 100644 Plugin/LoggerProxyPlugin.php create mode 100644 Plugin/Profiling/DbQueryLoggerPlugin.php diff --git a/Model/PerformanceTracingDto.php b/Model/PerformanceTracingDto.php new file mode 100644 index 0000000..ce31dd8 --- /dev/null +++ b/Model/PerformanceTracingDto.php @@ -0,0 +1,33 @@ +scope; + } + + public function getParentSpan(): ?Span + { + return $this->parentSpan; + } + + public function getSpan(): ?Span + { + return $this->span; + } +} diff --git a/Model/SentryInteraction.php b/Model/SentryInteraction.php index ea484f4..5aec00f 100644 --- a/Model/SentryInteraction.php +++ b/Model/SentryInteraction.php @@ -6,20 +6,36 @@ // phpcs:disable Magento2.Functions.DiscouragedFunction +use JustBetter\Sentry\Helper\Data; +use Throwable; + use function Sentry\captureException; use function Sentry\init; class SentryInteraction { - public function initialize($config) + public function __construct( + private Data $sentryHelper + ) { + } + + public function initialize($config): void { init($config); } - public function captureException(\Throwable $ex) + public function captureException(Throwable $ex): void { + if (!$this->sentryHelper->shouldCaptureException($ex)) { + return; + } + ob_start(); - captureException($ex); + + try { + captureException($ex); + } catch (Throwable) { + } ob_end_clean(); } } diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 0c8f536..0d9eb55 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -20,6 +20,7 @@ use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; +use Throwable; use function Sentry\startTransaction; @@ -68,8 +69,6 @@ public function startTransaction(AppInterface $app): void } $this->transaction = $transaction; - - // Set the current transaction as the current span so we can retrieve it later SentrySdk::getCurrentHub()->setSpan($transaction); } @@ -113,28 +112,38 @@ public function finishTransaction(ResponseInterface|int $statusCode): void } elseif ($state->getAreaCode() === 'graphql') { $this->transaction->setOp('graphql'); } else { - $this->transaction->setOp('other'); + $this->transaction->setOp($state->getAreaCode()); } - // Finish the transaction, this submits the transaction and it's span to Sentry - $this->transaction->finish(); + try { + // Finish the transaction, this submits the transaction and it's span to Sentry + $this->transaction->finish(); + } catch (Throwable) { + } $this->transaction = null; } - public function addSqlQuery($sql, $startTime, $endTime = null): void + public static function traceStart(SpanContext $context): PerformanceTracingDto { - $parentSpan = SentrySdk::getCurrentHub()->getSpan(); - if (!$parentSpan) { - return; + $scope = SentrySdk::getCurrentHub()->pushScope(); + $span = null; + + $parentSpan = $scope->getSpan(); + if ($parentSpan !== null && $parentSpan->getSampled()) { + $span = $parentSpan->startChild($context); + $scope->setSpan($span); } - $context = new SpanContext(); - $context->setOp('db.sql.query'); - $context->setDescription($sql); - $context->setStartTimestamp($startTime); - $context->setEndTimestamp($endTime ?: microtime(true)); + return new PerformanceTracingDto($scope, $parentSpan, $span); + } - $parentSpan->startChild($context); + public static function traceEnd(PerformanceTracingDto $context): void + { + if ($context->getSpan()) { + $context->getSpan()->finish(); + $context->getScope()->setSpan($context->getParentSpan()); + } + SentrySdk::getCurrentHub()->popScope(); } } diff --git a/Plugin/GlobalExceptionCatcher.php b/Plugin/GlobalExceptionCatcher.php index 45e66b3..9d35c19 100755 --- a/Plugin/GlobalExceptionCatcher.php +++ b/Plugin/GlobalExceptionCatcher.php @@ -4,7 +4,7 @@ // phpcs:disable Magento2.CodeAnalysis.EmptyBlock -use JustBetter\Sentry\Helper\Data as SenteryHelper; +use JustBetter\Sentry\Helper\Data as SentryHelper; use JustBetter\Sentry\Model\ReleaseIdentifier; use JustBetter\Sentry\Model\SentryInteraction; use JustBetter\Sentry\Model\SentryPerformance; @@ -12,11 +12,12 @@ use Magento\Framework\DataObject; use Magento\Framework\DataObjectFactory; use Magento\Framework\Event\ManagerInterface as EventManagerInterface; +use Throwable; class GlobalExceptionCatcher { public function __construct( - protected SenteryHelper $sentryHelper, + private SentryHelper $sentryHelper, private ReleaseIdentifier $releaseIdentifier, private SentryInteraction $sentryInteraction, private EventManagerInterface $eventManager, @@ -71,22 +72,12 @@ public function aroundLaunch(AppInterface $subject, callable $proceed) try { return $response = $proceed(); - } catch (\Throwable $ex) { - try { - if ($this->sentryHelper->shouldCaptureException($ex)) { - $this->sentryInteraction->captureException($ex); - } - } catch (\Throwable $bigProblem) { - // do nothing if sentry fails - } - - throw $ex; + } catch (Throwable $exception) { + $this->sentryInteraction->captureException($exception); + + throw $exception; } finally { - try { - $this->sentryPerformance->finishTransaction($response ?? 500); - } catch (\Throwable $bigProblem) { - // do nothing if sentry fails - } + $this->sentryPerformance->finishTransaction($response ?? 500); } } } diff --git a/Plugin/LoggerProxyPlugin.php b/Plugin/LoggerProxyPlugin.php deleted file mode 100644 index f1293ee..0000000 --- a/Plugin/LoggerProxyPlugin.php +++ /dev/null @@ -1,44 +0,0 @@ -sentryPerformance = $sentryPerformance; - } - - /** - * {@inheritdoc} - */ - public function beforeStartTimer() - { - $this->timer = microtime(true); - } - - /** - * @param LoggerProxy $subject - * @param string $type - * @param string $sql - * @param array $bind - * @param \Zend_Db_Statement_Pdo|null $result - * @return void - */ - public function beforeLogStats(LoggerProxy $subject, $type, $sql, $bind = [], $result = null) - { - $this->sentryPerformance->addSqlQuery($sql, $this->timer); - } -} diff --git a/Plugin/Profiling/DbQueryLoggerPlugin.php b/Plugin/Profiling/DbQueryLoggerPlugin.php new file mode 100644 index 0000000..fae99d1 --- /dev/null +++ b/Plugin/Profiling/DbQueryLoggerPlugin.php @@ -0,0 +1,35 @@ +tracingDto = SentryPerformance::traceStart( + SpanContext::make() + ->setOp('db.sql.query') + ->setStartTimestamp(microtime(true)) + ); + } + + public function beforeLogStats(LoggerInterface $subject, $type, $sql, $bind = [], $result = null): void + { + if ($this->tracingDto === null) { + return; + } + + $this->tracingDto->getSpan()?->setDescription($sql); + SentryPerformance::traceEnd($this->tracingDto); + $this->tracingDto = null; + } +} diff --git a/Plugin/Profiling/EventManagerPlugin.php b/Plugin/Profiling/EventManagerPlugin.php index 593939b..b97159e 100644 --- a/Plugin/Profiling/EventManagerPlugin.php +++ b/Plugin/Profiling/EventManagerPlugin.php @@ -4,16 +4,13 @@ namespace JustBetter\Sentry\Plugin\Profiling; +use JustBetter\Sentry\Model\SentryPerformance; use Magento\Framework\Event\ConfigInterface; use Magento\Framework\Event\ManagerInterface; -use Sentry\SentrySdk; -use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; class EventManagerPlugin { - private const SPAN_STORAGE_KEY = '__sentry_profiling_span'; - public function __construct( private ConfigInterface $config, private array $excludePatterns = [] @@ -24,29 +21,33 @@ public function __construct( '_load_after$', '_$', '^view_block_abstract_', + '^core_layout_render_e', ], $excludePatterns); } - public function beforeDispatch(ManagerInterface $subject, string|null $eventName, array $data = []): array + private function _canTrace(string|null $eventName): bool { if ($eventName === null) { - return [$eventName, $data]; - } - - $parent = SentrySdk::getCurrentHub()->getSpan(); - if ($parent === null) { - // can happen if no transaction has been started - return [$eventName, $data]; + return false; } foreach ($this->excludePatterns as $excludePattern) { if (preg_match('/'.$excludePattern.'/i', $eventName)) { - return [$eventName, $data]; + return false; } } if ($this->config->getObservers(mb_strtolower($eventName)) === []) { - return [$eventName, $data]; + return false; + } + + return true; + } + + public function aroundDispatch(ManagerInterface $subject, callable $callable, string $eventName, array $data = []): mixed + { + if (!$this->_canTrace($eventName)) { + return $callable($eventName, $data); } $context = SpanContext::make() @@ -56,25 +57,12 @@ public function beforeDispatch(ManagerInterface $subject, string|null $eventName 'event.name' => $eventName, ]); - $span = $parent->startChild($context); - SentrySdk::getCurrentHub()->setSpan($span); - $data[self::SPAN_STORAGE_KEY] = [$span, $parent]; - - return [$eventName, $data]; - } - - public function afterDispatch(ManagerInterface $subject, $result, string $name, array $data = []): void - { - /** @var Span[]|null $span */ - $span = $data[self::SPAN_STORAGE_KEY] ?? null; - if (!is_array($span)) { - return; - } - - if (isset($span[0])) { - $span[0]->finish(); + $tracingDto = SentryPerformance::traceStart($context); - SentrySdk::getCurrentHub()->setSpan($span[1]); + try { + return $callable($eventName, $data); + } finally { + SentryPerformance::traceEnd($tracingDto); } } } diff --git a/Plugin/Profiling/TemplatePlugin.php b/Plugin/Profiling/TemplatePlugin.php index 831b658..b3b9a25 100644 --- a/Plugin/Profiling/TemplatePlugin.php +++ b/Plugin/Profiling/TemplatePlugin.php @@ -4,48 +4,36 @@ namespace JustBetter\Sentry\Plugin\Profiling; +use JustBetter\Sentry\Model\SentryPerformance; use Magento\Framework\View\Element\Template; -use Sentry\SentrySdk; -use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; class TemplatePlugin { - private const SPAN_STORAGE_KEY = '__sentry_profiling_span_fetch_view'; - - public function beforeFetchView(Template $subject, $fileName): void + public function aroundFetchView(Template $subject, callable $callable, $fileName): mixed { - $parentSpan = SentrySdk::getCurrentHub()->getSpan(); - if (!$parentSpan) { - return; + $tags = []; + if (!empty($subject->getModuleName())) { + $tags['magento.module'] = $subject->getModuleName(); } $context = SpanContext::make() ->setOp('template.render') ->setDescription($subject->getNameInLayout() ?: $fileName) + ->setTags($tags) ->setData([ 'block_name' => $subject->getNameInLayout(), 'block_class' => get_class($subject), 'module' => $subject->getModuleName(), 'template' => $fileName, ]); - $span = $parentSpan->startChild($context); - - SentrySdk::getCurrentHub()->setSpan($span); - $subject->setData(self::SPAN_STORAGE_KEY, [$span, $parentSpan]); - } + $tracingDto = SentryPerformance::traceStart($context); - public function afterFetchView(Template $subject, $result, $fileName) - { - $span = $subject->getData(self::SPAN_STORAGE_KEY) ?: []; - - if ($span[0] instanceof Span) { - $span[0]->finish(); - - SentrySdk::getCurrentHub()->setSpan($span[1]); + try { + return $callable($fileName); + } finally { + SentryPerformance::traceEnd($tracingDto); } - - return $result; } } diff --git a/Plugin/SampleRequest.php b/Plugin/SampleRequest.php index 5e45784..5f30bff 100644 --- a/Plugin/SampleRequest.php +++ b/Plugin/SampleRequest.php @@ -7,7 +7,7 @@ use Magento\Framework\App\ResponseInterface; /** - * Plugin to sample request and send them to Sentry + * Plugin to sample request and send them to Sentry. */ class SampleRequest { diff --git a/etc/di.xml b/etc/di.xml index 952ba3c..c25afec 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -35,11 +35,14 @@ sortOrder="10" disabled="false"/> - + + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 0f98aa4..fbcf889 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -8,11 +8,4 @@ /> - - - - From 0cc420d8a28c44423048895f9bd7dc5fd8b76b80 Mon Sep 17 00:00:00 2001 From: Frederik Rommel Date: Wed, 28 Aug 2024 16:17:48 +0200 Subject: [PATCH 11/11] transaction: set name of route instead of full-path --- Model/SentryPerformance.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Model/SentryPerformance.php b/Model/SentryPerformance.php index 0d9eb55..5de1d25 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -99,6 +99,10 @@ public function finishTransaction(ResponseInterface|int $statusCode): void } if (in_array($state->getAreaCode(), ['frontend', 'webapi_rest', 'adminhtml'])) { + if (!empty($this->request->getFullActionName())) { + $this->transaction->setName(strtoupper($this->request->getMethod()). ' ' .$this->request->getFullActionName()); + } + $this->transaction->setOp('http'); $this->transaction->setData(array_merge(