vendor/sentry/sentry/src/Client.php line 247

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sentry;
  4. use GuzzleHttp\Promise\PromiseInterface;
  5. use Psr\Log\LoggerInterface;
  6. use Psr\Log\NullLogger;
  7. use Sentry\Integration\IntegrationInterface;
  8. use Sentry\Integration\IntegrationRegistry;
  9. use Sentry\Serializer\RepresentationSerializer;
  10. use Sentry\Serializer\RepresentationSerializerInterface;
  11. use Sentry\Serializer\SerializerInterface;
  12. use Sentry\State\Scope;
  13. use Sentry\Transport\TransportInterface;
  14. /**
  15.  * Default implementation of the {@see ClientInterface} interface.
  16.  *
  17.  * @author Stefano Arlandini <sarlandini@alice.it>
  18.  */
  19. final class Client implements ClientInterface
  20. {
  21.     /**
  22.      * The version of the protocol to communicate with the Sentry server.
  23.      */
  24.     public const PROTOCOL_VERSION '7';
  25.     /**
  26.      * The identifier of the SDK.
  27.      */
  28.     public const SDK_IDENTIFIER 'sentry.php';
  29.     /**
  30.      * The version of the SDK.
  31.      */
  32.     public const SDK_VERSION '3.12.0';
  33.     /**
  34.      * @var Options The client options
  35.      */
  36.     private $options;
  37.     /**
  38.      * @var TransportInterface The transport
  39.      */
  40.     private $transport;
  41.     /**
  42.      * @var LoggerInterface The PSR-3 logger
  43.      */
  44.     private $logger;
  45.     /**
  46.      * @var array<string, IntegrationInterface> The stack of integrations
  47.      *
  48.      * @psalm-var array<class-string<IntegrationInterface>, IntegrationInterface>
  49.      */
  50.     private $integrations;
  51.     /**
  52.      * @var RepresentationSerializerInterface The representation serializer of the client
  53.      */
  54.     private $representationSerializer;
  55.     /**
  56.      * @var StacktraceBuilder
  57.      */
  58.     private $stacktraceBuilder;
  59.     /**
  60.      * @var string The Sentry SDK identifier
  61.      */
  62.     private $sdkIdentifier;
  63.     /**
  64.      * @var string The SDK version of the Client
  65.      */
  66.     private $sdkVersion;
  67.     /**
  68.      * Constructor.
  69.      *
  70.      * @param Options                                $options                  The client configuration
  71.      * @param TransportInterface                     $transport                The transport
  72.      * @param string|null                            $sdkIdentifier            The Sentry SDK identifier
  73.      * @param string|null                            $sdkVersion               The Sentry SDK version
  74.      * @param SerializerInterface|null               $serializer               The serializer
  75.      * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments
  76.      * @param LoggerInterface|null                   $logger                   The PSR-3 logger
  77.      */
  78.     public function __construct(
  79.         Options $options,
  80.         TransportInterface $transport,
  81.         ?string $sdkIdentifier null,
  82.         ?string $sdkVersion null,
  83.         ?SerializerInterface $serializer null,
  84.         ?RepresentationSerializerInterface $representationSerializer null,
  85.         ?LoggerInterface $logger null
  86.     ) {
  87.         $this->options $options;
  88.         $this->transport $transport;
  89.         $this->logger $logger ?? new NullLogger();
  90.         $this->integrations IntegrationRegistry::getInstance()->setupIntegrations($options$this->logger);
  91.         $this->representationSerializer $representationSerializer ?? new RepresentationSerializer($this->options);
  92.         $this->stacktraceBuilder = new StacktraceBuilder($options$this->representationSerializer);
  93.         $this->sdkIdentifier $sdkIdentifier ?? self::SDK_IDENTIFIER;
  94.         $this->sdkVersion $sdkVersion ?? self::SDK_VERSION;
  95.     }
  96.     /**
  97.      * {@inheritdoc}
  98.      */
  99.     public function getOptions(): Options
  100.     {
  101.         return $this->options;
  102.     }
  103.     /**
  104.      * {@inheritdoc}
  105.      */
  106.     public function getCspReportUrl(): ?string
  107.     {
  108.         $dsn $this->options->getDsn();
  109.         if (null === $dsn) {
  110.             return null;
  111.         }
  112.         $endpoint $dsn->getCspReportEndpointUrl();
  113.         $query array_filter([
  114.             'sentry_release' => $this->options->getRelease(),
  115.             'sentry_environment' => $this->options->getEnvironment(),
  116.         ]);
  117.         if (!empty($query)) {
  118.             $endpoint .= '&' http_build_query($query'''&');
  119.         }
  120.         return $endpoint;
  121.     }
  122.     /**
  123.      * {@inheritdoc}
  124.      */
  125.     public function captureMessage(string $message, ?Severity $level null, ?Scope $scope null, ?EventHint $hint null): ?EventId
  126.     {
  127.         $event Event::createEvent();
  128.         $event->setMessage($message);
  129.         $event->setLevel($level);
  130.         return $this->captureEvent($event$hint$scope);
  131.     }
  132.     /**
  133.      * {@inheritdoc}
  134.      */
  135.     public function captureException(\Throwable $exception, ?Scope $scope null, ?EventHint $hint null): ?EventId
  136.     {
  137.         $hint $hint ?? new EventHint();
  138.         if (null === $hint->exception) {
  139.             $hint->exception $exception;
  140.         }
  141.         return $this->captureEvent(Event::createEvent(), $hint$scope);
  142.     }
  143.     /**
  144.      * {@inheritdoc}
  145.      */
  146.     public function captureEvent(Event $event, ?EventHint $hint null, ?Scope $scope null): ?EventId
  147.     {
  148.         $event $this->prepareEvent($event$hint$scope);
  149.         if (null === $event) {
  150.             return null;
  151.         }
  152.         try {
  153.             /** @var Response $response */
  154.             $response $this->transport->send($event)->wait();
  155.             $event $response->getEvent();
  156.             if (null !== $event) {
  157.                 return $event->getId();
  158.             }
  159.         } catch (\Throwable $exception) {
  160.         }
  161.         return null;
  162.     }
  163.     /**
  164.      * {@inheritdoc}
  165.      */
  166.     public function captureLastError(?Scope $scope null, ?EventHint $hint null): ?EventId
  167.     {
  168.         $error error_get_last();
  169.         if (null === $error || !isset($error['message'][0])) {
  170.             return null;
  171.         }
  172.         $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']);
  173.         return $this->captureException($exception$scope$hint);
  174.     }
  175.     /**
  176.      * {@inheritdoc}
  177.      *
  178.      * @psalm-template T of IntegrationInterface
  179.      */
  180.     public function getIntegration(string $className): ?IntegrationInterface
  181.     {
  182.         /** @psalm-var T|null */
  183.         return $this->integrations[$className] ?? null;
  184.     }
  185.     /**
  186.      * {@inheritdoc}
  187.      */
  188.     public function flush(?int $timeout null): PromiseInterface
  189.     {
  190.         return $this->transport->close($timeout);
  191.     }
  192.     /**
  193.      * {@inheritdoc}
  194.      */
  195.     public function getStacktraceBuilder(): StacktraceBuilder
  196.     {
  197.         return $this->stacktraceBuilder;
  198.     }
  199.     /**
  200.      * Assembles an event and prepares it to be sent of to Sentry.
  201.      *
  202.      * @param Event          $event The payload that will be converted to an Event
  203.      * @param EventHint|null $hint  May contain additional information about the event
  204.      * @param Scope|null     $scope Optional scope which enriches the Event
  205.      *
  206.      * @return Event|null The prepared event object or null if it must be discarded
  207.      */
  208.     private function prepareEvent(Event $event, ?EventHint $hint null, ?Scope $scope null): ?Event
  209.     {
  210.         if (null !== $hint) {
  211.             if (null !== $hint->exception && empty($event->getExceptions())) {
  212.                 $this->addThrowableToEvent($event$hint->exception$hint);
  213.             }
  214.             if (null !== $hint->stacktrace && null === $event->getStacktrace()) {
  215.                 $event->setStacktrace($hint->stacktrace);
  216.             }
  217.         }
  218.         $this->addMissingStacktraceToEvent($event);
  219.         $event->setSdkIdentifier($this->sdkIdentifier);
  220.         $event->setSdkVersion($this->sdkVersion);
  221.         $event->setTags(array_merge($this->options->getTags(false), $event->getTags()));
  222.         if (null === $event->getServerName()) {
  223.             $event->setServerName($this->options->getServerName());
  224.         }
  225.         if (null === $event->getRelease()) {
  226.             $event->setRelease($this->options->getRelease());
  227.         }
  228.         if (null === $event->getEnvironment()) {
  229.             $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT);
  230.         }
  231.         if (null === $event->getLogger()) {
  232.             $event->setLogger($this->options->getLogger(false));
  233.         }
  234.         $isTransaction EventType::transaction() === $event->getType();
  235.         $sampleRate $this->options->getSampleRate();
  236.         if (!$isTransaction && $sampleRate && mt_rand(1100) / 100.0 $sampleRate) {
  237.             $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]);
  238.             return null;
  239.         }
  240.         if (null !== $scope) {
  241.             $beforeEventProcessors $event;
  242.             $event $scope->applyToEvent($event$hint);
  243.             if (null === $event) {
  244.                 $this->logger->info(
  245.                     'The event will be discarded because one of the event processors returned "null".',
  246.                     ['event' => $beforeEventProcessors]
  247.                 );
  248.                 return null;
  249.             }
  250.         }
  251.         $beforeSendCallback $event;
  252.         $event $this->applyBeforeSendCallback($event$hint);
  253.         if (null === $event) {
  254.             $this->logger->info(
  255.                 sprintf(
  256.                     'The event will be discarded because the "%s" callback returned "null".',
  257.                     $this->getBeforeSendCallbackName($beforeSendCallback)
  258.                 ),
  259.                 ['event' => $beforeSendCallback]
  260.             );
  261.         }
  262.         return $event;
  263.     }
  264.     private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event
  265.     {
  266.         if ($event->getType() === EventType::event()) {
  267.             return ($this->options->getBeforeSendCallback())($event$hint);
  268.         }
  269.         if ($event->getType() === EventType::transaction()) {
  270.             return ($this->options->getBeforeSendTransactionCallback())($event$hint);
  271.         }
  272.         return $event;
  273.     }
  274.     private function getBeforeSendCallbackName(Event $event): string
  275.     {
  276.         $beforeSendCallbackName 'before_send';
  277.         if ($event->getType() === EventType::transaction()) {
  278.             $beforeSendCallbackName 'before_send_transaction';
  279.         }
  280.         return $beforeSendCallbackName;
  281.     }
  282.     /**
  283.      * Optionally adds a missing stacktrace to the Event if the client is configured to do so.
  284.      *
  285.      * @param Event $event The Event to add the missing stacktrace to
  286.      */
  287.     private function addMissingStacktraceToEvent(Event $event): void
  288.     {
  289.         if (!$this->options->shouldAttachStacktrace()) {
  290.             return;
  291.         }
  292.         // We should not add a stacktrace when the event already has one or contains exceptions
  293.         if (null !== $event->getStacktrace() || !empty($event->getExceptions())) {
  294.             return;
  295.         }
  296.         $event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace(
  297.             debug_backtrace(0),
  298.             __FILE__,
  299.             __LINE__ 3
  300.         ));
  301.     }
  302.     /**
  303.      * Stores the given exception in the passed event.
  304.      *
  305.      * @param Event      $event     The event that will be enriched with the exception
  306.      * @param \Throwable $exception The exception that will be processed and added to the event
  307.      * @param EventHint  $hint      Contains additional information about the event
  308.      */
  309.     private function addThrowableToEvent(Event $event\Throwable $exceptionEventHint $hint): void
  310.     {
  311.         if ($exception instanceof \ErrorException && null === $event->getLevel()) {
  312.             $event->setLevel(Severity::fromError($exception->getSeverity()));
  313.         }
  314.         $exceptions = [];
  315.         do {
  316.             $exceptions[] = new ExceptionDataBag(
  317.                 $exception,
  318.                 $this->stacktraceBuilder->buildFromException($exception),
  319.                 $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERICtrue)
  320.             );
  321.         } while ($exception $exception->getPrevious());
  322.         $event->setExceptions($exceptions);
  323.     }
  324. }