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..d9b096b 100644 --- a/Model/SentryPerformance.php +++ b/Model/SentryPerformance.php @@ -16,12 +16,15 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\ObjectManagerInterface; use Sentry\SentrySdk; +use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; +use Throwable; use function Sentry\startTransaction; +use function Sentry\trace; class SentryPerformance { @@ -68,8 +71,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 +114,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 @@ /> - - - -