diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 484b5c5da..3c3497b4d 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -39,6 +39,7 @@ "Laozi", "Laravel", "Livewire", + "longlang", "Macroable", "Mailables", "Mailgun", @@ -57,6 +58,7 @@ "openai", "overtrue", "phpize", + "phpkafka", "phpredis", "pmessage", "preprod", diff --git a/src/sentry/src/ConfigProvider.php b/src/sentry/src/ConfigProvider.php index a56a7c2b2..6ca66ca3b 100644 --- a/src/sentry/src/ConfigProvider.php +++ b/src/sentry/src/ConfigProvider.php @@ -51,25 +51,9 @@ public function __invoke(): array \Sentry\Transport\TransportInterface::class => Transport\CoHttpTransport::class, ], 'listeners' => [ - Listener\AmqpExceptionListener::class, - Listener\AsyncQueueExceptionListener::class, - Listener\CommandExceptionListener::class, - Listener\CrontabExceptionListener::class, - Listener\DbQueryListener::class, - Listener\KafkaExceptionListener::class, - Listener\RedisCommandExecutedListener::class, - Listener\RequestExceptionListener::class, - Listener\SetRedisEventEnableListener::class, - Listener\SetRequestLifecycleListener::class, - Crons\Listener\CronEventListener::class, - Tracing\Listener\TracingAmqpListener::class, - Tracing\Listener\TracingAsyncQueueListener::class, - Tracing\Listener\TracingCommandListener::class, - Tracing\Listener\TracingCrontabListener::class, - Tracing\Listener\TracingDbQueryListener::class, - Tracing\Listener\TracingKafkaListener::class, - Tracing\Listener\TracingRedisListener::class, - Tracing\Listener\TracingRequestListener::class, + Listener\EventHandleListener::class, + Crons\Listener\EventHandleListener::class, + Tracing\Listener\EventHandleListener::class, ], 'annotations' => [ 'scan' => [ diff --git a/src/sentry/src/Crons/Listener/CronEventListener.php b/src/sentry/src/Crons/Listener/CronEventListener.php deleted file mode 100644 index b1d205b2e..000000000 --- a/src/sentry/src/Crons/Listener/CronEventListener.php +++ /dev/null @@ -1,135 +0,0 @@ -switcher->isCronsEnable()) { - return; - } - - $hub = SentrySdk::getCurrentHub(); - $slug = $event->crontab->getName(); - $options = []; - - if (method_exists($event->crontab, 'getOptions')) { - $options = $event->crontab->getOptions(); - } - - if (isset($options['monitor']) && $options['monitor'] === false) { - return; - } - - // Notify Sentry your job is running - if ($event instanceof Event\BeforeExecute) { - $rule = $event->crontab->getRule(); - $rules = explode(' ', $rule); - - if (count($rules) > 5) { - $this->logger->warning(sprintf('Crontab rule %s is not supported by sentry', $rule)); - return; - } - - $updateMonitorConfig = (bool) ($options['update_monitor_config'] ?? true); - $monitorConfig = null; - - if ($updateMonitorConfig) { - $monitorSchedule = \Sentry\MonitorSchedule::crontab($rule); - $checkinMargin = (int) ($options['checkin_margin'] ?? $this->config->get('sentry.crons.checkin_margin', 5)); - $maxRuntime = (int) ($options['max_runtime'] ?? $this->config->get('sentry.crons.max_runtime', 15)); - $timezone = null; - if (method_exists($event->crontab, 'getTimezone')) { - $timezone = $event->crontab->getTimezone(); - } - $timezone ??= $this->config->get('sentry.crons.timezone', date_default_timezone_get()); - $failureIssueThreshold = isset($options['failure_issue_threshold']) ? (int) $options['failure_issue_threshold'] : null; - $recoveryThreshold = isset($options['recovery_threshold']) ? (int) $options['recovery_threshold'] : null; - - $monitorConfig = new \Sentry\MonitorConfig( - schedule: $monitorSchedule, - checkinMargin: $checkinMargin, - maxRuntime: $maxRuntime, - timezone: $timezone, - failureIssueThreshold: $failureIssueThreshold, - recoveryThreshold: $recoveryThreshold, - ); - } - - $checkInId = $hub->captureCheckIn( - slug: $slug, - status: CheckInStatus::inProgress(), - monitorConfig: $monitorConfig, - ); - - Context::set(Constants::CRON_CHECKIN_ID, $checkInId); - } - - // Notify Sentry your job has completed successfully - if ($event instanceof Event\AfterExecute) { - /** @var string $checkInId */ - $checkInId = Context::get(Constants::CRON_CHECKIN_ID); - if (! $checkInId) { - return; - } - $hub->captureCheckIn( - slug: $slug, - status: CheckInStatus::ok(), - checkInId: $checkInId, - ); - } - - // Notify Sentry your job has failed - if ($event instanceof Event\FailToExecute) { - /** @var string $checkInId */ - $checkInId = Context::get(Constants::CRON_CHECKIN_ID); - if (! $checkInId) { - return; - } - $hub->captureCheckIn( - slug: $slug, - status: CheckInStatus::error(), - checkInId: $checkInId, - ); - } - } -} diff --git a/src/sentry/src/Crons/Listener/EventHandleListener.php b/src/sentry/src/Crons/Listener/EventHandleListener.php new file mode 100644 index 000000000..583708c37 --- /dev/null +++ b/src/sentry/src/Crons/Listener/EventHandleListener.php @@ -0,0 +1,156 @@ +switcher->isCronsEnable()) { + return; + } + + $options = []; + if (method_exists($event->crontab, 'getOptions')) { + $options = $event->crontab->getOptions(); + } + + if (isset($options['monitor']) && $options['monitor'] === false) { + return; + } + + match ($event::class) { + Event\BeforeExecute::class => $this->handleCrontabTaskStarting($event, $options), + Event\AfterExecute::class => $this->handleCrontabTaskFinished($event), + Event\FailToExecute::class => $this->handleCrontabTaskFailed($event), + default => null, + }; + } + + protected function handleCrontabTaskStarting(Event\BeforeExecute $event, array $options): void + { + $hub = SentrySdk::getCurrentHub(); + $slug = $event->crontab->getName(); + $rule = $event->crontab->getRule(); + $rules = explode(' ', $rule); + + if (count($rules) > 5) { + $this->logger->warning(sprintf('Crontab rule %s is not supported by sentry', $rule)); + return; + } + + $updateMonitorConfig = (bool) ($options['update_monitor_config'] ?? true); + $monitorConfig = null; + + if ($updateMonitorConfig) { + $monitorConfig = $this->createMonitorConfig($event, $options, $rule); + } + + $checkInId = $hub->captureCheckIn( + slug: $slug, + status: CheckInStatus::inProgress(), + monitorConfig: $monitorConfig, + ); + + Context::set(Constants::CRON_CHECKIN_ID, $checkInId); + } + + protected function handleCrontabTaskFinished(Event\AfterExecute $event): void + { + /** @var string $checkInId */ + $checkInId = Context::get(Constants::CRON_CHECKIN_ID); + if (! $checkInId) { + return; + } + + $hub = SentrySdk::getCurrentHub(); + $slug = $event->crontab->getName(); + + $hub->captureCheckIn( + slug: $slug, + status: CheckInStatus::ok(), + checkInId: $checkInId, + ); + } + + protected function handleCrontabTaskFailed(Event\FailToExecute $event): void + { + /** @var string $checkInId */ + $checkInId = Context::get(Constants::CRON_CHECKIN_ID); + if (! $checkInId) { + return; + } + + $hub = SentrySdk::getCurrentHub(); + $slug = $event->crontab->getName(); + + $hub->captureCheckIn( + slug: $slug, + status: CheckInStatus::error(), + checkInId: $checkInId, + ); + } + + protected function createMonitorConfig(Event\BeforeExecute $event, array $options, string $rule): \Sentry\MonitorConfig + { + $monitorSchedule = \Sentry\MonitorSchedule::crontab($rule); + $checkinMargin = (int) ($options['checkin_margin'] ?? $this->config->get('sentry.crons.checkin_margin', 5)); + $maxRuntime = (int) ($options['max_runtime'] ?? $this->config->get('sentry.crons.max_runtime', 15)); + + $timezone = null; + if (method_exists($event->crontab, 'getTimezone')) { + $timezone = $event->crontab->getTimezone(); + } + $timezone ??= $this->config->get('sentry.crons.timezone', date_default_timezone_get()); + + $failureIssueThreshold = isset($options['failure_issue_threshold']) ? (int) $options['failure_issue_threshold'] : null; + $recoveryThreshold = isset($options['recovery_threshold']) ? (int) $options['recovery_threshold'] : null; + + return new \Sentry\MonitorConfig( + schedule: $monitorSchedule, + checkinMargin: $checkinMargin, + maxRuntime: $maxRuntime, + timezone: $timezone, + failureIssueThreshold: $failureIssueThreshold, + recoveryThreshold: $recoveryThreshold, + ); + } +} diff --git a/src/sentry/src/Listener/AmqpExceptionListener.php b/src/sentry/src/Listener/AmqpExceptionListener.php deleted file mode 100644 index 592e93703..000000000 --- a/src/sentry/src/Listener/AmqpExceptionListener.php +++ /dev/null @@ -1,47 +0,0 @@ -switcher->isEnable('amqp')) { - return; - } - - match ($event::class) { - Event\FailToConsume::class => $this->captureException($event->getThrowable()), - default => $this->setupSentrySdk(), - }; - - match ($event::class) { - Event\AfterConsume::class, - Event\FailToConsume::class => $this->flushEvents(), - default => null, - }; - } -} diff --git a/src/sentry/src/Listener/AsyncQueueExceptionListener.php b/src/sentry/src/Listener/AsyncQueueExceptionListener.php deleted file mode 100644 index ae0b1551b..000000000 --- a/src/sentry/src/Listener/AsyncQueueExceptionListener.php +++ /dev/null @@ -1,50 +0,0 @@ -switcher->isEnable('async_queue')) { - return; - } - - match ($event::class) { - Event\RetryHandle::class, - Event\FailedHandle::class => $this->captureException($event->getThrowable()), - default => $this->setupSentrySdk(), - }; - - match ($event::class) { - Event\AfterHandle::class, - Event\RetryHandle::class, - Event\FailedHandle::class => $this->flushEvents(), - default => null, - }; - } -} diff --git a/src/sentry/src/Listener/CaptureExceptionListener.php b/src/sentry/src/Listener/CaptureExceptionListener.php deleted file mode 100644 index 1f56dd7e3..000000000 --- a/src/sentry/src/Listener/CaptureExceptionListener.php +++ /dev/null @@ -1,69 +0,0 @@ -captureException($throwable); - } catch (Throwable $e) { - $this->container->get(StdoutLoggerInterface::class)->error((string) $e); - } finally { - $hub->getClient()?->flush(); - } - } - - protected function setupSentrySdk(): void - { - if (Context::has(static::SETUP)) { - if ($this->container->has(StdoutLoggerInterface::class)) { - $this->container->get(StdoutLoggerInterface::class)->warning('SentrySdk has been setup.'); - } - - return; - } - - SentrySdk::init(); - Context::set(static::SETUP, true); - } - - protected function flushEvents(): void - { - Integration::flushEvents(); - } -} diff --git a/src/sentry/src/Listener/CommandExceptionListener.php b/src/sentry/src/Listener/CommandExceptionListener.php deleted file mode 100644 index 76f186416..000000000 --- a/src/sentry/src/Listener/CommandExceptionListener.php +++ /dev/null @@ -1,42 +0,0 @@ -switcher->isEnable('command')) { - return; - } - - match ($event::class) { - Event\FailToHandle::class => $this->captureException($event->getThrowable()), - Event\AfterExecute::class => $this->flushEvents(), - default => $this->setupSentrySdk(), - }; - } -} diff --git a/src/sentry/src/Listener/CrontabExceptionListener.php b/src/sentry/src/Listener/CrontabExceptionListener.php deleted file mode 100644 index 0b09fcbe9..000000000 --- a/src/sentry/src/Listener/CrontabExceptionListener.php +++ /dev/null @@ -1,52 +0,0 @@ -switcher->isEnable('crontab')) { - return; - } - - match ($event::class) { - Event\FailToExecute::class => $this->captureException($event->throwable), - default => $this->setupSentrySdk(), - }; - - match ($event::class) { - Event\AfterExecute::class, - Event\FailToExecute::class => $this->flushEvents(), - default => null, - }; - } -} diff --git a/src/sentry/src/Listener/DbQueryListener.php b/src/sentry/src/Listener/DbQueryListener.php deleted file mode 100644 index b3ae769a9..000000000 --- a/src/sentry/src/Listener/DbQueryListener.php +++ /dev/null @@ -1,102 +0,0 @@ - $this->queryExecutedHandler($event), - $event instanceof TransactionBeginning => $this->transactionHandler($event), - $event instanceof TransactionCommitted => $this->transactionHandler($event), - $event instanceof TransactionRolledBack => $this->transactionHandler($event), - default => null - }; - } - - /** - * @param object|QueryExecuted $event - */ - protected function queryExecutedHandler(object $event): void - { - if (! $this->switcher->isBreadcrumbEnable('sql_queries')) { - return; - } - - $data = ['connectionName' => $event->connectionName]; - - if ($event->time !== null) { - $data['executionTimeMs'] = $event->time; - } - - if ($this->switcher->isBreadcrumbEnable('sql_bindings')) { - $data['bindings'] = $event->bindings; - } - - Integration::addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_INFO, - Breadcrumb::TYPE_DEFAULT, - 'sql.query', - $event->sql, - $data - )); - } - - /** - * @param ConnectionEvent|object $event - */ - protected function transactionHandler(object $event): void - { - if (! $this->switcher->isBreadcrumbEnable('sql_transaction')) { - return; - } - - $data = [ - 'connectionName' => $event->connectionName, - ]; - - Integration::addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_INFO, - Breadcrumb::TYPE_DEFAULT, - 'sql.transaction', - $event::class, - $data - )); - } -} diff --git a/src/sentry/src/Listener/EventHandleListener.php b/src/sentry/src/Listener/EventHandleListener.php new file mode 100644 index 000000000..25561ceb4 --- /dev/null +++ b/src/sentry/src/Listener/EventHandleListener.php @@ -0,0 +1,554 @@ + $this->handleBootApplication($event), + + // Database events + DbEvent\QueryExecuted::class => $this->handleDbQueryExecuted($event), + DbEvent\TransactionBeginning::class, + DbEvent\TransactionCommitted::class, + DbEvent\TransactionRolledBack::class => $this->handleDbTransaction($event), + + // Redis events + RedisEvent\CommandExecuted::class => $this->handleRedisCommandExecuted($event), + + // Request events + HttpEvent\RequestReceived::class, + RpcEvent\RequestReceived::class => $this->handleRequestReceived($event), + HttpEvent\RequestTerminated::class, + RpcEvent\RequestTerminated::class => $this->handleRequestTerminated($event), + + // Command events + CommandEvent\BeforeHandle::class => $this->handleCommandStarting($event), + CommandEvent\FailToHandle::class => $this->handleCommandFailed($event), + CommandEvent\AfterExecute::class => $this->handleCommandFinished($event), + + // Async Queue events + AsyncQueueEvent\BeforeHandle::class => $this->handleAsyncQueueJobProcessing($event), + AsyncQueueEvent\AfterHandle::class => $this->handleAsyncQueueJobProcessed($event), + AsyncQueueEvent\RetryHandle::class, + AsyncQueueEvent\FailedHandle::class => $this->handleAsyncQueueJobRetryOrFailed($event), + + // Crontab events + CrontabEvent\BeforeExecute::class => $this->handleCrontabTaskStarting($event), + CrontabEvent\AfterExecute::class => $this->handleCrontabTaskFinished($event), + CrontabEvent\FailToExecute::class => $this->handleCrontabTaskFailed($event), + + // AMQP events + AmqpEvent\BeforeConsume::class => $this->handleAmqpMessageProcessing($event), + AmqpEvent\AfterConsume::class => $this->handleAmqpMessageProcessed($event), + AmqpEvent\FailToConsume::class => $this->handleAmqpMessageFailed($event), + + // Kafka events + KafkaEvent\BeforeConsume::class => $this->handleKafkaMessageProcessing($event), + KafkaEvent\AfterConsume::class => $this->handleKafkaMessageProcessed($event), + KafkaEvent\FailToConsume::class => $this->handleKafkaMessageFailed($event), + + default => null, + }; + } + + protected function captureException(?Throwable $throwable): void + { + if (! $throwable) { + return; + } + + $hub = SentrySdk::getCurrentHub(); + + try { + $hub->captureException($throwable); + } catch (Throwable $e) { + $this->logger->error((string) $e); + } finally { + $hub->getClient()?->flush(); + } + } + + protected function setupSentrySdk(): void + { + Context::getOrSet(static::HUB, fn () => SentrySdk::init()); + } + + protected function flushEvents(): void + { + try { + Integration::flushEvents(); + } catch (Throwable $e) { + $this->logger->error((string) $e); + } + } + + /** + * @param BootApplication $event + */ + protected function handleBootApplication(object $event): void + { + $this->setupRequestLifecycle(); + $this->setupRedisEventEnable(); + } + + protected function setupRequestLifecycle(): void + { + foreach ($this->missingKeys as $key) { + if (! $this->config->has($key)) { + $this->config->set($key, true); + } + } + + if ( + ! $this->switcher->isEnable('request') + && ! $this->switcher->isTracingEnable('request') + ) { + return; + } + + $servers = $this->config->get('server.servers', []); + + foreach ($servers as &$server) { + $callbacks = $server['callbacks'] ?? []; + $handler = $callbacks[Event::ON_REQUEST][0] ?? $callbacks[Event::ON_RECEIVE][0] ?? null; + + if (! $handler) { + continue; + } + + if ( + is_a($handler, HttpServer::class, true) + || is_a($handler, RpcServer::class, true) + ) { + $server['options'] ??= []; + $server['options']['enable_request_lifecycle'] = true; + } + } + + $this->config->set('server.servers', $servers); + } + + protected function setupRedisEventEnable(): void + { + if (! $this->config->has('redis')) { + return; + } + + foreach ($this->config->get('redis', []) as $pool => $_) { + $this->config->set("redis.{$pool}.event.enable", true); + } + } + + /** + * @param DbEvent\QueryExecuted $event + */ + protected function handleDbQueryExecuted(object $event): void + { + if (! $this->switcher->isBreadcrumbEnable('sql_queries')) { + return; + } + + $data = ['connectionName' => $event->connectionName]; + + if ($event->time !== null) { + $data['executionTimeMs'] = $event->time; + } + + if ($this->switcher->isBreadcrumbEnable('sql_bindings')) { + $data['bindings'] = $event->bindings; + } + + Integration::addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_DEFAULT, + 'sql.query', + $event->sql, + $data + )); + } + + /** + * @param DbEvent\TransactionBeginning|DbEvent\TransactionCommitted|DbEvent\TransactionRolledBack $event + */ + protected function handleDbTransaction(object $event): void + { + if (! $this->switcher->isBreadcrumbEnable('sql_transaction')) { + return; + } + + $data = ['connectionName' => $event->connectionName]; + + Integration::addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_DEFAULT, + 'sql.transaction', + $event::class, + $data + )); + } + + /** + * @param RedisEvent\CommandExecuted $event + */ + protected function handleRedisCommandExecuted(object $event): void + { + if (! $this->switcher->isBreadcrumbEnable('redis')) { + return; + } + + Integration::addBreadcrumb(new Breadcrumb( + Breadcrumb::LEVEL_INFO, + Breadcrumb::TYPE_DEFAULT, + 'redis', + $event->command, + [ + 'arguments' => $event->parameters, + 'result' => $event->result, + 'duration' => $event->time * 1000, + ] + )); + } + + /** + * @param HttpEvent\RequestReceived|RpcEvent\RequestReceived $event + */ + protected function handleRequestReceived(object $event): void + { + if (! $this->switcher->isEnable('request')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param HttpEvent\RequestTerminated|RpcEvent\RequestTerminated $event + */ + protected function handleRequestTerminated(object $event): void + { + if (! $this->switcher->isEnable('request')) { + return; + } + + $this->captureException($event->exception); + $this->flushEvents(); + } + + /** + * @param CommandEvent\BeforeHandle $event + */ + protected function handleCommandStarting(object $event): void + { + if (! $this->switcher->isEnable('command')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param CommandEvent\FailToHandle $event + */ + protected function handleCommandFailed(object $event): void + { + if (! $this->switcher->isEnable('command')) { + return; + } + + $this->captureException($event->getThrowable()); + $this->flushEvents(); + } + + /** + * @param CommandEvent\AfterExecute $event + */ + protected function handleCommandFinished(object $event): void + { + if (! $this->switcher->isEnable('command')) { + return; + } + + $this->flushEvents(); + } + + /** + * @param AsyncQueueEvent\BeforeHandle $event + */ + protected function handleAsyncQueueJobProcessing(object $event): void + { + if (! $this->switcher->isEnable('async_queue')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param AsyncQueueEvent\AfterHandle $event + */ + protected function handleAsyncQueueJobProcessed(object $event): void + { + if (! $this->switcher->isEnable('async_queue')) { + return; + } + + $this->flushEvents(); + } + + /** + * @param AsyncQueueEvent\RetryHandle|AsyncQueueEvent\FailedHandle $event + */ + protected function handleAsyncQueueJobRetryOrFailed(object $event): void + { + if (! $this->switcher->isEnable('async_queue')) { + return; + } + + $this->captureException($event->getThrowable()); + $this->flushEvents(); + } + + /** + * @param CrontabEvent\BeforeExecute $event + */ + protected function handleCrontabTaskStarting(object $event): void + { + if (! $this->switcher->isEnable('crontab')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param CrontabEvent\AfterExecute $event + */ + protected function handleCrontabTaskFinished(object $event): void + { + if (! $this->switcher->isEnable('crontab')) { + return; + } + + $this->flushEvents(); + } + + /** + * @param CrontabEvent\FailToExecute $event + */ + protected function handleCrontabTaskFailed(object $event): void + { + if (! $this->switcher->isEnable('crontab')) { + return; + } + + $this->captureException($event->throwable); + $this->flushEvents(); + } + + /** + * @param AmqpEvent\BeforeConsume $event + */ + protected function handleAmqpMessageProcessing(object $event): void + { + if (! $this->switcher->isEnable('amqp')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param AmqpEvent\AfterConsume $event + */ + protected function handleAmqpMessageProcessed(object $event): void + { + if (! $this->switcher->isEnable('amqp')) { + return; + } + + $this->flushEvents(); + } + + /** + * @param AmqpEvent\FailToConsume $event + */ + protected function handleAmqpMessageFailed(object $event): void + { + if (! $this->switcher->isEnable('amqp')) { + return; + } + + $this->captureException($event->getThrowable()); + $this->flushEvents(); + } + + /** + * @param KafkaEvent\BeforeConsume $event + */ + protected function handleKafkaMessageProcessing(object $event): void + { + if (! $this->switcher->isEnable('kafka')) { + return; + } + + $this->setupSentrySdk(); + } + + /** + * @param KafkaEvent\AfterConsume $event + */ + protected function handleKafkaMessageProcessed(object $event): void + { + if (! $this->switcher->isEnable('kafka')) { + return; + } + + $this->flushEvents(); + } + + /** + * @param KafkaEvent\FailToConsume $event + */ + protected function handleKafkaMessageFailed(object $event): void + { + if (! $this->switcher->isEnable('kafka')) { + return; + } + + $this->captureException($event->getThrowable()); + $this->flushEvents(); + } +} diff --git a/src/sentry/src/Listener/KafkaExceptionListener.php b/src/sentry/src/Listener/KafkaExceptionListener.php deleted file mode 100644 index 6a4f4a2b9..000000000 --- a/src/sentry/src/Listener/KafkaExceptionListener.php +++ /dev/null @@ -1,47 +0,0 @@ -switcher->isEnable('kafka')) { - return; - } - - match ($event::class) { - Event\FailToConsume::class => $this->captureException($event->getThrowable()), - default => $this->setupSentrySdk(), - }; - - match ($event::class) { - Event\AfterConsume::class, - Event\FailToConsume::class => $this->flushEvents(), - default => null, - }; - } -} diff --git a/src/sentry/src/Listener/RedisCommandExecutedListener.php b/src/sentry/src/Listener/RedisCommandExecutedListener.php deleted file mode 100644 index 218feec12..000000000 --- a/src/sentry/src/Listener/RedisCommandExecutedListener.php +++ /dev/null @@ -1,57 +0,0 @@ -switcher->isBreadcrumbEnable('redis') - || ! $event instanceof CommandExecuted - ) { - return; - } - - Integration::addBreadcrumb(new Breadcrumb( - Breadcrumb::LEVEL_INFO, - Breadcrumb::TYPE_DEFAULT, - 'redis', - $event->command, - [ - 'arguments' => $event->parameters, - 'result' => $event->result, - 'duration' => $event->time * 1000, - ] - )); - } -} diff --git a/src/sentry/src/Listener/RequestExceptionListener.php b/src/sentry/src/Listener/RequestExceptionListener.php deleted file mode 100644 index 58f82d4fa..000000000 --- a/src/sentry/src/Listener/RequestExceptionListener.php +++ /dev/null @@ -1,51 +0,0 @@ -switcher->isEnable('request')) { - return; - } - - match ($event::class) { - RequestTerminated::class, RpcRequestTerminated::class => $this->captureException($event->exception), - default => $this->setupSentrySdk(), - }; - - match ($event::class) { - RequestTerminated::class, - RpcRequestTerminated::class => $this->flushEvents(), - default => null, - }; - } -} diff --git a/src/sentry/src/Listener/SetRedisEventEnableListener.php b/src/sentry/src/Listener/SetRedisEventEnableListener.php deleted file mode 100644 index 14608ab72..000000000 --- a/src/sentry/src/Listener/SetRedisEventEnableListener.php +++ /dev/null @@ -1,41 +0,0 @@ -config->has('redis')) { - return; - } - - foreach ($this->config->get('redis', []) as $pool => $_) { - $this->config->set("redis.{$pool}.event.enable", true); - } - } -} diff --git a/src/sentry/src/Listener/SetRequestLifecycleListener.php b/src/sentry/src/Listener/SetRequestLifecycleListener.php deleted file mode 100644 index 680f4d6f0..000000000 --- a/src/sentry/src/Listener/SetRequestLifecycleListener.php +++ /dev/null @@ -1,104 +0,0 @@ -config->has($key)) { - $this->config->set($key, true); - } - } - - if ( - ! $this->switcher->isEnable('request') - && ! $this->switcher->isTracingEnable('request') - ) { - return; - } - - $servers = $this->config->get('server.servers', []); - - foreach ($servers as &$server) { - $callbacks = $server['callbacks'] ?? []; - $handler = $callbacks[Event::ON_REQUEST][0] ?? $callbacks[Event::ON_RECEIVE][0] ?? null; - - if (! $handler) { - continue; - } - - if ( - is_a($handler, HttpServer::class, true) - || is_a($handler, RpcServer::class, true) - ) { - $server['options'] ??= []; - $server['options']['enable_request_lifecycle'] = true; - } - } - - $this->config->set('server.servers', $servers); - } -} diff --git a/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php b/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php index c2682623f..c96d72b50 100644 --- a/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php +++ b/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php @@ -61,8 +61,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) public function handleGet(ProceedingJoinPoint $proceedingJoinPoint) { - $destinationName = $proceedingJoinPoint->arguments['keys']['name'] ?? 'default'; - Context::set('sentry.messaging.destination.name', $destinationName); + Context::set( + 'sentry.messaging.destination.name', + $proceedingJoinPoint->arguments['keys']['name'] ?? 'default' + ); return $proceedingJoinPoint->process(); } @@ -87,19 +89,17 @@ public function handlePush(ProceedingJoinPoint $proceedingJoinPoint) $messageId = method_exists($job, 'getId') ? $job->getId() : uniqid('async_queue_', true); $destinationName = Context::get('sentry.messaging.destination.name', 'default'); $bodySize = (fn ($job) => strlen($this->packer->pack($job)))->call($driver, $job); - $data = [ + $span->setData([ 'messaging.system' => 'async_queue', 'messaging.operation' => 'publish', 'messaging.message.id' => $messageId, 'messaging.message.body.size' => $bodySize, 'messaging.destination.name' => $destinationName, - ]; - $data += match (true) { - $driver instanceof RedisDriver => $this->buildSpanDataOfRedisDriver($driver), - default => [] - }; + ]); + if ($driver instanceof RedisDriver) { + $span->setData($this->buildSpanDataOfRedisDriver($driver)); + } - $span->setData($data); $carrier = Carrier::fromSpan($span)->with([ 'publish_time' => microtime(true), 'message_id' => $messageId, diff --git a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php index 13b8b111f..921294829 100644 --- a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php @@ -52,10 +52,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } catch (Throwable $exception) { $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { $span?->setData([ diff --git a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php index 4eba1167f..9f475af0d 100644 --- a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php @@ -85,10 +85,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } catch (Throwable $exception) { $transaction->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { $transaction->setData([ diff --git a/src/sentry/src/Tracing/Aspect/DbAspect.php b/src/sentry/src/Tracing/Aspect/DbAspect.php index dbb7dd08a..e27362352 100644 --- a/src/sentry/src/Tracing/Aspect/DbAspect.php +++ b/src/sentry/src/Tracing/Aspect/DbAspect.php @@ -114,10 +114,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } catch (Throwable $exception) { $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { $span?->setData([ diff --git a/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php b/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php index a157d9f65..d480d40d3 100644 --- a/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php +++ b/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php @@ -73,7 +73,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $data = [ + $span->setData([ 'coroutine.id' => Coroutine::id(), 'db.system' => 'elasticsearch', 'db.operation.name' => $proceedingJoinPoint->methodName, @@ -82,9 +82,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) 'server.host' => '', // TODO 'server.port' => '', // TODO 'arguments' => json_encode($proceedingJoinPoint->arguments['keys'], JSON_UNESCAPED_UNICODE), - ]; - - $span->setData($data); + ]); try { $result = $proceedingJoinPoint->process(); @@ -96,10 +94,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } catch (Throwable $exception) { $span->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { $span->setData([ diff --git a/src/sentry/src/Tracing/Aspect/GrpcAspect.php b/src/sentry/src/Tracing/Aspect/GrpcAspect.php index b2eeecac9..3bb765d39 100644 --- a/src/sentry/src/Tracing/Aspect/GrpcAspect.php +++ b/src/sentry/src/Tracing/Aspect/GrpcAspect.php @@ -41,7 +41,6 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } $options = $proceedingJoinPoint->arguments['keys']['options']; - $data = [ 'grpc.method' => $method = $proceedingJoinPoint->arguments['keys']['method'], 'grpc.options' => $options, @@ -84,14 +83,16 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } catch (Throwable $exception) { $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $span?->setData([ + 'exception.stack_trace' => (string) $exception, ]); - $this->switcher->isTracingExtraTagEnable('exception.stack_trace') && $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); + } throw $exception; } finally { diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index 67637e3c4..d5896819d 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -94,7 +94,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($options, $guzzleConfig, $onStats, $span) { $request = $stats->getRequest(); $uri = $request->getUri(); - $data = [ + $span->setData([ // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http 'http.query' => $uri->getQuery(), 'http.fragment' => $uri->getFragment(), @@ -113,10 +113,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) 'http.guzzle.config' => $guzzleConfig, 'http.guzzle.options' => $options ?? [], 'duration' => $stats->getTransferTime() * 1000, // in milliseconds - ]; + ]); if ($response = $stats->getResponse()) { - $data = array_merge($data, [ + $span->setData([ 'http.response.status_code' => $response->getStatusCode(), 'http.response.reason' => $response->getReasonPhrase(), 'http.response.headers' => $response->getHeaders(), @@ -135,10 +135,12 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) if ($isTextual && $body->isSeekable()) { $pos = $body->tell(); - $data['http.response.body.contents'] = \GuzzleHttp\Psr7\Utils::copyToString($body, 8192); // 8KB 上限 + $span->setData([ + 'http.response.body.contents' => \GuzzleHttp\Psr7\Utils::copyToString($body, 8192), // 8KB 上限 + ]); $body->seek($pos); } else { - $data['http.response.body.contents'] = '[binary omitted]'; + $span->setData(['http.response.body.contents' => '[binary omitted]']); } } @@ -146,29 +148,31 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { $span->setTags([ - 'error' => true, + 'error' => 'true', 'http.response.reason' => $response->getReasonPhrase(), ]); } } if ($stats->getHandlerErrorData()) { - $span->setStatus(SpanStatus::internalError()); - $exception = $stats->getHandlerErrorData() instanceof Throwable ? $stats->getHandlerErrorData() : new Error(); - $span->setTags([ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.code' => $exception->getCode(), - ]); + $exception = $stats->getHandlerErrorData() instanceof Throwable + ? $stats->getHandlerErrorData() + : new Error(); + $span->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data = array_merge($data, [ + $span->setData([ 'exception.message' => $exception->getMessage(), 'exception.stack_trace' => (string) $exception, ]); } } - $span->setData($data)->finish(); + $span->finish(); if (is_callable($onStats)) { ($onStats)($stats); diff --git a/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php b/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php index 5327ad6fa..038548462 100644 --- a/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php +++ b/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php @@ -36,9 +36,8 @@ class KafkaProducerAspect extends AbstractAspect 'Hyperf\Kafka\Producer::sendBatchAsync', ]; - public function __construct( - protected Switcher $switcher - ) { + public function __construct(protected Switcher $switcher) + { } public function process(ProceedingJoinPoint $proceedingJoinPoint) @@ -57,8 +56,8 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) protected function sendAsync(ProceedingJoinPoint $proceedingJoinPoint) { $span = $this->startSpan( - 'queue.publish', - sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), + op: 'queue.publish', + description: sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), origin: 'auto.kafka' ); diff --git a/src/sentry/src/Tracing/Aspect/RedisAspect.php b/src/sentry/src/Tracing/Aspect/RedisAspect.php index e8037037f..667d6149a 100644 --- a/src/sentry/src/Tracing/Aspect/RedisAspect.php +++ b/src/sentry/src/Tracing/Aspect/RedisAspect.php @@ -55,11 +55,9 @@ class_exists(CommandExecuted::class) } $arguments = $proceedingJoinPoint->arguments['keys']; - $poolName = (fn () => $this->poolName ?? null)->call($proceedingJoinPoint->getInstance()); $pool = $this->container->get(PoolFactory::class)->getPool($poolName); $config = (fn () => $this->config ?? [])->call($pool); - $data = [ 'coroutine.id' => Coroutine::id(), 'db.system' => 'redis', @@ -78,14 +76,13 @@ class_exists(CommandExecuted::class) // $op = sprintf('%s %s', $arguments['name'], $arguments['arguments']['key'] ?? ''); // $description = sprintf('%s::%s()', $proceedingJoinPoint->className, $arguments['name']); $key = $arguments['arguments'][0] ?? ''; - $op = 'db.redis'; $description = sprintf( '%s %s', strtoupper($arguments['name'] ?? ''), is_array($key) ? implode(',', $key) : $key ); $span = $this->startSpan( - op: $op, + op: 'db.redis', description: $description, origin: 'auto.cache.redis', )?->setData($data); @@ -100,19 +97,19 @@ class_exists(CommandExecuted::class) if ($this->switcher->isTracingExtraTagEnable('redis.result')) { $span->setData(['redis.result' => $result]); } - } catch (Throwable $exception) { - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $span->setData(['exception.stack_trace' => (string) $exception]); - } + } catch (Throwable $e) { $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'error' => 'true', + 'exception.class' => $e::class, + 'exception.message' => $e->getMessage(), + 'exception.code' => (string) $e->getCode(), ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $span->setData(['exception.stack_trace' => (string) $e]); + } - throw $exception; + throw $e; } finally { $span?->finish(); } diff --git a/src/sentry/src/Tracing/Aspect/RpcAspect.php b/src/sentry/src/Tracing/Aspect/RpcAspect.php index 53a6e95fa..2a4b025f4 100644 --- a/src/sentry/src/Tracing/Aspect/RpcAspect.php +++ b/src/sentry/src/Tracing/Aspect/RpcAspect.php @@ -113,13 +113,6 @@ private function handleGenerateRpcPath(ProceedingJoinPoint $proceedingJoinPoint) private function handleSend(ProceedingJoinPoint $proceedingJoinPoint) { - $data = (array) Context::get(static::DATA, []); - $data['rpc.arguments'] = $proceedingJoinPoint->arguments['keys']; - - if ($this->container->has(Rpc\Context::class)) { - $data['rpc.context'] = $this->container->get(Rpc\Context::class)->getData(); - } - // TODO // 'server.address' => '', // 'server.port' => '', @@ -127,27 +120,33 @@ private function handleSend(ProceedingJoinPoint $proceedingJoinPoint) /** @var null|Span $span */ $span = Context::get(static::SPAN); + $span?->setData((array) Context::get(static::DATA, [])); + + if ($this->container->has(Rpc\Context::class)) { + $span?->setData(['rpc.context' => $this->container->get(Rpc\Context::class)->getData()]); + } + try { $result = $proceedingJoinPoint->process(); if ($this->switcher->isTracingExtraTagEnable('rpc.result')) { - $data['rpc.result'] = $result; + $span?->setData(['rpc.result' => $result]); } } catch (Throwable $exception) { $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; + $span?->setData(['exception.stack_trace' => (string) $exception]); } throw $exception; } finally { - $span?->setData($data)->finish(); + $span?->finish(); Context::destroy(static::SPAN); Context::destroy(static::DATA); diff --git a/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php b/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php index 1f4f9b4ac..cb08ee720 100644 --- a/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php +++ b/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php @@ -43,9 +43,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $data = [ - 'coroutine.id' => Coroutine::id(), - ]; + $data = ['coroutine.id' => Coroutine::id()]; $methodName = $proceedingJoinPoint->methodName; if (in_array($methodName, ['__call', '__callStatic'])) { @@ -63,32 +61,28 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $methodName ), asParent: true - ); + )?->setData($data); try { $result = $proceedingJoinPoint->process(); - if (! $span) { - return $result; - } - if ($this->switcher->isTracingExtraTagEnable('annotation.result')) { - $data['annotation.result'] = $result; + $span?->setData(['annotation.result' => $result]); } } catch (Throwable $exception) { - $span->setStatus(SpanStatus::internalError()) + $span?->setStatus(SpanStatus::internalError()) ->setTags([ - 'error' => true, + 'error' => 'true', 'exception.class' => $exception::class, 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), + 'exception.code' => (string) $exception->getCode(), ]); if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; + $span?->setData(['exception.stack_trace' => (string) $exception]); } throw $exception; } finally { - $span->setData($data)->finish(microtime(true)); + $span?->finish(microtime(true)); // Reset root span SentrySdk::getCurrentHub()->setSpan($parent); diff --git a/src/sentry/src/Tracing/Listener/EventHandleListener.php b/src/sentry/src/Tracing/Listener/EventHandleListener.php new file mode 100644 index 000000000..d8a98ca9d --- /dev/null +++ b/src/sentry/src/Tracing/Listener/EventHandleListener.php @@ -0,0 +1,713 @@ +ignoreCommands = (array) $this->config->get('sentry.ignore_commands', []); + } + + public function listen(): array + { + return [ + // Database events + QueryExecuted::class, + + // Request events + HttpEvent\RequestReceived::class, + HttpEvent\RequestHandled::class, + RpcEvent\RequestReceived::class, + RpcEvent\RequestHandled::class, + + // Command events + CommandEvent\BeforeHandle::class, + CommandEvent\AfterExecute::class, + + // Redis events + RedisEvent\CommandExecuted::class, + + // Crontab events + CrontabEvent\BeforeExecute::class, + CrontabEvent\FailToExecute::class, + CrontabEvent\AfterExecute::class, + + // AMQP events + AmqpEvent\BeforeConsume::class, + AmqpEvent\AfterConsume::class, + AmqpEvent\FailToConsume::class, + + // Kafka events + KafkaEvent\BeforeConsume::class, + KafkaEvent\AfterConsume::class, + KafkaEvent\FailToConsume::class, + + // AsyncQueue events + AsyncQueueEvent\BeforeHandle::class, + AsyncQueueEvent\AfterHandle::class, + AsyncQueueEvent\RetryHandle::class, + AsyncQueueEvent\FailedHandle::class, + ]; + } + + public function process(object $event): void + { + match ($event::class) { + // Database + QueryExecuted::class => $this->handleDbQueryExecuted($event), + + // Request + HttpEvent\RequestReceived::class, + RpcEvent\RequestReceived::class => $this->handleRequestReceived($event), + HttpEvent\RequestHandled::class, + RpcEvent\RequestHandled::class => $this->handleRequestHandled($event), + + // Command + CommandEvent\BeforeHandle::class => $this->handleCommandStarting($event), + CommandEvent\AfterExecute::class => $this->handleCommandFinished($event), + + // Redis + RedisEvent\CommandExecuted::class => $this->handleRedisCommandExecuted($event), + + // Crontab + CrontabEvent\BeforeExecute::class => $this->handleCrontabTaskStarting($event), + CrontabEvent\FailToExecute::class, + CrontabEvent\AfterExecute::class => $this->handleCrontabTaskFinished($event), + + // AMQP + AmqpEvent\BeforeConsume::class => $this->handleAmqpMessageProcessing($event), + AmqpEvent\AfterConsume::class, + AmqpEvent\FailToConsume::class => $this->handleAmqpMessageProcessed($event), + + // Kafka + KafkaEvent\BeforeConsume::class => $this->handleKafkaMessageProcessing($event), + KafkaEvent\AfterConsume::class, + KafkaEvent\FailToConsume::class => $this->handleKafkaMessageProcessed($event), + + // AsyncQueue + AsyncQueueEvent\BeforeHandle::class => $this->handleAsyncQueueJobProcessing($event), + AsyncQueueEvent\RetryHandle::class, + AsyncQueueEvent\FailedHandle::class, + AsyncQueueEvent\AfterHandle::class => $this->handleAsyncQueueJobProcessed($event), + + default => null, + }; + } + + private function handleDbQueryExecuted(QueryExecuted $event): void + { + if (! $this->switcher->isTracingSpanEnable('sql_queries')) { + return; + } + if (! SentrySdk::getCurrentHub()->getSpan()) { + return; + } + + $data = [ + 'coroutine.id' => Coroutine::id(), + 'db.system' => $event->connection->getDriverName(), + 'db.name' => $event->connection->getDatabaseName(), + ]; + + $sqlParse = SqlParser::parse($event->sql); + if (! empty($sqlParse['operation'])) { + $data['db.operation.name'] = $sqlParse['operation']; + } + if (! empty($sqlParse['table'])) { + $data['db.collection.name'] = $sqlParse['table']; + } + foreach ($event->bindings as $key => $value) { + $data['db.parameter.' . $key] = $value; + } + + $pool = $this->container->get(PoolFactory::class)->getPool($event->connectionName); + $data += [ + 'db.pool.name' => $event->connectionName, + 'db.pool.max' => $pool->getOption()->getMaxConnections(), + 'db.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), + 'db.pool.idle' => $pool->getConnectionsInChannel(), + 'db.pool.using' => $pool->getCurrentConnections(), + 'db.sql.bindings' => $event->bindings, + ]; + + $startTimestamp = microtime(true) - $event->time / 1000; + + $op = 'db.sql.query'; + $description = $event->sql; + + $this->startSpan( + op: $op, + description: $description, + origin: 'auto.db' + )?->setData($data) + ->setStartTimestamp($startTimestamp) + ->finish($startTimestamp + $event->time / 1000); + } + + private function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\RequestReceived $event): void + { + if (! $this->switcher->isTracingEnable('request')) { + return; + } + + $request = $event->request; + /** @var Dispatched $dispatched */ + $dispatched = $request->getAttribute(Dispatched::class); + + if (! $dispatched->isFound() && ! $this->switcher->isTracingEnable('missing_routes')) { + return; + } + + $serverName = $dispatched->serverName ?? 'http'; + $path = $request->getUri()->getPath(); + $method = strtoupper($request->getMethod()); + + [$route, $routeParams, $routeCallback] = $this->parseRoute($dispatched); + + [$name, $source] = match (strtolower($this->source)) { + 'custom' => [$routeCallback, TransactionSource::custom()], + 'url' => [$path, TransactionSource::url()], + default => [$route, TransactionSource::route()], + }; + + $transaction = $this->startRequestTransaction( + request: $request, + name: $name, + op: sprintf('%s.server', $serverName), + description: sprintf('%s %s', $method, $path), + origin: 'auto.request', + source: $source, + ); + + if (! $transaction->getSampled()) { + return; + } + + $data = [ + 'url.scheme' => $request->getUri()->getScheme(), + 'url.path' => $path, + 'http.request.method' => $method, + 'http.route' => $route, + 'http.route.params' => $routeParams, + ]; + foreach ($request->getHeaders() as $key => $value) { + $data['http.request.header.' . $key] = implode(', ', $value); + } + if ($this->container->has(RpcContext::class)) { + $data['rpc.context'] = $this->container->get(RpcContext::class)->getData(); + } + + $transaction->setData($data); + + $span = $this->startSpan( + op: 'request.received', + description: 'request.received', + origin: 'auto.request.received', + asParent: true + ); + + defer(function () use ($transaction, $span) { + $span?->finish(); + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(); + }); + } + + private function handleRequestHandled(HttpEvent\RequestHandled|RpcEvent\RequestHandled $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if ( + ! $transaction + || ! $transaction->getSampled() + || ! $traceId = (string) $transaction->getTraceId() + ) { + return; + } + + if ($event instanceof RpcEvent\RequestHandled) { + if ($this->container->has(RpcContext::class)) { + $this->container->get(RpcContext::class)->set('sentry-trace-id', $traceId); + } + } elseif ($event->response instanceof ResponsePlusInterface) { + $event->response->setHeader('sentry-trace-id', $traceId); + } + + $transaction->setHttpStatus($event->response->getStatusCode()); + + if ($exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData([ + 'exception.stack_trace' => (string) $exception, + ]); + } + } + } + + private function handleCommandStarting(CommandEvent\BeforeHandle $event): void + { + if ( + ! $this->switcher->isTracingEnable('command') + || Str::is($this->ignoreCommands, $event->getCommand()->getName()) + ) { + return; + } + + $command = $event->getCommand(); + + $this->continueTrace( + name: $command->getName() ?: '', + op: 'console.command', + description: $command->getDescription(), + origin: 'auto.command', + source: TransactionSource::custom() + ); + } + + private function handleCommandFinished(CommandEvent\AfterExecute $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if (! $transaction || ! $transaction->getSampled()) { + return; + } + + $command = $event->getCommand(); + $exitCode = (fn () => $this->exitCode ?? SymfonyCommand::SUCCESS)->call($command); + + $transaction->setData([ + 'command.arguments' => (fn () => $this->input->getArguments())->call($command), + 'command.options' => (fn () => $this->input->getOptions())->call($command), + ])->setTags([ + 'command.exit_code' => (string) $exitCode, + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData([ + 'exception.stack_trace' => (string) $exception, + ]); + } + } + + $transaction->setStatus( + $exitCode == SymfonyCommand::SUCCESS ? SpanStatus::ok() : SpanStatus::internalError() + ); + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(microtime(true)); + } + + private function handleRedisCommandExecuted(RedisEvent\CommandExecuted $event): void + { + if (! $this->switcher->isTracingSpanEnable('redis')) { + return; + } + + $pool = $this->container->get(RedisPoolFactory::class)->getPool($event->connectionName); + $config = $this->config->get('redis.' . $event->connectionName, []); + $redisStatement = (string) new RedisCommand($event->command, $event->parameters); + + $span = $this->startSpan( + op: 'db.redis', + description: $redisStatement, + origin: 'auto.cache.redis', + ); + + if (! $span) { + return; + } + + $span->setData([ + 'coroutine.id' => Coroutine::id(), + 'db.system' => 'redis', + 'db.statement' => $redisStatement, + 'db.redis.connection' => $event->connectionName, + 'db.redis.database_index' => $config['db'] ?? 0, + 'db.redis.parameters' => $event->parameters, + 'db.redis.pool.name' => $event->connectionName, + 'db.redis.pool.max' => $pool->getOption()->getMaxConnections(), + 'db.redis.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), + 'db.redis.pool.idle' => $pool->getConnectionsInChannel(), + 'db.redis.pool.using' => $pool->getCurrentConnections(), + 'duration' => $event->time * 1000, + ]); + + if ($this->switcher->isTracingExtraTagEnable('redis.result')) { + $span->setData(['db.redis.result' => $event->result]); + } + + if ($exception = $event->throwable) { + $span->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $span->setData(['exception.stack_trace' => (string) $exception]); + } + } + + $span->finish(); + } + + private function handleCrontabTaskStarting(CrontabEvent\BeforeExecute $event): void + { + if (! $this->switcher->isTracingEnable('crontab')) { + return; + } + + $crontab = $event->crontab; + + $this->continueTrace( + name: $crontab->getName() ?: '', + op: 'crontab.run', + description: $crontab->getMemo(), + origin: 'auto.crontab', + source: TransactionSource::task() + ); + } + + private function handleCrontabTaskFinished(CrontabEvent\FailToExecute|CrontabEvent\AfterExecute $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if (! $transaction || ! $transaction->getSampled()) { + return; + } + + $crontab = $event->crontab; + $transaction->setTags([ + 'crontab.rule' => $crontab->getRule(), + 'crontab.type' => $crontab->getType(), + 'crontab.options.is_single' => $crontab->isSingleton(), + 'crontab.options.is_on_one_server' => $crontab->isOnOneServer(), + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } + } + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(microtime(true)); + } + + private function handleAmqpMessageProcessing(AmqpEvent\BeforeConsume $event): void + { + if (! $this->switcher->isTracingEnable('amqp')) { + return; + } + + $message = $event->getMessage(); + $carrier = null; + + if (method_exists($event, 'getAMQPMessage')) { + /** @var AMQPMessage $amqpMessage */ + $amqpMessage = $event->getAMQPMessage(); + /** @var null|AMQPTable $applicationHeaders */ + $applicationHeaders = $amqpMessage->has('application_headers') ? $amqpMessage->get('application_headers') : null; + if ($applicationHeaders && isset($applicationHeaders[Constants::TRACE_CARRIER])) { + $carrier = Carrier::fromJson($applicationHeaders[Constants::TRACE_CARRIER]); + Context::set(Constants::TRACE_CARRIER, $carrier); + } + } + + $this->continueTrace( + sentryTrace: $carrier?->getSentryTrace() ?? '', + baggage: $carrier?->getBaggage() ?? '', + name: $message::class, + op: 'queue.process', + description: $message::class, + origin: 'auto.amqp', + source: TransactionSource::custom() + ); + } + + private function handleAmqpMessageProcessed(AmqpEvent\AfterConsume|AmqpEvent\FailToConsume $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if (! $transaction || ! $transaction->getSampled()) { + return; + } + + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER); + + /** @var ConsumerMessage $message */ + $message = $event->getMessage(); + $transaction->setData([ + 'messaging.system' => 'amqp', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => 0, + 'messaging.destination.name' => $carrier?->get('destination_name') ?: implode(', ', (array) $message->getRoutingKey()), + 'messaging.amqp.message.type' => $message->getTypeString(), + 'messaging.amqp.message.routing_key' => $message->getRoutingKey(), + 'messaging.amqp.message.exchange' => $message->getExchange(), + 'messaging.amqp.message.queue' => $message->getQueue(), + 'messaging.amqp.message.pool_name' => $message->getPoolName(), + 'messaging.amqp.message.result' => $event instanceof AmqpEvent\AfterConsume ? $event->getResult()->value : 'fail', + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } + } + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(microtime(true)); + } + + private function handleKafkaMessageProcessing(KafkaEvent\BeforeConsume $event): void + { + if (! $this->switcher->isTracingEnable('kafka')) { + return; + } + + $consumer = $event->getConsumer(); + $message = $event->getData(); + $carrier = null; + + if ($message instanceof ConsumeMessage) { + foreach ($message->getHeaders() as $header) { + if ($header->getHeaderKey() === Constants::TRACE_CARRIER) { + $carrier = Carrier::fromJson($header->getValue()); + Context::set(Constants::TRACE_CARRIER, $carrier); + break; + } + } + } + + $this->continueTrace( + sentryTrace: $carrier?->getSentryTrace() ?? '', + baggage: $carrier?->getBaggage() ?? '', + name: $consumer->getTopic() . ' process', + op: 'queue.process', + description: $consumer::class, + origin: 'auto.kafka', + source: TransactionSource::custom() + ); + } + + private function handleKafkaMessageProcessed(KafkaEvent\AfterConsume|KafkaEvent\FailToConsume $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if (! $transaction || ! $transaction->getSampled()) { + return; + } + + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER); + $consumer = $event->getConsumer(); + $transaction->setData([ + 'messaging.system' => 'kafka', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => 0, + 'messaging.destination.name' => $carrier?->get('destination_name') ?: (is_array($consumer->getTopic()) ? implode(',', $consumer->getTopic()) : $consumer->getTopic()), + 'messaging.kafka.consumer.group' => $consumer->getGroupId(), + 'messaging.kafka.consumer.pool' => $consumer->getPool(), + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } + } + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(microtime(true)); + } + + private function handleAsyncQueueJobProcessing(AsyncQueueEvent\BeforeHandle $event): void + { + if (! $this->switcher->isTracingEnable('async_queue')) { + return; + } + + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); + $job = $event->getMessage()->job(); + + $this->continueTrace( + sentryTrace: $carrier?->getSentryTrace() ?? '', + baggage: $carrier?->getBaggage() ?? '', + name: $job::class, + op: 'queue.process', + description: 'async_queue: ' . $job::class, + origin: 'auto.async_queue', + source: TransactionSource::custom() + ); + } + + private function handleAsyncQueueJobProcessed(AsyncQueueEvent\AfterHandle|AsyncQueueEvent\RetryHandle|AsyncQueueEvent\FailedHandle $event): void + { + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + + if (! $transaction || ! $transaction->getSampled()) { + return; + } + + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); + $transaction->setData([ + 'messaging.system' => 'async_queue', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => $event->getMessage()->getAttempts(), + 'messaging.destination.name' => $carrier?->get('destination_name') ?: 'unknown queue', + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.code' => (string) $exception->getCode(), + ]); + if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } + } + + SentrySdk::getCurrentHub()->setSpan($transaction); + + $transaction->finish(microtime(true)); + } + + private function parseRoute(Dispatched $dispatched): array + { + $route = ''; + $params = []; + $callback = ''; + + if ($dispatched instanceof Dispatched && $dispatched->isFound()) { + $route = $dispatched->handler->route; + $params = $dispatched->params; + $callback = match (true) { + $dispatched->handler->callback instanceof Closure => 'closure', + is_array($dispatched->handler->callback) => implode('@', $dispatched->handler->callback), + is_string($dispatched->handler->callback) => $dispatched->handler->callback, + default => $callback, + }; + } + + return [$route, $params, $callback]; + } +} diff --git a/src/sentry/src/Tracing/Listener/TracingAmqpListener.php b/src/sentry/src/Tracing/Listener/TracingAmqpListener.php deleted file mode 100644 index 66801018e..000000000 --- a/src/sentry/src/Tracing/Listener/TracingAmqpListener.php +++ /dev/null @@ -1,142 +0,0 @@ -switcher->isTracingEnable('amqp')) { - return; - } - - match ($event::class) { - BeforeConsume::class => $this->startTransaction($event), - AfterConsume::class, FailToConsume::class => $this->finishTransaction($event), - default => null - }; - } - - protected function startTransaction(BeforeConsume $event): void - { - $message = $event->getMessage(); - $carrier = null; - - if (method_exists($event, 'getAMQPMessage')) { - /** @var AMQPMessage $amqpMessage */ - $amqpMessage = $event->getAMQPMessage(); - /** @var null|AMQPTable $applicationHeaders */ - $applicationHeaders = $amqpMessage->has('application_headers') ? $amqpMessage->get('application_headers') : null; - if ($applicationHeaders && isset($applicationHeaders[Constants::TRACE_CARRIER])) { - $carrier = Carrier::fromJson($applicationHeaders[Constants::TRACE_CARRIER]); - Context::set(Constants::TRACE_CARRIER, $carrier); - } - } - - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $message::class, - op: 'queue.process', - description: $message::class, - origin: 'auto.amqp', - source: TransactionSource::custom() - )->setStartTimestamp(microtime(true)); - } - - protected function finishTransaction(AfterConsume|FailToConsume $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - // If this transaction is not sampled, we can stop here to prevent doing work for nothing - if (! $transaction || ! $transaction->getSampled()) { - return; - } - - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER); - - /** @var ConsumerMessage $message */ - $message = $event->getMessage(); - $data = [ - 'messaging.system' => 'amqp', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => 0, - 'messaging.destination.name' => $carrier?->get('destination_name') ?: implode(', ', (array) $message->getRoutingKey()), - // for amqp - 'messaging.amqp.message.type' => $message->getTypeString(), - 'messaging.amqp.message.routing_key' => $message->getRoutingKey(), - 'messaging.amqp.message.exchange' => $message->getExchange(), - 'messaging.amqp.message.queue' => $message->getQueue(), - 'messaging.amqp.message.pool_name' => $message->getPoolName(), - 'messaging.amqp.message.result' => $event instanceof AfterConsume ? $event->getResult()->value : 'fail', - ]; - $tags = []; - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()); - $tags = array_merge($tags, [ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $transaction->setData($data)->setTags($tags); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(microtime(true)); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingAsyncQueueListener.php b/src/sentry/src/Tracing/Listener/TracingAsyncQueueListener.php deleted file mode 100644 index d86fb24be..000000000 --- a/src/sentry/src/Tracing/Listener/TracingAsyncQueueListener.php +++ /dev/null @@ -1,123 +0,0 @@ -switcher->isTracingEnable('async_queue')) { - return; - } - - match ($event::class) { - BeforeHandle::class => $this->startTransaction($event), - RetryHandle::class, FailedHandle::class, AfterHandle::class => $this->finishTransaction($event), - default => null, - }; - } - - protected function startTransaction(BeforeHandle $event): void - { - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); - - $job = $event->getMessage()->job(); - - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $job::class, - op: 'queue.process', - description: 'async_queue: ' . $job::class, - origin: 'auto.async_queue', - source: TransactionSource::custom() - )->setStartTimestamp(microtime(true)); - } - - protected function finishTransaction(AfterHandle|RetryHandle|FailedHandle $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - // If this transaction is not sampled, we can stop here to prevent doing work for nothing - if (! $transaction || ! $transaction->getSampled()) { - return; - } - - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); - $data = [ - 'messaging.system' => 'async_queue', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => $event->getMessage()->getAttempts(), - 'messaging.destination.name' => $carrier?->get('destination_name') ?: 'unknown queue', - ]; - $tags = []; - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()); - $tags = array_merge($tags, [ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $transaction->setData($data)->setTags($tags); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(microtime(true)); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingCommandListener.php b/src/sentry/src/Tracing/Listener/TracingCommandListener.php deleted file mode 100644 index 3d6b5f516..000000000 --- a/src/sentry/src/Tracing/Listener/TracingCommandListener.php +++ /dev/null @@ -1,126 +0,0 @@ -ignoreCommands = (array) $config->get('sentry.ignore_commands', []); - } - - public function listen(): array - { - return [ - BeforeHandle::class, - AfterExecute::class, - ]; - } - - /** - * @param BeforeHandle|AfterExecute|object $event - */ - public function process(object $event): void - { - if ( - ! $this->switcher->isTracingEnable('command') - || Str::is($this->ignoreCommands, $event->getCommand()->getName()) - ) { - return; - } - - match ($event::class) { - BeforeHandle::class => $this->startTransaction($event), - AfterExecute::class => $this->finishTransaction($event), - default => null, - }; - } - - protected function startTransaction(BeforeHandle $event): void - { - $command = $event->getCommand(); - - $this->continueTrace( - name: $command->getName() ?: '', - op: 'console.command', - description: $command->getDescription(), - origin: 'auto.command', - source: TransactionSource::custom() - ); - } - - protected function finishTransaction(AfterExecute $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - // If this transaction is not sampled, we can stop here to prevent doing work for nothing - if (! $transaction || ! $transaction->getSampled()) { - return; - } - $command = $event->getCommand(); - - $exitCode = (fn () => $this->exitCode ?? SymfonyCommand::SUCCESS)->call($command); - $data = [ - 'command.arguments' => (fn () => $this->input->getArguments())->call($command), - 'command.options' => (fn () => $this->input->getOptions())->call($command), - ]; - $tags = [ - 'command.exit_code' => $exitCode, - ]; - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()); - $tags = array_merge($tags, [ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $transaction->setStatus($exitCode == SymfonyCommand::SUCCESS ? SpanStatus::ok() : SpanStatus::internalError()) - ->setData($data) - ->setTags($tags); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(microtime(true)); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingCrontabListener.php b/src/sentry/src/Tracing/Listener/TracingCrontabListener.php deleted file mode 100644 index 7d305ea2d..000000000 --- a/src/sentry/src/Tracing/Listener/TracingCrontabListener.php +++ /dev/null @@ -1,107 +0,0 @@ -switcher->isTracingEnable('crontab')) { - return; - } - - match ($event::class) { - BeforeExecute::class => $this->startTransaction($event), - AfterExecute::class, FailToExecute::class => $this->finishTransaction($event), - default => null, - }; - } - - protected function startTransaction(BeforeExecute $event): void - { - $crontab = $event->crontab; - - $this->continueTrace( - name: $crontab->getName() ?: '', - op: 'crontab.run', - description: $crontab->getMemo(), - origin: 'auto.crontab', - source: TransactionSource::task() - ); - } - - protected function finishTransaction(AfterExecute|FailToExecute $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - // If this transaction is not sampled, we can stop here to prevent doing work for nothing - if (! $transaction || ! $transaction->getSampled()) { - return; - } - - $crontab = $event->crontab; - $data = []; - $tags = [ - 'crontab.rule' => $crontab->getRule(), - 'crontab.type' => $crontab->getType(), - 'crontab.options.is_single' => $crontab->isSingleton(), - 'crontab.options.is_on_one_server' => $crontab->isOnOneServer(), - ]; - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()); - $tags = array_merge($tags, [ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $transaction->setData($data)->setTags($tags); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(microtime(true)); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingDbQueryListener.php b/src/sentry/src/Tracing/Listener/TracingDbQueryListener.php deleted file mode 100644 index 453e84efb..000000000 --- a/src/sentry/src/Tracing/Listener/TracingDbQueryListener.php +++ /dev/null @@ -1,116 +0,0 @@ - $this->queryExecutedHandler($event), - default => null - }; - } - - /** - * @param object|QueryExecuted $event - */ - protected function queryExecutedHandler(object $event): void - { - if (! $this->switcher->isTracingSpanEnable('sql_queries')) { - return; - } - if (! SentrySdk::getCurrentHub()->getSpan()) { - return; - } - - $data = [ - 'coroutine.id' => Coroutine::id(), - 'db.system' => $event->connection->getDriverName(), - 'db.name' => $event->connection->getDatabaseName(), - ]; - - $sqlParse = SqlParser::parse($event->sql); - if (! empty($sqlParse['operation'])) { - $data['db.operation.name'] = $sqlParse['operation']; - } - if (! empty($sqlParse['table'])) { - $data['db.collection.name'] = $sqlParse['table']; - } - foreach ($event->bindings as $key => $value) { - $data['db.parameter.' . $key] = $value; - } - - $pool = $this->container->get(PoolFactory::class)->getPool($event->connectionName); - $data += [ - 'db.pool.name' => $event->connectionName, - 'db.pool.max' => $pool->getOption()->getMaxConnections(), - 'db.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), - 'db.pool.idle' => $pool->getConnectionsInChannel(), - 'db.pool.using' => $pool->getCurrentConnections(), - // 'server.host' => $event->connection->getConfig('host') ?? '', - // 'server.port' => $event->connection->getConfig('port') ?? '', - 'db.sql.bindings' => $event->bindings, - ]; - - $startTimestamp = microtime(true) - $event->time / 1000; - - // rule: operate db.table - // $op = sprintf( - // '%s%s', - // $sqlParse['operation'] ? $sqlParse['operation'] . ' ' : '', - // implode('.', array_filter([$event->connection->getDatabaseName(), $sqlParse['table']])) - // ); - $op = 'db.sql.query'; - $description = $event->sql; - - // Already check in the previous context - $this->startSpan( - op: $op, - description: $description, - origin: 'auto.db' - )?->setData($data) - ->setStartTimestamp($startTimestamp) - ->finish($startTimestamp + $event->time / 1000); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingKafkaListener.php b/src/sentry/src/Tracing/Listener/TracingKafkaListener.php deleted file mode 100644 index d9f09baa5..000000000 --- a/src/sentry/src/Tracing/Listener/TracingKafkaListener.php +++ /dev/null @@ -1,134 +0,0 @@ -switcher->isTracingEnable('kafka')) { - return; - } - - match ($event::class) { - BeforeConsume::class => $this->startTransaction($event), - AfterConsume::class, FailToConsume::class => $this->finishTransaction($event), - default => null, - }; - } - - protected function startTransaction(BeforeConsume $event): void - { - $consumer = $event->getConsumer(); - $message = $event->getData(); - $carrier = null; - - if ($message instanceof ConsumeMessage) { - foreach ($message->getHeaders() as $header) { - if ($header->getHeaderKey() === Constants::TRACE_CARRIER) { - $carrier = Carrier::fromJson($header->getValue()); - Context::set(Constants::TRACE_CARRIER, $carrier); - break; - } - } - } - - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $consumer->getTopic() . ' process', - op: 'queue.process', - description: $consumer::class, - origin: 'auto.kafka', - source: TransactionSource::custom() - )->setStartTimestamp(microtime(true)); - } - - protected function finishTransaction(AfterConsume|FailToConsume $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - // If this transaction is not sampled, we can stop here to prevent doing work for nothing - if (! $transaction || ! $transaction->getSampled()) { - return; - } - - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER); - $consumer = $event->getConsumer(); - $tags = []; - $data = [ - 'messaging.system' => 'kafka', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => 0, - 'messaging.destination.name' => $carrier?->get('destination_name') ?: (is_array($consumer->getTopic()) ? implode(',', $consumer->getTopic()) : $consumer->getTopic()), - // for kafka - 'messaging.kafka.consumer.group' => $consumer->getGroupId(), - 'messaging.kafka.consumer.pool' => $consumer->getPool(), - ]; - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()); - $tags = array_merge($tags, [ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $transaction->setData($data)->setTags($tags); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(microtime(true)); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingRedisListener.php b/src/sentry/src/Tracing/Listener/TracingRedisListener.php deleted file mode 100644 index bbd09190a..000000000 --- a/src/sentry/src/Tracing/Listener/TracingRedisListener.php +++ /dev/null @@ -1,103 +0,0 @@ -switcher->isTracingSpanEnable('redis')) { - return; - } - - $pool = $this->container->get(PoolFactory::class)->getPool($event->connectionName); - $config = $this->config->get('redis.' . $event->connectionName, []); - $redisStatement = (string) new RedisCommand($event->command, $event->parameters); - - $data = [ - 'coroutine.id' => Coroutine::id(), - 'db.system' => 'redis', - 'db.statement' => $redisStatement, - 'db.redis.connection' => $event->connectionName, - 'db.redis.database_index' => $config['db'] ?? 0, - 'db.redis.parameters' => $event->parameters, - 'db.redis.pool.name' => $event->connectionName, - 'db.redis.pool.max' => $pool->getOption()->getMaxConnections(), - 'db.redis.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), - 'db.redis.pool.idle' => $pool->getConnectionsInChannel(), - 'db.redis.pool.using' => $pool->getCurrentConnections(), - 'duration' => $event->time * 1000, - ]; - - // rule: operation db.table - $op = 'db.redis'; - $span = $this->startSpan( - op: $op, - description: $redisStatement, - origin: 'auto.cache.redis', - ); - - if (! $span) { - return; - } - - if ($this->switcher->isTracingExtraTagEnable('redis.result')) { - $data['db.redis.result'] = $event->result; - } - - if ($exception = $event->throwable) { - $span->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnable('exception.stack_trace')) { - $data['exception.stack_trace'] = (string) $exception; - } - } - - $span->setData($data) - ->finish(); - } -} diff --git a/src/sentry/src/Tracing/Listener/TracingRequestListener.php b/src/sentry/src/Tracing/Listener/TracingRequestListener.php deleted file mode 100644 index ff5f99d7d..000000000 --- a/src/sentry/src/Tracing/Listener/TracingRequestListener.php +++ /dev/null @@ -1,202 +0,0 @@ -switcher->isTracingEnable('request')) { - return; - } - - match ($event::class) { - RequestReceived::class, RpcRequestReceived::class => $this->startTransaction($event), - RequestHandled::class, RpcRequestHandled::class => $this->setTraceIdAndException($event), - default => null, - }; - } - - private function startTransaction(RequestReceived|RpcRequestReceived $event): void - { - $request = $event->request; - /** @var Dispatched $dispatched */ - $dispatched = $request->getAttribute(Dispatched::class); - - if (! $dispatched->isFound() && ! $this->switcher->isTracingEnable('missing_routes')) { - return; - } - - $serverName = $dispatched->serverName ?? 'http'; - $path = $request->getUri()->getPath(); - $method = strtoupper($request->getMethod()); - - /** - * @var string $route - * @var array $routeParams - * @var string $routeCallback - */ - [$route, $routeParams, $routeCallback] = $this->parseRoute($dispatched); - - /** - * @var string $name - * @var TransactionSource $source - */ - [$name, $source] = match (strtolower($this->source)) { - 'custom' => [$routeCallback, TransactionSource::custom()], - 'url' => [$path, TransactionSource::url()], - default => [$route, TransactionSource::route()], - }; - - // Get sentry-trace and baggage - $transaction = $this->startRequestTransaction( - request: $request, - name: $name, - op: sprintf('%s.server', $serverName), - description: sprintf('%s %s', $method, $path), - origin: 'auto.request', - source: $source, - ); - - if (! $transaction->getSampled()) { - return; - } - - // Set data - $data = [ - 'url.scheme' => $request->getUri()->getScheme(), - 'url.path' => $path, - 'http.request.method' => $method, - 'http.route' => $route, - 'http.route.params' => $routeParams, - ]; - foreach ($request->getHeaders() as $key => $value) { - $data['http.request.header.' . $key] = implode(', ', $value); - } - if ($this->container->has(RpcContext::class)) { - $data['rpc.context'] = $this->container->get(RpcContext::class)->getData(); - } - - $transaction->setData($data); - - $span = $this->startSpan( - op: 'request.received', - description: 'request.received', - origin: 'auto.request.received', - asParent: true - ); - - defer(function () use ($transaction, $span) { - $span?->finish(); - - SentrySdk::getCurrentHub()->setSpan($transaction); - - $transaction->finish(); - }); - } - - private function setTraceIdAndException(RequestHandled|RpcRequestHandled $event): void - { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); - - if ( - ! $transaction - || ! $transaction->getSampled() - || ! $traceId = (string) $transaction->getTraceId() - ) { - return; - } - - if ($event instanceof RpcRequestHandled) { - $this->container->has(RpcContext::class) && $this->container->get(RpcContext::class)->set('sentry-trace-id', $traceId); - } elseif ($event->response instanceof ResponsePlusInterface) { - $event->response->setHeader('sentry-trace-id', $traceId); - } - - // Set http status code - $transaction->setHttpStatus($event->response->getStatusCode()); - - if ($exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => true, - 'exception.class' => $exception::class, - 'exception.code' => $exception->getCode(), - 'exception.message' => $exception->getMessage(), - ]) - ->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - } - - private function parseRoute(Dispatched $dispatched): array - { - $route = ''; - $params = []; - $callback = ''; - - if ($dispatched instanceof Dispatched && $dispatched->isFound()) { - $route = $dispatched->handler->route; - $params = $dispatched->params; - $callback = match (true) { - $dispatched->handler->callback instanceof Closure => 'closure', - is_array($dispatched->handler->callback) => implode('@', $dispatched->handler->callback), - is_string($dispatched->handler->callback) => $dispatched->handler->callback, - default => $callback, - }; - } - - return [$route, $params, $callback]; - } -}