diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..251ced3f6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Update PHPCS (#1599) +f8b64d330d03cbf809795beb5846b2c48175cd62 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index f61e77a0a..3c95813ec 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -18,13 +18,13 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer phpcs + run: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run phpstan: name: PHPStan @@ -42,7 +42,7 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer phpstan + run: vendor/bin/phpstan analyse psalm: name: Psalm @@ -62,4 +62,4 @@ jobs: run: composer update --no-progress --no-interaction --prefer-dist - name: Run script - run: composer psalm + run: vendor/bin/psalm diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8f4027a98..3975a591f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -10,9 +10,20 @@ 'imports_order' => ['class', 'function', 'const'], ], 'declare_strict_types' => true, - 'yoda_style' => true, + 'get_class_to_class_keyword' => false, + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], 'self_accessor' => false, - 'phpdoc_no_useless_inheritdoc' => false, + 'modernize_strpos' => false, + 'nullable_type_declaration_for_default_null_value' => [ + 'use_nullable_type_declaration' => true, + ], + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + ], 'phpdoc_to_comment' => false, 'phpdoc_align' => [ 'tags' => ['param', 'return', 'throws', 'type', 'var'], diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d402977..e8f353f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,659 +1,126 @@ # CHANGELOG -## 3.22.0 +## 4.0.0 -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.22.0. +The Sentry SDK team is thrilled to announce the immediate availability of Sentry PHP SDK v4.0.0. -### Features +# Breaking Change -- Adopt Starfish HTTP attributes in spans and breadcrumbs [(#1581)](https://github.com/getsentry/sentry-php/pull/1581) +Please refer to the [UPGRADE-4.0.md](UPGRADE-4.0.md) guide for a complete list of breaking changes. -### Bug Fixes +- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry. -- Don't add empty HTTP fragment or query strings to breadcrumb data [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) + If you are using [sentry.io](https://sentry.io), no action is needed. + If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`. -### Misc +- You need to have `ext-curl` installed to use the SDK. -- Remove obsolete `tags` option depreaction [(#1588)](https://github.com/getsentry/sentry-php/pull/1588) -- Run CI on PHP 8.3 [(1591)](https://github.com/getsentry/sentry-php/pull/1591) -- Add support for `symfony/options-resolver: ^7.0` [(1597)](https://github.com/getsentry/sentry-php/pull/1597) - -## 3.21.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.21.0. - -### Features - -- Add `Sentry::captureCheckIn()` [(#1573)](https://github.com/getsentry/sentry-php/pull/1573) - - Sending check-ins from the SDK is now simplified. - - ```php - $checkInId = Sentry\captureCheckIn( - slug: 'monitor-slug', - status: CheckInStatus::inProgress() - ); - - - // do something - - Sentry\captureCheckIn( - checkInId: $checkInId, - slug: 'monitor-slug', - status: CheckInStatus::ok() - ); - ``` - - You can also pass in a `monitorConfig` object as well as the `duration`. - -- Undeprecate the `tags` option [(#1561)](https://github.com/getsentry/sentry-php/pull/1561) - - You can now set tags that are applied to each event when calling `Sentry::init()`. +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. ```php Sentry\init([ - 'tags' => [ - 'foo' => 'bar', - ], - ]) + 'ignore_exceptions' => [BadThingsHappenedException::class], + ]); ``` + + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check now, so you can also ignore more generic exceptions. -- Apply the `prefixes`option to profiling frames [(#1568)](https://github.com/getsentry/sentry-php/pull/1568) +# Features - If you added the `prefixes` option when calling `Sentry::init()`, this option will now also apply to profile frames. +- Add new fluent APIs [(#1601)](https://github.com/getsentry/sentry-php/pull/1601) ```php - Sentry\init([ - 'prefixes' => ['/var/www/html'], - ]) + // Before + $transactionContext = new TransactionContext(); + $transactionContext->setName('GET /example'); + $transactionContext->setOp('http.server'); + + // After + $transactionContext = (new TransactionContext()) + ->setName('GET /example'); + ->setOp('http.server'); ``` -### Misc - -- Deduplicate profile stacks and frames [(#1570)](https://github.com/getsentry/sentry-php/pull/1570) - - This will decrease the payload size of the `profile` event payload. - -- Add the transaction's sampling decision to the trace envelope header [(#1562)](https://github.com/getsentry/sentry-php/pull/1562) - -## 3.20.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.1. - -### Bug Fixes - -- Use the result of `isTracingEnabled()` to determine the behaviour of `getBaggage()` and `getTraceparent()` [(#1555)](https://github.com/getsentry/sentry-php/pull/1555) - -### Misc - -- Always return a `TransactionContext` from `continueTrace()` [(#1556)](https://github.com/getsentry/sentry-php/pull/1556) - -## 3.20.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.20.0. - -### Features - -- Tracing without Performance [(#1516)](https://github.com/getsentry/sentry-php/pull/1516) - - You can now set up distributed tracing without the need to use the performance APIs. - This allows you to connect your errors that hail from other Sentry instrumented applications to errors in your PHP application. - - To continue a trace, fetch the incoming Sentry tracing headers and call `\Sentry\continueTrace()` as early as possible in the request cycle. +- Simplify the breadcrumb API [(#1603)](https://github.com/getsentry/sentry-php/pull/1603) ```php - $sentryTraceHeader = $request->getHeaderLine('sentry-trace'); - $baggageHeader = $request->getHeaderLine('baggage'); + // Before + \Sentry\addBreadcrumb( + new \Sentry\Breadcrumb( + \Sentry\Breadcrumb::LEVEL_INFO, + \Sentry\Breadcrumb::TYPE_DEFAULT, + 'auth', // category + 'User authenticated', // message (optional) + ['user_id' => $userId] // data (optional) + ) + ); - continueTrace($sentryTraceHeader, $baggageHeader); + // After + \Sentry\addBreadcrumb( + category: 'auth', + message: 'User authenticated', // optional + metadata: ['user_id' => $userId], // optional + level: Breadcrumb::LEVEL_INFO, // set by default + type: Breadcrumb::TYPE_DEFAULT, // set by default + ); ``` - To continue a trace outward, you may attach the Sentry tracing headers to any HTTP client request. - You can fetch the required header values by calling `\Sentry\getBaggage()` and `\Sentry\getTraceparent()`. - -- Upserting Cron Monitors [(#1511)](https://github.com/getsentry/sentry-php/pull/1511) - - You can now create and update your Cron Monitors programmatically with code. - Read more about this in our [docs](https://docs.sentry.io/platforms/php/crons/#upserting-cron-monitors). - -## 3.19.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.1. - -### Bug Fixes - -- Use HTTP/1.1 when compression is enabled [(#1542)](https://github.com/getsentry/sentry-php/pull/1542) - -## 3.19.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.19.0. - -### Misc - -- Add support for `guzzlehttp/promises` v2 [(#1536)](https://github.com/getsentry/sentry-php/pull/1536) - -## 3.18.2 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.2. - -### Bug Fixes - -- Require php-http/message-factory [(#1534)](https://github.com/getsentry/sentry-php/pull/1534) - -## 3.18.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.1. - -### Bug Fixes - -- Guard against empty profiles [(#1528)](https://github.com/getsentry/sentry-php/pull/1528) -- Ignore empty context values [(#1529)](https://github.com/getsentry/sentry-php/pull/1529) - -## 3.18.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.18.0. +- New `logger` option [(#1625)](https://github.com/getsentry/sentry-php/pull/1625) -### Features - -- Add `TransactionContext::fromEnvironment` [(#1519)](https://github.com/getsentry/sentry-php/pull/1519) - -### Misc - -- Sent all events to the `/envelope` endpoint if tracing is enabled [(#1518)](https://github.com/getsentry/sentry-php/pull/1518) -- Attach the Dynamic Sampling Context to error events [(#1522)](https://github.com/getsentry/sentry-php/pull/1522) - -## 3.17.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.17.0. - -### Features - -- Add `ignore_exceptions` & `ignore_transactions` options [(#1503)](https://github.com/getsentry/sentry-php/pull/1503) - - We deprecated the [IgnoreErrorsIntegration](https://docs.sentry.io/platforms/php/integrations/#ignoreerrorsintegration) in favor of this new option. - The option will also take [previous exceptions](https://www.php.net/manual/en/exception.getprevious.php) into account. + To make it easier to debug the internals of the SDK, the `logger` option now accepts a `Psr\Log\LoggerInterface` instance. + We do provide two implementations, `Sentry\Logger\DebugFileLogger` and `Sentry\Logger\DebugStdOutLogger`. ```php - \Sentry\init([ - 'ignore_exceptions' => [BadThingsHappenedException::class], + // This logs messages to the provided file path + Sentry\init([ + 'logger' => new DebugFileLogger(filePath: ROOT . DS . 'sentry.log'), ]); - ``` - - To ignore a transaction being sent to Sentry, add its name to the config option. - You can find the transaction name on the [Performance page](https://sentry.io/performance/). - ```php - \Sentry\init([ - 'ignore_transactions' => ['GET /health'], + // This logs messages to stdout + Sentry\init([ + 'logger' => new DebugStdOutLogger(), ]); ``` -### Misc - - - Bump `php-http/discovery` to `^1.15` [(#1504)](https://github.com/getsentry/sentry-php/pull/1504) +- New default cURL HTTP client [(#1589)](https://github.com/getsentry/sentry-php/pull/1589) - You may need to allow the added composer plugin, introduced in `php-http/discovery v1.15.0`, to execute when running `composer update`. - We previously pinned this package to version `<1.15`. - Due to conflicts with other packages, we decided to lift this restriction. - -## 3.16.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.16.0. -This release adds initial support for [Cron Monitoring](https://docs.sentry.io/product/crons/). - -> **Warning** -> Cron Monitoring is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. -> If you have any questions or feedback, please email us at crons-feedback@sentry.io, reach out via Discord (#cronjobs), or open an issue. - -### Features - -- Add inital support for Cron Monitoring [(#1467)](https://github.com/getsentry/sentry-php/pull/1467) - - You can use Cron Monitoring to monitor your cron jobs. No pun intended. - - Add the code below to your application or script that is invoked by your cron job. - The first Check-In will let Sentry know that your job started, with the second Check-In reporting the outcome. + The SDK now ships with its own HTTP client based on cURL. A few new options were added. ```php - ', - status: CheckInStatus::inProgress(), - ); - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); - - try { - - // do stuff - - $checkIn->setStatus(CheckInStatus::ok()); - } catch (Throwable $e) { - $checkIn->setStatus(CheckInStatus::error()); - } - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); + Sentry\init([ + 'http_proxy_authentication' => 'username:password', // user name and password to use for proxy authentication + 'http_ssl_verify_peer' => false, // default true, verify the peer's SSL certificate + 'http_compression' => false, // default true, http request body compression + ]); ``` - If you only want to check if a cron did run, you may create a "Heartbeat" instead. - Add the code below to your application or script that is invoked by your cron job. - + To use a different client, you may use the `http_client` option. ```php - ', - status: CheckInStatus::ok(), // or - CheckInStatus::error() - duration: 10, // optional - duration in seconds - ); - - $event = Event::createCheckIn(); - $event->setCheckIn($checkIn); - - $this->hub->captureEvent($event); - ``` - -- Introduce a new `trace` helper function [(#1490)](https://github.com/getsentry/sentry-php/pull/1490) - - We made it a tad easier to add custom tracing spans to your application. - - ```php - $spanContext = new SpanContext(); - $spanContext->setOp('function'); - $spanContext->setDescription('Soemthing to be traced'); - - trace( - function (Scope $scope) { - // something to be traced - }, - $spanContext, - ); - ``` - -## 3.15.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.15.0. -This release adds initial support for [Profiling](https://docs.sentry.io/product/profiling/). - -> **Warning** -> Profiling is currently in beta. Beta features are still in-progress and may have bugs. We recognize the irony. -> If you have any questions or feedback, please email us at profiling@sentry.io, reach out via Discord (#profiling), or open an issue. - -Profiling is only available on Sentry SaaS (sentry.io). Support for Sentry self-hosted is planned once Profiling is released into GA. - -### Features - -- Add initial support for profiling [(#1477)](https://github.com/getsentry/sentry-php/pull/1477) - - Under the hood, we're using Wikipedia's sampling profiler [Excimer](https://github.com/wikimedia/mediawiki-php-excimer). - We chose this profiler for its low overhead and for being used in production by one of the largest PHP-powered websites in the world. - - Excimer works with PHP 7.2 and up, for PHP 8.2 support, make sure to use Excimer version 1.1.0. - - There is currently no support for either Windows or macOS. + use Sentry\Client; + use Sentry\HttpClient\HttpClientInterface; + use Sentry\HttpClient\Request; + use Sentry\HttpClient\Response; + use Sentry\Options; - You can install Excimer via your operating systems package manager. + $httpClient = new class() implements HttpClientInterface { + public function sendRequest(Request $request, Options $options): Response + { - ```bash - apt-get install php-excimer - ``` - - If no suitable version is available, you may build Excimer from source. - - ```bash - git clone https://github.com/wikimedia/mediawiki-php-excimer.git - - cd excimer/ - phpize && ./configure && make && sudo make install - ``` - - Depending on your environment, you may need to enable the Excimer extension afterward. - - ```bash - phpenmod -s fpm excimer - # or - phpenmod -s apache2 excimer - ``` - - Once the extension is installed, you may enable profiling by adding the new `profiles_sample_rate` config option to your `Sentry::init` method. - - ```php - \Sentry\init([ - 'dsn' => '__DSN__', - 'traces_sample_rate' => 1.0, - 'profiles_sample_rate' => 1.0, + // your custom implementation + + return new Response($response->getStatusCode(), $response->getHeaders(), ''); + } + }; + + Sentry\init([ + 'http_client' => $httpClient, ]); ``` - Profiles are being sampled in relation to your `traces_sample_rate`. - - Please note that the profiler is started inside transactions only. If you're not using our [Laravel](https://github.com/getsentry/sentry-laravel) or [Symfony](https://github.com/getsentry/sentry-symfony) SDKs, you may need to manually add transactions to your application as described [here](https://docs.sentry.io/platforms/php/performance/instrumentation/custom-instrumentation/). - - #### Other things you should consider: - - - The current sample rate of the profiler is set to 101Hz (every ~10ms). A minimum of two samples is required for a profile being sent, hence requests that finish in less than ~20ms won't hail any profiles. - - The maximum duration of a profile is 30s, hence we do not recommend enabling the extension in an CLI environment. - - By design, the profiler will take samples at the end of any userland functions. You may see long sample durations on tasks like HTTP client requests and DB queries. - You can read more about Excimer's architecture [here](https://techblog.wikimedia.org/2021/03/03/profiling-php-in-production-at-scale/). - -## 3.14.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.14.0. - -### Features - -- Add a new `enable_tracing: true/false` option, an alternative for `traces_sample_rate: 1.0/null` [(#1458)](https://github.com/getsentry/sentry-php/pull/1458) - -### Bug Fixes - -- Fix missing keys in the request body [(#1470)](https://github.com/getsentry/sentry-php/pull/1470) -- Add support for partial JSON encoding [(#1481)](https://github.com/getsentry/sentry-php/pull/1481) -- Prevent calling *magic methods* when retrieving the ID from an object [(#1483)](https://github.com/getsentry/sentry-php/pull/1483) -- Only serialize scalar object IDs [(#1485)](https://github.com/getsentry/sentry-php/pull/1485) - -### Misc - -- The SDK is now licensed under MIT [(#1471)](https://github.com/getsentry/sentry-php/pull/1471) - - Read more about Sentry's licensing [here](https://open.sentry.io/licensing/). -- Deprecate `Client::__construct` `$serializer` argument. It is currently un-used [(#1482)](https://github.com/getsentry/sentry-php/pull/1482) - -## 3.13.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.1. - -### Bug Fixes - -- Sanatize HTTP client spans & breadcrumbs [(#1453)](https://github.com/getsentry/sentry-php/pull/1453) -- Pin php-http/discovery to `< 1.15` to disable some unwanted side-effect introduced in this new minor version [(#1464)](https://github.com/getsentry/sentry-php/pull/1464) - -## 3.13.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry PHP SDK v3.13.0. - -### Features - -- Object IDs are now automatically serialized as part of a stack trace frame [(#1443)](https://github.com/getsentry/sentry-php/pull/1443) - - If `Obj::getID()` or `Obj->id` is accessible, this value will be displayed inside the stack trace frame on the issue details page. - To attach local variables to your stack trace, make sure `zend.exception_ignore_arg: 0` is set in your `php.ini`. - See https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces - -- Add more functionality to the `ExceptionMechanism::class` [(#1450)](https://github.com/getsentry/sentry-php/pull/1450) - - Attach arbitrary data - ```php - $hint = EventHint::fromArray([ - 'exception' => $exception, - 'mechanism' => new ExceptionMechanism( - ExceptionMechanism::TYPE_GENERIC, - false, - [ - 'key' => 'value', - //... - ], - ), - ]); - captureEvent(Event::createEvent(), $hint); - ``` - Learn more about the interface of the `ExceptionMechanism` on https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism - - Access or mutate `ExceptionMechanism::data` via `ExceptionMechanism::getData()` and `ExceptionMechanism::setData()` - - If an exception contains a user-provided `code`, the value will be serialized into the event and displayed on the issues details page. - ```php - throw new \Exception('Oh no!', 123); - ``` - -## 3.12.1 (2023-01-12) - -- fix: Allow `null` on `getTracesSampleRate` and `setTracesSampleRate` in `Options` class (#1441) - -## 3.12.0 (2022-11-22) - -- feat: Add `before_send_transaction` option (#1424) -- fix: Set `traces_sample_rate` to `null` by default (#1428) - -## 3.11.0 (2022-10-25) - -- fix: Only include the transaction name to the DSC if it has good quality (#1410) -- ref: Enable the ModulesIntegration by default (#1415) -- ref: Expose the ExceptionMechanism through the event hint (#1416) - -## 3.10.0 (2022-10-19) - -- ref: Add correct `never` option for `max_request_body_size` (#1397) - - Deprecate `max_request_body_size.none` in favour of `max_request_body_size.never` -- fix: Sampling now correctly takes in account the parent sampling decision if available instead of always being `false` when tracing is disabled (#1407) - -## 3.9.1 (2022-10-11) - -- fix: Suppress errors on is_callable (#1401) - -## 3.9.0 (2022-10-05) - -- feat: Add `trace_propagation_targets` option (#1396) -- feat: Expose a function to retrieve the URL of the CSP endpoint (#1378) -- feat: Add support for Dynamic Sampling (#1360) - - Add `segment` to `UserDataBag` - - Add `TransactionSource`, to set information about the transaction name via `TransactionContext::setSource()` (#1382) - - Deprecate `TransactionContext::fromSentryTrace()` in favor of `TransactionContext::fromHeaders()` - -## 3.8.1 (2022-09-21) - -- fix: Use constant for the SDK version (#1374) -- fix: Do not throw an TypeError on numeric HTTP headers (#1370) - -## 3.8.0 (2022-09-05) - -- Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) -- Do not setup any error handlers if the DSN is null (#1349) -- Add setter for type on the `ExceptionDataBag` (#1347) -- Drop symfony/polyfill-uuid in favour of a standalone implementation (#1346) - -## 3.7.0 (2022-07-18) - -- Fix `Scope::getTransaction()` so that it returns also unsampled transactions (#1334) -- Set the event extras by taking the data from the Monolog record's extra (#1330) - -## 3.6.1 (2022-06-27) - -- Set the `sentry-trace` header when using the tracing middleware (#1331) - -## 3.6.0 (2022-06-10) - -- Add support for `monolog/monolog:^3.0` (#1321) -- Add `setTag` and `removeTag` public methods to `Event` for easier manipulation of tags (#1324) - -## 3.5.0 (2022-05-19) - -- Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) -- Fix stripping of memory addresses from stacktrace frames of anonymous classes in PHP `>=7.4.2` (#1314) -- Set the default `send_attempts` to `0` (this disables retries) and deprecate the option. If you require retries you can increase the `send_attempts` option to the desired value. (#1312) -- Add `http_connect_timeout` and `http_timeout` client options (#1282) - -## 3.4.0 (2022-03-14) - -- Update Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) -- Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization -- The `withScope` methods now return the callback's return value (#1263) -- Set the event extras by taking the data from the Monolog record's context (#1244) -- Make the `StacktraceBuilder` class part of the public API and add the `Client::getStacktraceBuilder()` method to build custom stacktraces (#1124) -- Support handling the server rate-limits when sending events to Sentry (#1291) -- Treat the project ID component of the DSN as a `string` rather than an `integer` (#1293) - -## 3.3.7 (2022-01-19) - -- Fix the serialization of a `callable` when the autoloader throws exceptions (#1280) - -## 3.3.6 (2022-01-14) - -- Optimize `Span` constructor and add benchmarks (#1274) -- Handle autoloader that throws an exception while trying to serialize a possible callable (#1276) - -## 3.3.5 (2021-12-27) - -- Bump the minimum required version of the `jean85/pretty-package-versions` package (#1267) - -## 3.3.4 (2021-11-08) - -- Avoid overwriting the error level set by the user on the event when capturing an `ErrorException` exception (#1251) -- Allow installing the project alongside Symfony `6.x` components (#1257) -- Run the test suite against PHP `8.1` (#1245) - -## 3.3.3 (2021-10-04) - -- Fix fatal error in the `EnvironmentIntegration` integration if the `php_uname` function is disabled (#1243) - -## 3.3.2 (2021-07-19) - -- Allow installation of `guzzlehttp/psr7:^2.0` (#1225) -- Allow installation of `psr/log:^1.0|^2.0|^3.0` (#1229) - -## 3.3.1 (2021-06-21) - -- Fix missing collecting of frames's arguments when using `captureEvent()` without expliciting a stacktrace or an exception (#1223) - -## 3.3.0 (2021-05-26) - -- Allow setting a custom timestamp on the breadcrumbs (#1193) -- Add option `ignore_tags` to `IgnoreErrorsIntegration` in order to ignore exceptions by tags values (#1201) - -## 3.2.2 (2021-05-06) - -- Fix missing handling of `EventHint` in the `HubAdapter::capture*()` methods (#1206) - -## 3.2.1 (2021-04-06) - -- Changes behaviour of `error_types` option when not set: before it defaulted to `error_reporting()` statically at SDK initialization; now it will be evaluated each time during error handling to allow silencing errors temporarily (#1196) - -## 3.2.0 (2021-03-03) - -- Make the HTTP headers sanitizable in the `RequestIntegration` integration instead of removing them entirely (#1161) -- Deprecate the `logger` option (#1167) -- Pass the event hint from the `capture*()` methods down to the `before_send` callback (#1138) -- Deprecate the `tags` option, see the [docs](https://docs.sentry.io/platforms/php/guides/laravel/enriching-events/tags/) for other ways to set tags (#1174) -- Make sure the `environment` field is set to `production` if it has not been overridden explicitly (#1116) - -## 3.1.5 (2021-02-18) - -- Fix incorrect detection of silenced errors (by the `@` operator) (#1183) - -## 3.1.4 (2021-02-02) - -- Allow jean85/pretty-package-versions 2.0 (#1170) - -## 3.1.3 (2021-01-25) - -- Fix the fetching of the version of the SDK (#1169) -- Add the `$customSamplingContext` argument to `Hub::startTransaction()` and `HubAdapter::startTransaction()` to fix deprecations thrown in Symfony (#1176) - -## 3.1.2 (2021-01-08) - -- Fix unwanted call to the `before_send` callback with transaction events, use `traces_sampler` instead to filter transactions (#1158) -- Fix the `logger` option not being applied to the event object (#1165) -- Fix a bug that made some event attributes being overwritten by option config values when calling `captureEvent()` (#1148) - -## 3.1.1 (2020-12-07) - -- Add support for PHP 8.0 (#1087) -- Change the error handling for silenced fatal errors using `@` to use a mask check in order to be php 8 compatible (#1141) -- Update the `guzzlehttp/promises` package to the minimum required version compatible with PHP 8 (#1144) -- Update the `symfony/options-resolver` package to the minimum required version compatible with PHP 8 (#1144) - -## 3.1.0 (2020-12-01) - -- Fix capturing of the request body in the `RequestIntegration` integration (#1139) -- Deprecate `SpanContext::fromTraceparent()` in favor of `TransactionContext::fromSentryTrace()` (#1134) -- Allow setting custom data on the sampling context by passing it as 2nd argument of the `startTransaction()` function (#1134) -- Add setter for value on the `ExceptionDataBag` (#1100) -- Add `Scope::removeTag` method (#1126) - -## 3.0.4 (2020-11-06) - -- Fix stacktrace missing from payload for non-exception events (#1123) -- Fix capturing of the request body in the `RequestIntegration` integration when the stream is empty (#1119) - -## 3.0.3 (2020-10-12) - -- Fix missing source code excerpts for stacktrace frames whose absolute file path is equal to the file path (#1104) -- Fix requirements to construct a valid object instance of the `UserDataBag` class (#1108) - -## 3.0.2 (2020-10-02) - -- Fix use of the `sample_rate` option rather than `traces_sample_rate` when capturing a `Transaction` (#1106) - -## 3.0.1 (2020-10-01) - -- Fix use of `Transaction` instead of `Span` in the `GuzzleMiddleware` middleware (#1099) - -## 3.0.0 (2020-09-28) - -**Tracing API** - -In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. -More information can be found in our [Performance](https://docs.sentry.io/platforms/php/performance/) docs. - -**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are -using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using -[sentry.io](https://sentry.io) nothing will change and no action is needed. - -- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) -- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) -- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) -- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) -- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) -- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) -- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) -- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) -- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) -- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) -- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) -- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) -- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) -- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) -- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) -- Refactor how the event data gets serialized to JSON (#1077) -- Add `traces_sampler` option to set custom sample rate callback (#1083) -- [BC BREAK] Add named constructors to the `Event` class (#1085) -- Raise the minimum version of PHP to `7.2` and the minimum version of some dependencies (#1088) -- [BC BREAK] Change the `captureEvent` to only accept an instance of the `Event` class rather than also a plain array (#1094) -- Add Guzzle middleware to trace performance of HTTP requests (#1096) - -## 3.0.0-beta1 (2020-09-03) - -**Tracing API** - -In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. -More information can be found in our [Performance](https://docs.sentry.io/product/performance/) docs or specific -[PHP SDK](https://docs.sentry.io/platforms/php/) docs. + To use a different transport, you may use the `transport` option. A custom transport must implement the `TransportInterface`. + If you use the `transport` option, the `http_client` option has no effect. -**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are -using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using -[sentry.io](https://sentry.io) nothing will change and no action is needed. +# Misc -- [BC BREAK] Remove the deprecated code that made the `Hub` class a singleton (#1038) -- [BC BREAK] Remove deprecated code that permitted to register the error, fatal error and exception handlers at once (#1037) -- [BC BREAK] Change the default value for the `error_types` option from `E_ALL` to the value get from `error_reporting()` (#1037) -- [BC BREAK] Remove deprecated code to return the event ID as a `string` rather than an object instance from the transport, the client and the hub (#1036) -- [BC BREAK] Remove some deprecated methods from the `Options` class. (#1047) -- [BC BREAK] Remove the deprecated code from the `ModulesIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `RequestIntegration` integration (#1047) -- [BC BREAK] Remove the deprecated code from the `Breadcrumb` class (#1047) -- [BC BREAK] Remove the deprecated methods from the `ClientBuilderInterface` interface and its implementations (#1047) -- [BC BREAK] The `Scope::setUser()` method now always merges the given data with the existing one instead of replacing it as a whole (#1047) -- [BC BREAK] Remove the `Context::CONTEXT_USER`, `Context::CONTEXT_RUNTIME`, `Context::CONTEXT_TAGS`, `Context::CONTEXT_EXTRA`, `Context::CONTEXT_SERVER_OS` constants (#1047) -- [BC BREAK] Use PSR-17 factories in place of the Httplug's ones and return a promise from the transport (#1066) -- [BC BREAK] The Monolog handler does not set anymore tags and extras on the event object (#1068) -- [BC BREAK] Remove the `UserContext`, `ExtraContext` and `Context` classes and refactor the `ServerOsContext` and `RuntimeContext` classes (#1071) -- [BC BREAK] Remove the `FlushableClientInterface` and the `ClosableTransportInterface` interfaces (#1079) -- [BC BREAK] Remove the `SpoolTransport` transport and all its related classes (#1080) -- Add the `EnvironmentIntegration` integration to gather data for the `os` and `runtime` contexts (#1071) -- Refactor how the event data gets serialized to JSON (#1077) +- The abandoned package `php-http/message-factory` was removed. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 11d78096c..000000000 --- a/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -gc -am .PHONY: test - -develop: update-submodules - composer install - make setup-git - -update-submodules: - git submodule init - git submodule update - -cs: - vendor/bin/php-cs-fixer fix --verbose --diff - -cs-dry-run: - vendor/bin/php-cs-fixer fix --verbose --diff --dry-run - -cs-fix: - vendor/bin/php-cs-fixer fix - -psalm: - vendor/bin/psalm - -phpstan: - vendor/bin/phpstan analyse - -test: cs-fix phpstan psalm - vendor/bin/phpunit --verbose - -setup-git: - git config branch.autosetuprebase always diff --git a/README.md b/README.md index 12da4a6b2..1acc03d2a 100644 --- a/README.md +++ b/README.md @@ -24,37 +24,16 @@ information needed to prioritize, identify, reproduce and fix each issue. ### Install -To install the SDK you will need to be using [Composer]([https://getcomposer.org/) -in your project. To install it please see the [docs](https://getcomposer.org/download/). - -This is our "core" SDK, meaning that all the important code regarding error handling lives here. -If you are happy with using the HTTP client we recommend install the SDK like: [`sentry/sdk`](https://github.com/getsentry/sentry-php-sdk) +Install the SDK using [Composer](https://getcomposer.org/). ```bash -composer require sentry/sdk +composer require sentry/sentry ``` -This package (`sentry/sentry`) is not tied to any specific library that sends HTTP messages. Instead, -it uses [Httplug](https://github.com/php-http/httplug) to let users choose whichever -PSR-7 implementation and HTTP client they want to use. - -If you just want to get started quickly you should run the following command: - -```bash -composer require sentry/sentry php-http/curl-client -``` - -This is basically what our metapackage (`sentry/sdk`) provides. - -This will install the library itself along with an HTTP client adapter that uses -cURL as transport method (provided by Httplug). You do not have to use those -packages if you do not want to. The SDK does not care about which transport method -you want to use because it's an implementation detail of your application. You may -use any package that provides [`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation) -and [`http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation). - ### Configuration +Initialize the SDK as early as possible in your application. + ```php \Sentry\init(['dsn' => '___PUBLIC_DSN___' ]); ``` @@ -76,7 +55,7 @@ The following integrations are fully supported and maintained by the Sentry team - [Symfony](https://github.com/getsentry/sentry-symfony) - [Laravel](https://github.com/getsentry/sentry-laravel) -## 3rd party integrations +## 3rd party integrations using the old SDK 3.x The following integrations are available and maintained by members of the Sentry community. @@ -91,14 +70,14 @@ The following integrations are available and maintained by members of the Sentry - [October CMS](https://github.com/OFFLINE-GmbH/oc-sentry-plugin) - ... feel free to be famous, create a port to your favourite platform! -## 3rd party integrations using old SDK 2.x +## 3rd party integrations using the old SDK 2.x - [Neos Flow](https://github.com/networkteam/Networkteam.SentryClient) - [OXID eShop](https://github.com/OXIDprojects/sentry) - [TYPO3](https://github.com/networkteam/sentry_client) - [CakePHP](https://github.com/Connehito/cake-sentry/tree/3.x) -## 3rd party integrations using old SDK 1.x +## 3rd party integrations using the old SDK 1.x - [Neos CMS](https://github.com/networkteam/Netwokteam.Neos.SentryClient) - [OpenCart](https://github.com/BurdaPraha/oc_sentry) @@ -116,7 +95,7 @@ Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). ## Getting help/support -If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr). There is a ton of great people in our Discord community ready to help you! +If you need help setting up or configuring the PHP SDK (or anything else in the Sentry universe) please head over to the [Sentry Community on Discord](https://discord.com/invite/sentry). There is a ton of great people in our Discord community ready to help you! ## Resources diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md new file mode 100644 index 000000000..16eaf1ce9 --- /dev/null +++ b/UPGRADE-4.0.md @@ -0,0 +1,65 @@ +# Upgrade 3.x to 4.0 + +- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry. + + If you are using [sentry.io](https://sentry.io), no action is needed. + If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`. + +- Added `ext-curl` as a composer requirement. + +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_errors` option instead. + + ```php + Sentry\init([ + 'ignore_exceptions' => [BadThingsHappenedException::class], + ]); + ``` + + This option performs an [`is_a`](https://www.php.net/manual/en/function.is-a.php) check, so you can also ignore more generic exceptions. + +- Removed support for `symfony/options-resolver: ^3.4.43`. + +- The `RequestFetcher` now relies on `guzzlehttp/psr7: ^1.8.4|^2.1.1`. + +- Added new methods to `ClientInterface` + + ```php + public function getCspReportUrl(): ?string; + + public function getStacktraceBuilder(): StacktraceBuilder; + ``` + +- Added new methods to `HubInterface` + + ```php + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; + ``` + +- The new default value for the `trace_propagation_targets` option is now `null`. To not attach any headers to outgoing requests, using the `GuzzleTracingMiddleware`, set this option to `[]`. +- The `ignore_errors` option now performs a `is_a` check on the provided class strings. +- The `send_attempts` option was removed. You may implement a custom transport if you rely on this behaviour. +- The `enable_compression` option was removed. Use `http_compression` instead. +- The `logger` option now accepts a `Psr\Log\LoggerInterface` instance instead of `string`. + +- Removed `Options::getSendAttempts/setSendAttempts()`. +- Removed `Options::isCompressionEnabled/setEnableCompression()`. Use `Options::isHttpCompressionEnabled/setEnableHttpCompression()` instead. +- Removed `SpanContext::fromTraceparent()`. Use `Sentry\continueTrace()` instead. +- Removed `TransactionContext::fromSentryTrace()`. Use `Sentry\continueTrace()` instead. +- Removed `Sentry\Exception\InvalidArgumentException`. Use `\InvalidArgumentException` instead. +- Removed `Sentry\Exception/ExceptionInterface`. +- Removed `ClientBuilderInterface()`. +- Removed `ClientBuilder::setSerializer()`. +- Removed `ClientBuilder::setTransportFactory()`. You can set a custom transport via the `transport` option. +- Removed `Client::__construct()` parameter `SerializerInterface $serializer`. +- Removed `TransportFactoryInterface`. +- Removed `DefaultTransportFactory`. +- Removed `HttpClientFactoryInterface`. +- Removed `HttpClientFactory`. +- Removed `NullTransport`. +- Removed `Dsn::getSecretKey()`. +- Removed `Dsn::setSecretKey()`. +- Removed `EventType::default()`. + +- Added return type to `Dsn::getProjectId(): string`. +- Changed return type to `Options::getLogger(): ?LoggerInterface`. +- Changed parameter type of `Options::setLogger(LoggerInterface $logger)`. diff --git a/composer.json b/composer.json index 81e9f19bd..4ff1f8e95 100644 --- a/composer.json +++ b/composer.json @@ -23,31 +23,19 @@ "php": "^7.2|^8.0", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/promises": "^1.5.3|^2.0", + "ext-curl": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "jean85/pretty-package-versions": "^1.5|^2.0.4", - "php-http/async-client-implementation": "^1.0", - "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.15", - "php-http/httplug": "^1.1|^2.0", - "php-http/message": "^1.5", - "php-http/message-factory": "^1.1", - "psr/http-factory": "^1.0", - "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", - "symfony/polyfill-php80": "^1.17" + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "friendsofphp/php-cs-fixer": "^3.4", + "guzzlehttp/promises": "^1.0|^2.0", "guzzlehttp/psr7": "^1.8.4|^2.1.1", - "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.6|^2.0|^3.0", - "nikic/php-parser": "^4.10.3", - "php-http/mock-client": "^1.3", "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.3", - "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", "vimeo/psalm": "^4.17" @@ -56,7 +44,6 @@ "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." }, "conflict": { - "php-http/client-common": "1.8.0", "raven/raven": "*" }, "autoload": { @@ -73,12 +60,21 @@ } }, "scripts": { + "check": [ + "@cs-check", + "@phpstan", + "@psalm", + "@tests" + ], "tests": [ "vendor/bin/phpunit --verbose" ], - "phpcs": [ + "cs-check": [ "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" ], + "cs-fix": [ + "vendor/bin/php-cs-fixer fix --verbose --diff" + ], "phpstan": [ "vendor/bin/phpstan analyse" ], @@ -87,12 +83,7 @@ ] }, "config": { - "sort-packages": true, - "allow-plugins": { - "composer/package-versions-deprecated": true, - "php-http/discovery": false, - "phpstan/extension-installer": true - } + "sort-packages": true }, "prefer-stable": true } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 39aa31fc7..7083cd320 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,20 +1,10 @@ parameters: ignoreErrors: - - - message: "#^Constructor of class Sentry\\\\Client has an unused parameter \\$serializer\\.$#" - count: 1 - path: src/Client.php - - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null but returns \\(T of Sentry\\\\Integration\\\\IntegrationInterface\\)\\|null\\.$#" count: 1 path: src/Client.php - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" - count: 3 - path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int\\<0, 65535\\>, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 @@ -36,59 +26,19 @@ parameters: path: src/Dsn.php - - message: "#^Access to constant CONNECT_TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Access to constant PROXY on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Access to constant TIMEOUT on an unknown class GuzzleHttp\\\\RequestOptions\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Call to static method create\\(\\) on an unknown class Symfony\\\\Component\\\\HttpClient\\\\HttpClient\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Call to static method createWithConfig\\(\\) on an unknown class Http\\\\Adapter\\\\Guzzle6\\\\Client\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Http\\\\Client\\\\Curl\\\\Client\\.$#" - count: 1 - path: src/HttpClient/HttpClientFactory.php - - - - message: "#^Method Sentry\\\\HttpClient\\\\HttpClientFactory\\:\\:resolveClient\\(\\) should return Http\\\\Client\\\\HttpAsyncClient\\|Psr\\\\Http\\\\Client\\\\ClientInterface but returns Symfony\\\\Component\\\\HttpClient\\\\HttplugClient\\.$#" + message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" count: 1 - path: src/HttpClient/HttpClientFactory.php + path: src/Integration/RequestIntegration.php - - message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" count: 1 - path: src/Integration/IgnoreErrorsIntegration.php + path: src/Logger/DebugFileLogger.php - - message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" count: 1 - path: src/Integration/RequestIntegration.php + path: src/Logger/DebugStdOutLogger.php - message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" @@ -140,6 +90,11 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpClient\\(\\) should return Sentry\\\\HttpClient\\\\HttpClientInterface\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpConnectTimeout\\(\\) should return float but returns mixed\\.$#" count: 1 @@ -150,13 +105,23 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpProxyAuthentication\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpSslVerifyPeer\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpTimeout\\(\\) should return float but returns mixed\\.$#" count: 1 path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\ but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\\\> but returns mixed\\.$#" count: 1 path: src/Options.php @@ -181,7 +146,7 @@ parameters: path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return string but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return Psr\\\\Log\\\\LoggerInterface\\|null but returns mixed\\.$#" count: 1 path: src/Options.php @@ -215,11 +180,6 @@ parameters: count: 1 path: src/Options.php - - - message: "#^Method Sentry\\\\Options\\:\\:getSendAttempts\\(\\) should return int but returns mixed\\.$#" - count: 1 - path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:getServerName\\(\\) should return string but returns mixed\\.$#" count: 1 @@ -245,13 +205,18 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getTransport\\(\\) should return Sentry\\\\Transport\\\\TransportInterface\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:hasDefaultIntegrations\\(\\) should return bool but returns mixed\\.$#" count: 1 path: src/Options.php - - message: "#^Method Sentry\\\\Options\\:\\:isCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" + message: "#^Method Sentry\\\\Options\\:\\:isHttpCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" count: 1 path: src/Options.php @@ -280,51 +245,6 @@ parameters: count: 1 path: src/Serializer/AbstractSerializer.php - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureLastError\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureMessage\\(\\) invoked with 4 parameters, 1\\-3 required\\.$#" - count: 1 - path: src/State/Hub.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/State/HubAdapter.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$customSamplingContext$#" - count: 1 - path: src/State/HubInterface.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$hint$#" - count: 3 - path: src/State/HubInterface.php - - message: "#^Call to method getResponse\\(\\) on an unknown class GuzzleHttp\\\\Exception\\\\RequestException\\.$#" count: 1 @@ -335,11 +255,6 @@ parameters: count: 1 path: src/Tracing/GuzzleTracingMiddleware.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: src/Tracing/SpanContext.php - - message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" count: 1 @@ -369,23 +284,3 @@ parameters: message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" count: 1 path: src/Util/JSON.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureLastError\\(\\) invoked with 1 parameter, 0 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureMessage\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" - count: 1 - path: src/functions.php - - - - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:startTransaction\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 1 - path: src/functions.php diff --git a/src/Breadcrumb.php b/src/Breadcrumb.php index 7229fddc4..a5e3116dc 100644 --- a/src/Breadcrumb.php +++ b/src/Breadcrumb.php @@ -4,8 +4,6 @@ namespace Sentry; -use Sentry\Exception\InvalidArgumentException; - /** * This class stores all the information about a breadcrumb. * @@ -118,7 +116,7 @@ final class Breadcrumb public function __construct(string $level, string $type, string $category, ?string $message = null, array $metadata = [], ?float $timestamp = null) { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } $this->type = $type; @@ -174,7 +172,7 @@ public function getLevel(): string public function withLevel(string $level): self { if (!\in_array($level, self::ALLOWED_LEVELS, true)) { - throw new InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); + throw new \InvalidArgumentException('The value of the $level argument must be one of the Breadcrumb::LEVEL_* constants.'); } if ($level === $this->level) { diff --git a/src/CheckIn.php b/src/CheckIn.php index 7cc70e756..1a0eafbe5 100644 --- a/src/CheckIn.php +++ b/src/CheckIn.php @@ -49,7 +49,7 @@ final class CheckIn public function __construct( string $monitorSlug, CheckInStatus $status, - string $id = null, + ?string $id = null, ?string $release = null, ?string $environment = null, $duration = null, @@ -80,9 +80,11 @@ public function getMonitorSlug(): string return $this->monitorSlug; } - public function setMonitorSlug(string $monitorSlug): void + public function setMonitorSlug(string $monitorSlug): self { $this->monitorSlug = $monitorSlug; + + return $this; } public function getStatus(): CheckInStatus @@ -90,9 +92,11 @@ public function getStatus(): CheckInStatus return $this->status; } - public function setStatus(CheckInStatus $status): void + public function setStatus(CheckInStatus $status): self { $this->status = $status; + + return $this; } public function getRelease(): ?string @@ -100,9 +104,11 @@ public function getRelease(): ?string return $this->release; } - public function setRelease(string $release): void + public function setRelease(string $release): self { $this->release = $release; + + return $this; } public function getEnvironment(): ?string @@ -110,9 +116,11 @@ public function getEnvironment(): ?string return $this->environment; } - public function setEnvironment(string $environment): void + public function setEnvironment(string $environment): self { $this->environment = $environment; + + return $this; } /** @@ -126,9 +134,11 @@ public function getDuration() /** * @param int|float|null $duration The duration of the check-in in seconds */ - public function setDuration($duration): void + public function setDuration($duration): self { $this->duration = $duration; + + return $this; } public function getMonitorConfig(): ?MonitorConfig @@ -136,8 +146,10 @@ public function getMonitorConfig(): ?MonitorConfig return $this->monitorConfig; } - public function setMonitorConfig(?MonitorConfig $monitorConfig): void + public function setMonitorConfig(?MonitorConfig $monitorConfig): self { $this->monitorConfig = $monitorConfig; + + return $this; } } diff --git a/src/Client.php b/src/Client.php index 5f1cef5fc..5cc99cd3e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,15 +4,14 @@ namespace Sentry; -use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; +use Sentry\Transport\Result; use Sentry\Transport\TransportInterface; /** @@ -81,7 +80,6 @@ final class Client implements ClientInterface * @param TransportInterface $transport The transport * @param string|null $sdkIdentifier The Sentry SDK identifier * @param string|null $sdkVersion The Sentry SDK version - * @param SerializerInterface|null $serializer The serializer argument is deprecated since version 3.3 and will be removed in 4.0. It's currently unused. * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments * @param LoggerInterface|null $logger The PSR-3 logger */ @@ -90,17 +88,17 @@ public function __construct( TransportInterface $transport, ?string $sdkIdentifier = null, ?string $sdkVersion = null, - ?SerializerInterface $serializer = null, ?RepresentationSerializerInterface $representationSerializer = null, ?LoggerInterface $logger = null ) { $this->options = $options; $this->transport = $transport; - $this->logger = $logger ?? new NullLogger(); - $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; + $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); + $this->logger = $logger ?? new NullLogger(); + + $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); } /** @@ -118,7 +116,7 @@ public function getCspReportUrl(): ?string { $dsn = $this->options->getDsn(); - if (null === $dsn) { + if ($dsn === null) { return null; } @@ -154,7 +152,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E { $hint = $hint ?? new EventHint(); - if (null === $hint->exception) { + if ($hint->exception === null) { $hint->exception = $exception; } @@ -168,19 +166,23 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop { $event = $this->prepareEvent($event, $hint, $scope); - if (null === $event) { + if ($event === null) { return null; } try { - /** @var Response $response */ - $response = $this->transport->send($event)->wait(); - $event = $response->getEvent(); + /** @var Result $result */ + $result = $this->transport->send($event); + $event = $result->getEvent(); - if (null !== $event) { + if ($event !== null) { return $event->getId(); } } catch (\Throwable $exception) { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), + ['exception' => $exception, 'event' => $event] + ); } return null; @@ -193,7 +195,7 @@ public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): { $error = error_get_last(); - if (null === $error || !isset($error['message'][0])) { + if ($error === null || !isset($error['message'][0])) { return null; } @@ -216,7 +218,7 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} */ - public function flush(?int $timeout = null): PromiseInterface + public function flush(?int $timeout = null): Result { return $this->transport->close($timeout); } @@ -240,12 +242,12 @@ public function getStacktraceBuilder(): StacktraceBuilder */ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event { - if (null !== $hint) { - if (null !== $hint->exception && empty($event->getExceptions())) { + if ($hint !== null) { + if ($hint->exception !== null && empty($event->getExceptions())) { $this->addThrowableToEvent($event, $hint->exception, $hint); } - if (null !== $hint->stacktrace && null === $event->getStacktrace()) { + if ($hint->stacktrace !== null && $event->getStacktrace() === null) { $event->setStacktrace($hint->stacktrace); } } @@ -256,22 +258,18 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event->setSdkVersion($this->sdkVersion); $event->setTags(array_merge($this->options->getTags(), $event->getTags())); - if (null === $event->getServerName()) { + if ($event->getServerName() === null) { $event->setServerName($this->options->getServerName()); } - if (null === $event->getRelease()) { + if ($event->getRelease() === null) { $event->setRelease($this->options->getRelease()); } - if (null === $event->getEnvironment()) { + if ($event->getEnvironment() === null) { $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); } - if (null === $event->getLogger()) { - $event->setLogger($this->options->getLogger(false)); - } - $isTransaction = EventType::transaction() === $event->getType(); $sampleRate = $this->options->getSampleRate(); @@ -283,15 +281,15 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $event = $this->applyIgnoreOptions($event); - if (null === $event) { + if ($event === null) { return null; } - if (null !== $scope) { + if ($scope !== null) { $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint, $this->options); - if (null === $event) { + if ($event === null) { $this->logger->info( 'The event will be discarded because one of the event processors returned "null".', ['event' => $beforeEventProcessors] @@ -304,7 +302,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco $beforeSendCallback = $event; $event = $this->applyBeforeSendCallback($event, $hint); - if (null === $event) { + if ($event === null) { $this->logger->info( sprintf( 'The event will be discarded because the "%s" callback returned "null".', @@ -327,13 +325,15 @@ private function applyIgnoreOptions(Event $event): ?Event } foreach ($exceptions as $exception) { - if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) { - $this->logger->info( - 'The event will be discarded because it matches an entry in "ignore_exceptions".', - ['event' => $event] - ); - - return null; + foreach ($this->options->getIgnoreExceptions() as $ignoredException) { + if (is_a($exception->getType(), $ignoredException, true)) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['event' => $event] + ); + + return null; + } } } } @@ -341,7 +341,7 @@ private function applyIgnoreOptions(Event $event): ?Event if ($event->getType() === EventType::transaction()) { $transactionName = $event->getTransaction(); - if (null === $transactionName) { + if ($transactionName === null) { return $event; } @@ -394,7 +394,7 @@ private function addMissingStacktraceToEvent(Event $event): void } // We should not add a stacktrace when the event already has one or contains exceptions - if (null !== $event->getStacktrace() || !empty($event->getExceptions())) { + if ($event->getStacktrace() !== null || !empty($event->getExceptions())) { return; } @@ -414,7 +414,7 @@ private function addMissingStacktraceToEvent(Event $event): void */ private function addThrowableToEvent(Event $event, \Throwable $exception, EventHint $hint): void { - if ($exception instanceof \ErrorException && null === $event->getLevel()) { + if ($exception instanceof \ErrorException && $event->getLevel() === null) { $event->setLevel(Severity::fromError($exception->getSeverity())); } diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 8cdcc7317..54a433eb0 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,21 +4,20 @@ namespace Sentry; -use Http\Discovery\Psr17FactoryDiscovery; use Psr\Log\LoggerInterface; -use Sentry\HttpClient\HttpClientFactory; +use Sentry\HttpClient\HttpClient; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\Serializer\PayloadSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\SerializerInterface; -use Sentry\Transport\DefaultTransportFactory; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\HttpTransport; use Sentry\Transport\TransportInterface; /** - * The default implementation of {@link ClientBuilderInterface}. + * A configurable builder for Client objects. * - * @author Stefano Arlandini + * @internal */ -final class ClientBuilder implements ClientBuilderInterface +final class ClientBuilder { /** * @var Options The client options @@ -26,19 +25,14 @@ final class ClientBuilder implements ClientBuilderInterface private $options; /** - * @var TransportFactoryInterface|null The transport factory - */ - private $transportFactory; - - /** - * @var TransportInterface|null The transport + * @var TransportInterface The transport */ private $transport; /** - * @var SerializerInterface|null The serializer to be injected in the client + * @var HttpClientInterface The HTTP client */ - private $serializer; + private $httpClient; /** * @var RepresentationSerializerInterface|null The representation serializer to be injected in the client @@ -65,130 +59,99 @@ final class ClientBuilder implements ClientBuilderInterface * * @param Options|null $options The client options */ - public function __construct(Options $options = null) + public function __construct(?Options $options = null) { $this->options = $options ?? new Options(); + + $this->logger = $this->options->getLogger() ?? null; + + $this->httpClient = $this->options->getHttpClient() ?? new HttpClient($this->sdkIdentifier, $this->sdkVersion); + $this->transport = $this->options->getTransport() ?? new HttpTransport( + $this->options, + $this->httpClient, + new PayloadSerializer($this->options), + $this->logger + ); } /** - * {@inheritdoc} + * @param array $options The client options, in naked array form */ - public static function create(array $options = []): ClientBuilderInterface + public static function create(array $options = []): self { return new self(new Options($options)); } - /** - * {@inheritdoc} - */ public function getOptions(): Options { return $this->options; } - /** - * {@inheritdoc} - */ - public function setSerializer(SerializerInterface $serializer): ClientBuilderInterface + public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self { - $this->serializer = $serializer; + $this->representationSerializer = $representationSerializer; return $this; } - /** - * {@inheritdoc} - */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): ClientBuilderInterface + public function getLogger(): ?LoggerInterface { - $this->representationSerializer = $representationSerializer; - - return $this; + return $this->logger; } - /** - * {@inheritdoc} - */ - public function setLogger(LoggerInterface $logger): ClientBuilderInterface + public function setLogger(LoggerInterface $logger): self { $this->logger = $logger; return $this; } - /** - * {@inheritdoc} - */ - public function setSdkIdentifier(string $sdkIdentifier): ClientBuilderInterface + public function setSdkIdentifier(string $sdkIdentifier): self { $this->sdkIdentifier = $sdkIdentifier; return $this; } - /** - * {@inheritdoc} - */ - public function setSdkVersion(string $sdkVersion): ClientBuilderInterface + public function setSdkVersion(string $sdkVersion): self { $this->sdkVersion = $sdkVersion; return $this; } - /** - * {@inheritdoc} - */ - public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface + public function getTransport(): TransportInterface { - $this->transportFactory = $transportFactory; - - return $this; + return $this->transport; } - /** - * {@inheritdoc} - */ - public function getClient(): ClientInterface + public function setTransport(TransportInterface $transport): self { - $this->transport = $this->transport ?? $this->createTransportInstance(); + $this->transport = $transport; - return new Client($this->options, $this->transport, $this->sdkIdentifier, $this->sdkVersion, $this->serializer, $this->representationSerializer, $this->logger); + return $this; } - /** - * Creates a new instance of the transport mechanism. - */ - private function createTransportInstance(): TransportInterface + public function getHttpClient(): HttpClientInterface { - if (null !== $this->transport) { - return $this->transport; - } + return $this->httpClient; + } - $transportFactory = $this->transportFactory ?? $this->createDefaultTransportFactory(); + public function setHttpClient(HttpClientInterface $httpClient): self + { + $this->httpClient = $httpClient; - return $transportFactory->create($this->options); + return $this; } - /** - * Creates a new instance of the {@see DefaultTransportFactory} factory. - */ - private function createDefaultTransportFactory(): DefaultTransportFactory + public function getClient(): ClientInterface { - $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); - $httpClientFactory = new HttpClientFactory( - null, - null, - $streamFactory, - null, + return new Client( + $this->options, + $this->transport, $this->sdkIdentifier, - $this->sdkVersion - ); - - return new DefaultTransportFactory( - $streamFactory, - Psr17FactoryDiscovery::findRequestFactory(), - $httpClientFactory, + $this->sdkVersion, + $this->representationSerializer, $this->logger ); } diff --git a/src/ClientBuilderInterface.php b/src/ClientBuilderInterface.php deleted file mode 100644 index 35d67b12a..000000000 --- a/src/ClientBuilderInterface.php +++ /dev/null @@ -1,97 +0,0 @@ - - */ -interface ClientBuilderInterface -{ - /** - * Creates a new instance of this builder. - * - * @param array $options The client options, in naked array form - * - * @return static - */ - public static function create(array $options = []): self; - - /** - * The options that will be used to create the {@see Client}. - */ - public function getOptions(): Options; - - /** - * Gets the instance of the client built using the configured options. - */ - public function getClient(): ClientInterface; - - /** - * Sets a serializer instance to be injected as a dependency of the client. - * - * @param SerializerInterface $serializer The serializer to be used by the client to fill the events - * - * @return $this - */ - public function setSerializer(SerializerInterface $serializer): self; - - /** - * Sets a representation serializer instance to be injected as a dependency of the client. - * - * @param RepresentationSerializerInterface $representationSerializer The representation serializer, used to serialize function - * arguments in stack traces, to have string representation - * of non-string values - * - * @return $this - */ - public function setRepresentationSerializer(RepresentationSerializerInterface $representationSerializer): self; - - /** - * Sets a PSR-3 logger to log internal debug messages. - * - * @param LoggerInterface $logger The logger instance - * - * @return $this - */ - public function setLogger(LoggerInterface $logger): ClientBuilderInterface; - - /** - * Sets the transport factory. - * - * @param TransportFactoryInterface $transportFactory The transport factory - * - * @return $this - */ - public function setTransportFactory(TransportFactoryInterface $transportFactory): ClientBuilderInterface; - - /** - * Sets the SDK identifier to be passed onto {@see Event} and HTTP User-Agent header. - * - * @param string $sdkIdentifier The SDK identifier to be sent in {@see Event} and HTTP User-Agent headers - * - * @return $this - * - * @internal - */ - public function setSdkIdentifier(string $sdkIdentifier): self; - - /** - * Sets the SDK version to be passed onto {@see Event} and HTTP User-Agent header. - * - * @param string $sdkVersion The version of the SDK in use, to be sent alongside the SDK identifier - * - * @return $this - * - * @internal - */ - public function setSdkVersion(string $sdkVersion): self; -} diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 3f6d06f38..0aab383a4 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -4,18 +4,10 @@ namespace Sentry; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\Integration\IntegrationInterface; use Sentry\State\Scope; +use Sentry\Transport\Result; -/** - * This interface must be implemented by all Raven client classes. - * - * @method StacktraceBuilder getStacktraceBuilder() Returns the stacktrace builder of the client. - * @method string|null getCspReportUrl() Returns an URL for security policy reporting that's generated from the given DSN - * - * @author Stefano Arlandini - */ interface ClientInterface { /** @@ -23,6 +15,11 @@ interface ClientInterface */ public function getOptions(): Options; + /** + * Returns an URL for security policy reporting that's generated from the configured DSN. + */ + public function getCspReportUrl(): ?string; + /** * Logs a message. * @@ -31,7 +28,7 @@ public function getOptions(): Options; * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs an exception. @@ -40,7 +37,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception, ?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs the most recent error (obtained with {@link error_get_last}). @@ -48,7 +45,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null/*, * @param Scope|null $scope An optional scope keeping the state * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(?Scope $scope = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. @@ -78,5 +75,10 @@ public function getIntegration(string $className): ?IntegrationInterface; * * @param int|null $timeout Maximum time in seconds the client should wait */ - public function flush(?int $timeout = null): PromiseInterface; + public function flush(?int $timeout = null): Result; + + /** + * Returns the stacktrace builder of the client. + */ + public function getStacktraceBuilder(): StacktraceBuilder; } diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 5578dc4be..c8424f912 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -52,7 +52,7 @@ public function __construct( ?string $kernelVersion = null, ?string $machineType = null ) { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -78,7 +78,7 @@ public function getName(): string */ public function setName(string $name): void { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index d0a114e48..65ea1fa36 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -29,7 +29,7 @@ final class RuntimeContext */ public function __construct(string $name, ?string $version = null) { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -52,7 +52,7 @@ public function getName(): string */ public function setName(string $name): void { - if ('' === trim($name)) { + if (trim($name) === '') { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Dsn.php b/src/Dsn.php index ee09d68d0..071a6292f 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -32,11 +32,6 @@ final class Dsn implements \Stringable */ private $publicKey; - /** - * @var string|null The secret key to authenticate the SDK - */ - private $secretKey; - /** * @var string The ID of the resource to access */ @@ -50,21 +45,19 @@ final class Dsn implements \Stringable /** * Class constructor. * - * @param string $scheme The protocol to be used to access the resource - * @param string $host The host that holds the resource - * @param int $port The port on which the resource is exposed - * @param string $projectId The ID of the resource to access - * @param string $path The specific resource that the web client wants to access - * @param string $publicKey The public key to authenticate the SDK - * @param string|null $secretKey The secret key to authenticate the SDK - */ - private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey, ?string $secretKey) + * @param string $scheme The protocol to be used to access the resource + * @param string $host The host that holds the resource + * @param int $port The port on which the resource is exposed + * @param string $projectId The ID of the resource to access + * @param string $path The specific resource that the web client wants to access + * @param string $publicKey The public key to authenticate the SDK + */ + private function __construct(string $scheme, string $host, int $port, string $projectId, string $path, string $publicKey) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; $this->publicKey = $publicKey; - $this->secretKey = $secretKey; $this->path = $path; $this->projectId = $projectId; } @@ -78,7 +71,7 @@ public static function createFromString(string $value): self { $parsedDsn = parse_url($value); - if (false === $parsedDsn) { + if ($parsedDsn === false) { throw new \InvalidArgumentException(sprintf('The "%s" DSN is invalid.', $value)); } @@ -88,10 +81,6 @@ public static function createFromString(string $value): self } } - if (isset($parsedDsn['pass']) && empty($parsedDsn['pass'])) { - throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); - } - if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } @@ -101,18 +90,17 @@ public static function createFromString(string $value): self $lastSlashPosition = strrpos($parsedDsn['path'], '/'); $path = $parsedDsn['path']; - if (false !== $lastSlashPosition) { + if ($lastSlashPosition !== false) { $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } return new self( $parsedDsn['scheme'], $parsedDsn['host'], - $parsedDsn['port'] ?? ('http' === $parsedDsn['scheme'] ? 80 : 443), + $parsedDsn['port'] ?? ($parsedDsn['scheme'] === 'http' ? 80 : 443), $projectId, $path, - $parsedDsn['user'], - $parsedDsn['pass'] ?? null + $parsedDsn['user'] ); } @@ -150,18 +138,10 @@ public function getPath(): string /** * Gets the ID of the resource to access. - * - * @return int|string */ - public function getProjectId(bool $returnAsString = false) + public function getProjectId(): string { - if ($returnAsString) { - return $this->projectId; - } - - @trigger_error(sprintf('Calling the method %s() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - return (int) $this->projectId; + return $this->projectId; } /** @@ -172,22 +152,6 @@ public function getPublicKey(): string return $this->publicKey; } - /** - * Gets the secret key to authenticate the SDK. - */ - public function getSecretKey(): ?string - { - return $this->secretKey; - } - - /** - * Returns the URL of the API for the store endpoint. - */ - public function getStoreApiEndpointUrl(): string - { - return $this->getBaseEndpointUrl() . '/store/'; - } - /** * Returns the URL of the API for the envelope endpoint. */ @@ -211,17 +175,13 @@ public function __toString(): string { $url = $this->scheme . '://' . $this->publicKey; - if (null !== $this->secretKey) { - $url .= ':' . $this->secretKey; - } - $url .= '@' . $this->host; - if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) { $url .= ':' . $this->port; } - if (null !== $this->path) { + if ($this->path !== null) { $url .= $this->path; } @@ -237,11 +197,11 @@ private function getBaseEndpointUrl(): string { $url = $this->scheme . '://' . $this->host; - if (('http' === $this->scheme && 80 !== $this->port) || ('https' === $this->scheme && 443 !== $this->port)) { + if (($this->scheme === 'http' && $this->port !== 80) || ($this->scheme === 'https' && $this->port !== 443)) { $url .= ':' . $this->port; } - if (null !== $this->path) { + if ($this->path !== null) { $url .= $this->path; } diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 1fee7d01c..fc6ff3253 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -132,7 +132,7 @@ private function __construct() */ public static function registerOnceErrorHandler(): self { - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -145,7 +145,7 @@ public static function registerOnceErrorHandler(): self self::$handlerInstance->isErrorHandlerRegistered = true; self::$handlerInstance->previousErrorHandler = set_error_handler($errorHandlerCallback); - if (null === self::$handlerInstance->previousErrorHandler) { + if (self::$handlerInstance->previousErrorHandler === null) { restore_error_handler(); // Specifying the error types caught by the error handler with the @@ -172,7 +172,7 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s throw new \InvalidArgumentException('The $reservedMemorySize argument must be greater than 0.'); } - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -195,7 +195,7 @@ public static function registerOnceFatalErrorHandler(int $reservedMemorySize = s */ public static function registerOnceExceptionHandler(): self { - if (null === self::$handlerInstance) { + if (self::$handlerInstance === null) { self::$handlerInstance = new self(); } @@ -272,7 +272,7 @@ public function addExceptionHandlerListener(callable $listener): void */ private function handleError(int $level, string $message, string $file, int $line, ?array $errcontext = []): bool { - $isSilencedError = 0 === error_reporting(); + $isSilencedError = error_reporting() === 0; if (\PHP_MAJOR_VERSION >= 8) { // Starting from PHP8, when a silenced error occurs the `error_reporting()` @@ -301,7 +301,7 @@ private function handleError(int $level, string $message, string $file, int $lin $this->invokeListeners($this->errorListeners, $errorAsException); - if (null !== $this->previousErrorHandler) { + if ($this->previousErrorHandler !== null) { return false !== ($this->previousErrorHandler)($level, $message, $file, $line, $errcontext); } @@ -317,7 +317,7 @@ private function handleFatalError(): void { // If there is not enough memory that can be used to handle the error // do nothing - if (null === self::$reservedMemory) { + if (self::$reservedMemory === null) { return; } @@ -353,7 +353,7 @@ private function handleException(\Throwable $exception): void $this->previousExceptionHandler = null; try { - if (null !== $previousExceptionHandler) { + if ($previousExceptionHandler !== null) { $previousExceptionHandler($exception); return; diff --git a/src/Event.php b/src/Event.php index 215754309..b9fd06e27 100644 --- a/src/Event.php +++ b/src/Event.php @@ -200,7 +200,7 @@ public static function createEvent(?EventId $eventId = null): self * * @param EventId|null $eventId The ID of the event */ - public static function createTransaction(EventId $eventId = null): self + public static function createTransaction(?EventId $eventId = null): self { return new self($eventId, EventType::transaction()); } @@ -233,9 +233,11 @@ public function getSdkIdentifier(): string * * @internal */ - public function setSdkIdentifier(string $sdkIdentifier): void + public function setSdkIdentifier(string $sdkIdentifier): self { $this->sdkIdentifier = $sdkIdentifier; + + return $this; } /** @@ -253,15 +255,15 @@ public function getSdkVersion(): string * * @internal */ - public function setSdkVersion(string $sdkVersion): void + public function setSdkVersion(string $sdkVersion): self { $this->sdkVersion = $sdkVersion; + + return $this; } /** * Gets the timestamp of when this event was generated. - * - * @return float */ public function getTimestamp(): ?float { @@ -271,9 +273,11 @@ public function getTimestamp(): ?float /** * Sets the timestamp of when the Event was created. */ - public function setTimestamp(?float $timestamp): void + public function setTimestamp(?float $timestamp): self { $this->timestamp = $timestamp; + + return $this; } /** @@ -289,9 +293,11 @@ public function getLevel(): ?Severity * * @param Severity|null $level The severity */ - public function setLevel(?Severity $level): void + public function setLevel(?Severity $level): self { $this->level = $level; + + return $this; } /** @@ -307,9 +313,11 @@ public function getLogger(): ?string * * @param string|null $logger The logger name */ - public function setLogger(?string $logger): void + public function setLogger(?string $logger): self { $this->logger = $logger; + + return $this; } /** @@ -327,14 +335,11 @@ public function getTransaction(): ?string * * @param string|null $transaction The transaction name */ - public function setTransaction(?string $transaction): void + public function setTransaction(?string $transaction): self { $this->transaction = $transaction; - } - public function setCheckIn(?CheckIn $checkIn): void - { - $this->checkIn = $checkIn; + return $this; } public function getCheckIn(): ?CheckIn @@ -342,6 +347,13 @@ public function getCheckIn(): ?CheckIn return $this->checkIn; } + public function setCheckIn(?CheckIn $checkIn): self + { + $this->checkIn = $checkIn; + + return $this; + } + /** * Gets the name of the server. */ @@ -355,9 +367,11 @@ public function getServerName(): ?string * * @param string|null $serverName The server name */ - public function setServerName(?string $serverName): void + public function setServerName(?string $serverName): self { $this->serverName = $serverName; + + return $this; } /** @@ -373,9 +387,11 @@ public function getRelease(): ?string * * @param string|null $release The release */ - public function setRelease(?string $release): void + public function setRelease(?string $release): self { $this->release = $release; + + return $this; } /** @@ -411,11 +427,13 @@ public function getMessageParams(): array * @param string[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ - public function setMessage(string $message, array $params = [], ?string $formatted = null): void + public function setMessage(string $message, array $params = [], ?string $formatted = null): self { $this->message = $message; $this->messageParams = $params; $this->messageFormatted = $formatted; + + return $this; } /** @@ -433,9 +451,11 @@ public function getModules(): array * * @param array $modules */ - public function setModules(array $modules): void + public function setModules(array $modules): self { $this->modules = $modules; + + return $this; } /** @@ -453,9 +473,11 @@ public function getRequest(): array * * @param array $request The request data */ - public function setRequest(array $request): void + public function setRequest(array $request): self { $this->request = $request; + + return $this; } /** @@ -498,9 +520,11 @@ public function getExtra(): array * * @param array $extra The context object */ - public function setExtra(array $extra): void + public function setExtra(array $extra): self { $this->extra = $extra; + + return $this; } /** @@ -518,9 +542,11 @@ public function getTags(): array * * @param array $tags The tags to set */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $this->tags = $tags; + + return $this; } /** @@ -529,9 +555,11 @@ public function setTags(array $tags): void * @param string $key The key that uniquely identifies the tag * @param string $value The value */ - public function setTag(string $key, string $value): void + public function setTag(string $key, string $value): self { $this->tags[$key] = $value; + + return $this; } /** @@ -539,9 +567,11 @@ public function setTag(string $key, string $value): void * * @param string $key The key that uniquely identifies the tag */ - public function removeTag(string $key): void + public function removeTag(string $key): self { unset($this->tags[$key]); + + return $this; } /** @@ -557,9 +587,11 @@ public function getUser(): ?UserDataBag * * @param UserDataBag|null $user The context object */ - public function setUser(?UserDataBag $user): void + public function setUser(?UserDataBag $user): self { $this->user = $user; + + return $this; } /** @@ -575,9 +607,11 @@ public function getOsContext(): ?OsContext * * @param OsContext|null $osContext The context object */ - public function setOsContext(?OsContext $osContext): void + public function setOsContext(?OsContext $osContext): self { $this->osContext = $osContext; + + return $this; } /** @@ -593,9 +627,11 @@ public function getRuntimeContext(): ?RuntimeContext * * @param RuntimeContext|null $runtimeContext The context object */ - public function setRuntimeContext(?RuntimeContext $runtimeContext): void + public function setRuntimeContext(?RuntimeContext $runtimeContext): self { $this->runtimeContext = $runtimeContext; + + return $this; } /** @@ -615,9 +651,11 @@ public function getFingerprint(): array * * @param string[] $fingerprint The strings */ - public function setFingerprint(array $fingerprint): void + public function setFingerprint(array $fingerprint): self { $this->fingerprint = $fingerprint; + + return $this; } /** @@ -633,9 +671,11 @@ public function getEnvironment(): ?string * * @param string|null $environment The name of the environment */ - public function setEnvironment(?string $environment): void + public function setEnvironment(?string $environment): self { $this->environment = $environment; + + return $this; } /** @@ -653,9 +693,11 @@ public function getBreadcrumbs(): array * * @param Breadcrumb[] $breadcrumbs The breadcrumb array */ - public function setBreadcrumb(array $breadcrumbs): void + public function setBreadcrumb(array $breadcrumbs): self { $this->breadcrumbs = $breadcrumbs; + + return $this; } /** @@ -673,7 +715,7 @@ public function getExceptions(): array * * @param ExceptionDataBag[] $exceptions The exceptions */ - public function setExceptions(array $exceptions): void + public function setExceptions(array $exceptions): self { foreach ($exceptions as $exception) { if (!$exception instanceof ExceptionDataBag) { @@ -682,6 +724,8 @@ public function setExceptions(array $exceptions): void } $this->exceptions = $exceptions; + + return $this; } /** @@ -697,9 +741,11 @@ public function getStacktrace(): ?Stacktrace * * @param Stacktrace|null $stacktrace The stacktrace instance */ - public function setStacktrace(?Stacktrace $stacktrace): void + public function setStacktrace(?Stacktrace $stacktrace): self { $this->stacktrace = $stacktrace; + + return $this; } public function getType(): EventType @@ -713,9 +759,11 @@ public function getType(): EventType * @param string $name The name that uniquely identifies the SDK metadata * @param mixed $data The data of the SDK metadata */ - public function setSdkMetadata(string $name, $data): void + public function setSdkMetadata(string $name, $data): self { $this->sdkMetadata[$name] = $data; + + return $this; } /** @@ -731,7 +779,7 @@ public function setSdkMetadata(string $name, $data): void */ public function getSdkMetadata(?string $name = null) { - if (null !== $name) { + if ($name !== null) { return $this->sdkMetadata[$name] ?? null; } @@ -751,9 +799,11 @@ public function getStartTimestamp(): ?float * * @param float|null $startTimestamp The start time of the measurement */ - public function setStartTimestamp(?float $startTimestamp): void + public function setStartTimestamp(?float $startTimestamp): self { $this->startTimestamp = $startTimestamp; + + return $this; } /** @@ -771,14 +821,11 @@ public function getSpans(): array * * @param Span[] $spans The list of spans */ - public function setSpans(array $spans): void + public function setSpans(array $spans): self { $this->spans = $spans; - } - public function setProfile(?Profile $profile): void - { - $this->profile = $profile; + return $this; } public function getProfile(): ?Profile @@ -786,6 +833,13 @@ public function getProfile(): ?Profile return $this->profile; } + public function setProfile(?Profile $profile): self + { + $this->profile = $profile; + + return $this; + } + public function getTraceId(): ?string { $traceId = $this->getContexts()['trace']['trace_id']; diff --git a/src/EventHint.php b/src/EventHint.php index ed3b0b9f1..ceff03315 100644 --- a/src/EventHint.php +++ b/src/EventHint.php @@ -55,15 +55,15 @@ public static function fromArray(array $hintData): self $stacktrace = $hintData['stacktrace'] ?? null; $extra = $hintData['extra'] ?? []; - if (null !== $exception && !$exception instanceof \Throwable) { + if ($exception !== null && !$exception instanceof \Throwable) { throw new \InvalidArgumentException(sprintf('The value of the "exception" field must be an instance of a class implementing the "%s" interface. Got: "%s".', \Throwable::class, get_debug_type($exception))); } - if (null !== $mechanism && !$mechanism instanceof ExceptionMechanism) { + if ($mechanism !== null && !$mechanism instanceof ExceptionMechanism) { throw new \InvalidArgumentException(sprintf('The value of the "mechanism" field must be an instance of the "%s" class. Got: "%s".', ExceptionMechanism::class, get_debug_type($mechanism))); } - if (null !== $stacktrace && !$stacktrace instanceof Stacktrace) { + if ($stacktrace !== null && !$stacktrace instanceof Stacktrace) { throw new \InvalidArgumentException(sprintf('The value of the "stacktrace" field must be an instance of the "%s" class. Got: "%s".', Stacktrace::class, get_debug_type($stacktrace))); } diff --git a/src/EventType.php b/src/EventType.php index c4e79c4d0..beb04930f 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -27,22 +27,11 @@ private function __construct(string $value) $this->value = $value; } - /** - * Creates an instance of this enum for the "default" value. - */ - public static function default(): self - { - return self::getInstance('default'); - } - public static function event(): self { return self::getInstance('event'); } - /** - * Creates an instance of this enum for the "transaction" value. - */ public static function transaction(): self { return self::getInstance('transaction'); diff --git a/src/Exception/EventCreationException.php b/src/Exception/EventCreationException.php index 94dae2a2c..666515193 100644 --- a/src/Exception/EventCreationException.php +++ b/src/Exception/EventCreationException.php @@ -7,7 +7,7 @@ /** * This exception is thrown when an issue is preventing the creation of an {@see Event}. */ -class EventCreationException extends \RuntimeException implements ExceptionInterface +class EventCreationException extends \RuntimeException { /** * EventCreationException constructor. diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php deleted file mode 100644 index d4be5c56d..000000000 --- a/src/Exception/ExceptionInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * @deprecated since version 3.1, to be removed in 4.0 - */ -interface ExceptionInterface -{ -} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php deleted file mode 100644 index 745f0e82b..000000000 --- a/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * @deprecated since version 3.1, to be removed in 4.0 - */ -class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface -{ -} diff --git a/src/ExceptionDataBag.php b/src/ExceptionDataBag.php index e93b6c617..3bdd36d4d 100644 --- a/src/ExceptionDataBag.php +++ b/src/ExceptionDataBag.php @@ -53,9 +53,11 @@ public function getType(): string * * @param string $type The exception type */ - public function setType(string $type): void + public function setType(string $type): self { $this->type = $type; + + return $this; } /** @@ -69,9 +71,11 @@ public function getValue(): string /** * Sets the value of the exception. */ - public function setValue(string $value): void + public function setValue(string $value): self { $this->value = $value; + + return $this; } /** @@ -87,9 +91,11 @@ public function getStacktrace(): ?Stacktrace * * @param Stacktrace $stacktrace The stacktrace */ - public function setStacktrace(Stacktrace $stacktrace): void + public function setStacktrace(Stacktrace $stacktrace): self { $this->stacktrace = $stacktrace; + + return $this; } /** @@ -105,8 +111,10 @@ public function getMechanism(): ?ExceptionMechanism * * @param ExceptionMechanism|null $mechanism The mechanism that created this exception */ - public function setMechanism(?ExceptionMechanism $mechanism): void + public function setMechanism(?ExceptionMechanism $mechanism): self { $this->mechanism = $mechanism; + + return $this; } } diff --git a/src/ExceptionMechanism.php b/src/ExceptionMechanism.php index 9a3cf23ee..fed2081b1 100644 --- a/src/ExceptionMechanism.php +++ b/src/ExceptionMechanism.php @@ -83,8 +83,10 @@ public function getData(): array * * @param array $data */ - public function setData(array $data): void + public function setData(array $data): self { $this->data = $data; + + return $this; } } diff --git a/src/Frame.php b/src/Frame.php index 4d6a067af..fa5b3e169 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -153,9 +153,11 @@ public function getPreContext(): array * * @param string[] $preContext The source code lines */ - public function setPreContext(array $preContext): void + public function setPreContext(array $preContext): self { $this->preContext = $preContext; + + return $this; } /** @@ -173,9 +175,11 @@ public function getContextLine(): ?string * * @param string|null $contextLine The source code line */ - public function setContextLine(?string $contextLine): void + public function setContextLine(?string $contextLine): self { $this->contextLine = $contextLine; + + return $this; } /** @@ -193,9 +197,11 @@ public function getPostContext(): array * * @param string[] $postContext The source code lines */ - public function setPostContext(array $postContext): void + public function setPostContext(array $postContext): self { $this->postContext = $postContext; + + return $this; } /** @@ -213,9 +219,11 @@ public function isInApp(): bool * * @param bool $inApp flag indicating whether the frame is application-related */ - public function setIsInApp(bool $inApp): void + public function setIsInApp(bool $inApp): self { $this->inApp = $inApp; + + return $this; } /** @@ -235,9 +243,11 @@ public function getVars(): array * * @param array $vars The variables */ - public function setVars(array $vars): void + public function setVars(array $vars): self { $this->vars = $vars; + + return $this; } /** @@ -245,6 +255,6 @@ public function setVars(array $vars): void */ public function isInternal(): bool { - return self::INTERNAL_FRAME_FILENAME === $this->file; + return $this->file === self::INTERNAL_FRAME_FILENAME; } } diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 5cd61bae6..143252d57 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -74,7 +74,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) { $functionName = $backtraceFrame['class']; - if (str_starts_with($functionName, Frame::ANONYMOUS_CLASS_PREFIX)) { + if (mb_substr($functionName, 0, mb_strlen(Frame::ANONYMOUS_CLASS_PREFIX)) === Frame::ANONYMOUS_CLASS_PREFIX) { $functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath($this->options, substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX))); } @@ -89,7 +89,7 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac $strippedFilePath, $line, $rawFunctionName, - Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null, + $file !== Frame::INTERNAL_FRAME_FILENAME ? $file : null, $this->getFunctionArguments($backtraceFrame), $this->isFrameInApp($file, $functionName) ); @@ -103,11 +103,11 @@ public function buildFromBacktraceFrame(string $file, int $line, array $backtrac */ private function isFrameInApp(string $file, ?string $functionName): bool { - if (Frame::INTERNAL_FRAME_FILENAME === $file) { + if ($file === Frame::INTERNAL_FRAME_FILENAME) { return false; } - if (null !== $functionName && str_starts_with($functionName, 'Sentry\\')) { + if ($functionName !== null && substr($functionName, 0, \strlen('Sentry\\')) === 'Sentry\\') { return false; } @@ -117,7 +117,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool $isInApp = true; foreach ($excludedAppPaths as $excludedAppPath) { - if (str_starts_with($absoluteFilePath, $excludedAppPath)) { + if (mb_substr($absoluteFilePath, 0, mb_strlen($excludedAppPath)) === $excludedAppPath) { $isInApp = false; break; @@ -125,7 +125,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool } foreach ($includedAppPaths as $includedAppPath) { - if (str_starts_with($absoluteFilePath, $includedAppPath)) { + if (mb_substr($absoluteFilePath, 0, mb_strlen($includedAppPath)) === $includedAppPath) { $isInApp = true; break; @@ -156,7 +156,7 @@ private function getFunctionArguments(array $backtraceFrame): array if (isset($backtraceFrame['class'])) { if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']); - } elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) { + } elseif (isset($backtraceFrame['type']) && $backtraceFrame['type'] === '::') { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic'); } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); @@ -170,7 +170,7 @@ private function getFunctionArguments(array $backtraceFrame): array $argumentValues = []; - if (null !== $reflectionFunction) { + if ($reflectionFunction !== null) { $argumentValues = $this->getFunctionArgumentValues($reflectionFunction, $backtraceFrame['args']); } else { foreach ($backtraceFrame['args'] as $parameterPosition => $parameterValue) { diff --git a/src/HttpClient/Authentication/SentryAuthentication.php b/src/HttpClient/Authentication/SentryAuthentication.php deleted file mode 100644 index 917487e83..000000000 --- a/src/HttpClient/Authentication/SentryAuthentication.php +++ /dev/null @@ -1,78 +0,0 @@ - - */ -final class SentryAuthentication implements AuthenticationInterface -{ - /** - * @var Options The Sentry client configuration - */ - private $options; - - /** - * @var string The SDK identifier - */ - private $sdkIdentifier; - - /** - * @var string The SDK version - */ - private $sdkVersion; - - /** - * Constructor. - * - * @param Options $options The Sentry client configuration - * @param string $sdkIdentifier The Sentry SDK identifier in use - * @param string $sdkVersion The Sentry SDK version in use - */ - public function __construct(Options $options, string $sdkIdentifier, string $sdkVersion) - { - $this->options = $options; - $this->sdkIdentifier = $sdkIdentifier; - $this->sdkVersion = $sdkVersion; - } - - /** - * {@inheritdoc} - */ - public function authenticate(RequestInterface $request): RequestInterface - { - $dsn = $this->options->getDsn(); - - if (null === $dsn) { - return $request; - } - - $data = [ - 'sentry_version' => Client::PROTOCOL_VERSION, - 'sentry_client' => $this->sdkIdentifier . '/' . $this->sdkVersion, - 'sentry_key' => $dsn->getPublicKey(), - ]; - - if (null !== $dsn->getSecretKey()) { - $data['sentry_secret'] = $dsn->getSecretKey(); - } - - $headers = []; - - foreach ($data as $headerKey => $headerValue) { - $headers[] = $headerKey . '=' . $headerValue; - } - - return $request->withHeader('X-Sentry-Auth', 'Sentry ' . implode(', ', $headers)); - } -} diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php new file mode 100644 index 000000000..96a3dd01a --- /dev/null +++ b/src/HttpClient/HttpClient.php @@ -0,0 +1,106 @@ +sdkIdentifier = $sdkIdentifier; + $this->sdkVersion = $sdkVersion; + } + + public function sendRequest(Request $request, Options $options): Response + { + $dsn = $options->getDsn(); + if ($dsn === null) { + throw new \RuntimeException('The DSN option must be set to use the HttpClient.'); + } + + $requestData = $request->getStringBody(); + if ($requestData === null) { + throw new \RuntimeException('The request data is empty.'); + } + + $curlHandle = curl_init(); + + $requestHeaders = Http::getRequestHeaders($dsn, $this->sdkIdentifier, $this->sdkVersion); + + if ( + \extension_loaded('zlib') + && $options->isHttpCompressionEnabled() + ) { + $requestData = gzcompress($requestData, -1, \ZLIB_ENCODING_GZIP); + $requestHeaders[] = 'Content-Encoding: gzip'; + } + + $responseHeaders = []; + $responseHeaderCallback = function ($curlHandle, $headerLine) use (&$responseHeaders): int { + return Http::parseResponseHeaders($headerLine, $responseHeaders); + }; + + curl_setopt($curlHandle, \CURLOPT_URL, $dsn->getEnvelopeApiEndpointUrl()); + curl_setopt($curlHandle, \CURLOPT_HTTPHEADER, $requestHeaders); + curl_setopt($curlHandle, \CURLOPT_USERAGENT, $this->sdkIdentifier . '/' . $this->sdkVersion); + curl_setopt($curlHandle, \CURLOPT_TIMEOUT, $options->getHttpTimeout()); + curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, $options->getHttpConnectTimeout()); + curl_setopt($curlHandle, \CURLOPT_ENCODING, ''); + curl_setopt($curlHandle, \CURLOPT_POST, true); + curl_setopt($curlHandle, \CURLOPT_POSTFIELDS, $requestData); + curl_setopt($curlHandle, \CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlHandle, \CURLOPT_HEADERFUNCTION, $responseHeaderCallback); + curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); + + $httpSslVerifyPeer = $options->getHttpSslVerifyPeer(); + if ($httpSslVerifyPeer) { + curl_setopt($curlHandle, \CURLOPT_SSL_VERIFYPEER, true); + } + + $httpProxy = $options->getHttpProxy(); + if ($httpProxy !== null) { + curl_setopt($curlHandle, \CURLOPT_PROXY, $httpProxy); + curl_setopt($curlHandle, \CURLOPT_HEADEROPT, \CURLHEADER_SEPARATE); + } + + $httpProxyAuthentication = $options->getHttpProxyAuthentication(); + if ($httpProxyAuthentication !== null) { + curl_setopt($curlHandle, \CURLOPT_PROXYUSERPWD, $httpProxyAuthentication); + } + + $body = curl_exec($curlHandle); + + if ($body === false) { + $errorCode = curl_errno($curlHandle); + $error = curl_error($curlHandle); + curl_close($curlHandle); + + $message = 'cURL Error (' . $errorCode . ') ' . $error; + + return new Response(0, [], $message); + } + + $statusCode = curl_getinfo($curlHandle, \CURLINFO_HTTP_CODE); + + curl_close($curlHandle); + + return new Response($statusCode, $responseHeaders, ''); + } +} diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php deleted file mode 100644 index ba12123a4..000000000 --- a/src/HttpClient/HttpClientFactory.php +++ /dev/null @@ -1,165 +0,0 @@ -streamFactory = $streamFactory; - $this->httpClient = $httpClient; - $this->sdkIdentifier = $sdkIdentifier; - $this->sdkVersion = $sdkVersion; - } - - /** - * {@inheritdoc} - */ - public function create(Options $options): HttpAsyncClientInterface - { - if (null === $options->getDsn()) { - throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); - } - - if (null !== $this->httpClient && null !== $options->getHttpProxy()) { - throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); - } - - $httpClient = $this->httpClient ?? $this->resolveClient($options); - - $httpClientPlugins = [ - new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), - new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), - new RetryPlugin(['retries' => $options->getSendAttempts(false)]), - new ErrorPlugin(['only_server_exception' => true]), - ]; - - if ($options->isCompressionEnabled()) { - $httpClientPlugins[] = new GzipEncoderPlugin($this->streamFactory); - $httpClientPlugins[] = new DecoderPlugin(); - } - - return new PluginClient($httpClient, $httpClientPlugins); - } - - /** - * @return ClientInterface|HttpAsyncClientInterface - */ - private function resolveClient(Options $options) - { - if (class_exists(SymfonyHttplugClient::class)) { - $symfonyConfig = [ - 'timeout' => $options->getHttpConnectTimeout(), - 'max_duration' => $options->getHttpTimeout(), - 'http_version' => $options->isCompressionEnabled() ? '1.1' : null, - ]; - - if (null !== $options->getHttpProxy()) { - $symfonyConfig['proxy'] = $options->getHttpProxy(); - } - - return new SymfonyHttplugClient(SymfonyHttpClient::create($symfonyConfig)); - } - - if (class_exists(Guzzle7HttpClient::class) || class_exists(Guzzle6HttpClient::class)) { - $guzzleConfig = [ - GuzzleHttpClientOptions::TIMEOUT => $options->getHttpTimeout(), - GuzzleHttpClientOptions::CONNECT_TIMEOUT => $options->getHttpConnectTimeout(), - ]; - - if (null !== $options->getHttpProxy()) { - $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); - } - - if (class_exists(Guzzle7HttpClient::class)) { - return Guzzle7HttpClient::createWithConfig($guzzleConfig); - } - - return Guzzle6HttpClient::createWithConfig($guzzleConfig); - } - - if (class_exists(CurlHttpClient::class)) { - $curlConfig = [ - \CURLOPT_TIMEOUT => $options->getHttpTimeout(), - \CURLOPT_HTTP_VERSION => $options->isCompressionEnabled() ? \CURL_HTTP_VERSION_1_1 : \CURL_HTTP_VERSION_NONE, - \CURLOPT_CONNECTTIMEOUT => $options->getHttpConnectTimeout(), - ]; - - if (null !== $options->getHttpProxy()) { - $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); - } - - return new CurlHttpClient(null, null, $curlConfig); - } - - if (null !== $options->getHttpProxy()) { - throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client", the "symfony/http-client" or the "php-http/guzzle6-adapter" package to be installed.'); - } - - return HttpAsyncClientDiscovery::find(); - } -} diff --git a/src/HttpClient/HttpClientFactoryInterface.php b/src/HttpClient/HttpClientFactoryInterface.php deleted file mode 100644 index a0b409d70..000000000 --- a/src/HttpClient/HttpClientFactoryInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ -final class GzipEncoderPlugin implements PluginInterface -{ - /** - * @var StreamFactoryInterface The PSR-17 stream factory - */ - private $streamFactory; - - /** - * Constructor. - * - * @param StreamFactoryInterface $streamFactory The stream factory - * - * @throws \RuntimeException If the zlib extension is not enabled - */ - public function __construct(StreamFactoryInterface $streamFactory) - { - if (!\extension_loaded('zlib')) { - throw new \RuntimeException('The "zlib" extension must be enabled to use this plugin.'); - } - - $this->streamFactory = $streamFactory; - } - - /** - * {@inheritdoc} - */ - public function handleRequest(RequestInterface $request, callable $next, callable $first): PromiseInterface - { - $requestBody = $request->getBody(); - - if ($requestBody->isSeekable()) { - $requestBody->rewind(); - } - - // Instead of using a stream filter we have to compress the whole request - // body in one go to work around a PHP bug. See https://github.com/getsentry/sentry-php/pull/877 - $encodedBody = gzcompress($requestBody->getContents(), -1, \ZLIB_ENCODING_GZIP); - - if (false === $encodedBody) { - throw new \RuntimeException('Failed to GZIP-encode the request body.'); - } - - $request = $request->withHeader('Content-Encoding', 'gzip'); - $request = $request->withBody($this->streamFactory->createStream($encodedBody)); - - return $next($request); - } -} diff --git a/src/HttpClient/Request.php b/src/HttpClient/Request.php new file mode 100644 index 000000000..91627ddf6 --- /dev/null +++ b/src/HttpClient/Request.php @@ -0,0 +1,26 @@ +stringBody; + } + + public function setStringBody(string $stringBody): void + { + $this->stringBody = $stringBody; + } +} diff --git a/src/HttpClient/Response.php b/src/HttpClient/Response.php new file mode 100644 index 000000000..76748271a --- /dev/null +++ b/src/HttpClient/Response.php @@ -0,0 +1,94 @@ +statusCode = $statusCode; + $this->headers = $headers; + $this->error = $error; + + foreach ($headers as $name => $value) { + $this->headerNames[strtolower($name)] = $name; + } + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function isSuccess(): bool + { + return $this->statusCode >= 200 && $this->statusCode <= 299; + } + + public function hasHeader(string $name): bool + { + return isset($this->headerNames[strtolower($name)]); + } + + /** + * @return string[] + */ + public function getHeader(string $header): array + { + if (!$this->hasHeader($header)) { + return []; + } + + $header = $this->headerNames[strtolower($header)]; + + return $this->headers[$header]; + } + + public function getHeaderLine(string $name): string + { + $value = $this->getHeader($name); + if (empty($value)) { + return ''; + } + + return implode(',', $value); + } + + public function getError(): string + { + return $this->error; + } + + public function hasError(): bool + { + return $this->error !== ''; + } +} diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php index 530dc86af..a8894a7e2 100644 --- a/src/Integration/AbstractErrorListenerIntegration.php +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -38,7 +38,7 @@ protected function addExceptionMechanismToEvent(Event $event): Event foreach ($exceptions as $exception) { $data = []; $mechanism = $exception->getMechanism(); - if (null !== $mechanism) { + if ($mechanism !== null) { $data = $mechanism->getData(); } diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index 9552fafc6..fb0f591e3 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -26,7 +26,7 @@ public function setupOnce(): void Scope::addGlobalEventProcessor(static function (Event $event): Event { $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); - if (null !== $integration) { + if ($integration !== null) { $event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext())); $event->setOsContext($integration->updateServerOsContext($event->getOsContext())); } @@ -37,11 +37,11 @@ public function setupOnce(): void private function updateRuntimeContext(?RuntimeContext $runtimeContext): RuntimeContext { - if (null === $runtimeContext) { + if ($runtimeContext === null) { $runtimeContext = new RuntimeContext('php'); } - if (null === $runtimeContext->getVersion()) { + if ($runtimeContext->getVersion() === null) { $runtimeContext->setVersion(PHPVersion::parseVersion()); } @@ -54,23 +54,23 @@ private function updateServerOsContext(?OsContext $osContext): ?OsContext return $osContext; } - if (null === $osContext) { + if ($osContext === null) { $osContext = new OsContext(php_uname('s')); } - if (null === $osContext->getVersion()) { + if ($osContext->getVersion() === null) { $osContext->setVersion(php_uname('r')); } - if (null === $osContext->getBuild()) { + if ($osContext->getBuild() === null) { $osContext->setBuild(php_uname('v')); } - if (null === $osContext->getKernelVersion()) { + if ($osContext->getKernelVersion() === null) { $osContext->setKernelVersion(php_uname('a')); } - if (null === $osContext->getMachineType()) { + if ($osContext->getMachineType() === null) { $osContext->setMachineType(php_uname('m')); } diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index 1d7539e27..b56d1c00e 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -27,7 +27,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return; } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 4c3d2c386..18e7afc75 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -25,7 +25,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration) { + if ($integration === null) { return; } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index 5c72341e3..688103cd4 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -28,7 +28,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return; } diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 31104b102..d0ed04f99 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -42,25 +42,25 @@ public function setupOnce(): void Scope::addGlobalEventProcessor(static function (Event $event): Event { $client = SentrySdk::getCurrentHub()->getClient(); - if (null === $client) { + if ($client === null) { return $event; } $maxContextLines = $client->getOptions()->getContextLines(); $integration = $client->getIntegration(self::class); - if (null === $integration || null === $maxContextLines) { + if ($integration === null || $maxContextLines === null) { return $event; } $stacktrace = $event->getStacktrace(); - if (null !== $stacktrace) { + if ($stacktrace !== null) { $integration->addContextToStacktraceFrames($maxContextLines, $stacktrace); } foreach ($event->getExceptions() as $exception) { - if (null !== $exception->getStacktrace()) { + if ($exception->getStacktrace() !== null) { $integration->addContextToStacktraceFrames($maxContextLines, $exception->getStacktrace()); } } @@ -78,7 +78,7 @@ public function setupOnce(): void private function addContextToStacktraceFrames(int $maxContextLines, Stacktrace $stacktrace): void { foreach ($stacktrace->getFrames() as $frame) { - if ($frame->isInternal() || null === $frame->getAbsoluteFilePath()) { + if ($frame->isInternal() || $frame->getAbsoluteFilePath() === null) { continue; } @@ -113,7 +113,7 @@ private function getSourceCodeExcerpt(int $maxContextLines, string $filePath, in 'post_context' => [], ]; - $target = max(0, ($lineNumber - ($maxContextLines + 1))); + $target = max(0, $lineNumber - ($maxContextLines + 1)); $currentLineNumber = $target + 1; try { diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php deleted file mode 100644 index 7264be103..000000000 --- a/src/Integration/IgnoreErrorsIntegration.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * @psalm-type IntegrationOptions array{ - * ignore_exceptions: list>, - * ignore_tags: array - * } - */ -final class IgnoreErrorsIntegration implements IntegrationInterface -{ - /** - * @var array The options - * - * @psalm-var IntegrationOptions - */ - private $options; - - /** - * Creates a new instance of this integration and configures it with the - * given options. - * - * @param array $options The options - * - * @psalm-param array{ - * ignore_exceptions?: list>, - * ignore_tags?: array - * } $options - */ - public function __construct(array $options = []) - { - $resolver = new OptionsResolver(); - $resolver->setDefaults([ - 'ignore_exceptions' => [], - 'ignore_tags' => [], - ]); - - $resolver->setAllowedTypes('ignore_exceptions', ['array']); - $resolver->setAllowedTypes('ignore_tags', ['array']); - - $this->options = $resolver->resolve($options); - } - - /** - * {@inheritdoc} - */ - public function setupOnce(): void - { - Scope::addGlobalEventProcessor(static function (Event $event): ?Event { - $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); - - if (null !== $integration && $integration->shouldDropEvent($event, $integration->options)) { - return null; - } - - return $event; - }); - } - - /** - * Checks whether the given event should be dropped according to the options - * that configures the current instance of this integration. - * - * @param Event $event The event to check - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function shouldDropEvent(Event $event, array $options): bool - { - if ($this->isIgnoredException($event, $options)) { - return true; - } - - if ($this->isIgnoredTag($event, $options)) { - return true; - } - - return false; - } - - /** - * Checks whether the given event should be dropped or not according to the - * criteria defined in the integration's options. - * - * @param Event $event The event instance - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function isIgnoredException(Event $event, array $options): bool - { - $exceptions = $event->getExceptions(); - - if (empty($exceptions)) { - return false; - } - - foreach ($options['ignore_exceptions'] as $ignoredException) { - if (is_a($exceptions[0]->getType(), $ignoredException, true)) { - return true; - } - } - - return false; - } - - /** - * Checks whether the given event should be dropped or not according to the - * criteria defined in the integration's options. - * - * @param Event $event The event instance - * @param array $options The options of the integration - * - * @psalm-param IntegrationOptions $options - */ - private function isIgnoredTag(Event $event, array $options): bool - { - $tags = $event->getTags(); - - if (empty($tags)) { - return false; - } - - foreach ($options['ignore_tags'] as $key => $value) { - if (isset($tags[$key]) && $tags[$key] === $value) { - return true; - } - } - - return false; - } -} diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 2c33c83ad..e7209f4c7 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -32,7 +32,7 @@ private function __construct() */ public static function getInstance(): self { - if (null === self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } @@ -134,7 +134,7 @@ private function getDefaultIntegrations(Options $options): array new ModulesIntegration(), ]; - if (null !== $options->getDsn()) { + if ($options->getDsn() !== null) { array_unshift($integrations, new ExceptionListenerIntegration(), new ErrorListenerIntegration(), new FatalErrorListenerIntegration()); } diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index dc47bd89a..860d90151 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -32,7 +32,7 @@ public function setupOnce(): void // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out - if (null !== $integration) { + if ($integration !== null) { $event->setModules(self::getComposerPackages()); } diff --git a/src/Integration/RequestFetcher.php b/src/Integration/RequestFetcher.php index f15fef4b3..333205b5f 100644 --- a/src/Integration/RequestFetcher.php +++ b/src/Integration/RequestFetcher.php @@ -4,7 +4,7 @@ namespace Sentry\Integration; -use Http\Discovery\Psr17Factory; +use GuzzleHttp\Psr7\ServerRequest; use Psr\Http\Message\ServerRequestInterface; /** @@ -22,6 +22,6 @@ public function fetchRequest(): ?ServerRequestInterface return null; } - return (new Psr17Factory())->createServerRequestFromGlobals(); + return ServerRequest::fromGlobals(); } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 6c38d2058..4238b457c 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -41,11 +41,8 @@ final class RequestIntegration implements IntegrationInterface /** * This constant is a map of maximum allowed sizes for each value of the * `max_request_body_size` option. - * - * @deprecated The 'none' option is deprecated since version 3.10, to be removed in 4.0 */ private const MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP = [ - 'none' => 0, 'never' => 0, 'small' => self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH, 'medium' => self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH, @@ -110,7 +107,7 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration || null === $client) { + if ($integration === null || $client === null) { return $event; } @@ -124,7 +121,7 @@ private function processEvent(Event $event, Options $options): void { $request = $this->requestFetcher->fetchRequest(); - if (null === $request) { + if ($request === null) { return; } @@ -144,9 +141,9 @@ private function processEvent(Event $event, Options $options): void $user = $event->getUser(); $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; - if (null === $user) { + if ($user === null) { $user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']); - } elseif (null === $user->getIpAddress()) { + } elseif ($user->getIpAddress() === null) { $user->setIpAddress($serverParams['REMOTE_ADDR']); } @@ -226,9 +223,9 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re $requestBody = ''; $maxLength = self::MAX_REQUEST_BODY_SIZE_OPTION_TO_MAX_LENGTH_MAP[$maxRequestBodySize]; - if (0 < $maxLength) { + if ($maxLength > 0) { $stream = $request->getBody(); - while (0 < $maxLength && !$stream->eof()) { + while ($maxLength > 0 && !$stream->eof()) { if ('' === $buffer = $stream->read(min($maxLength, self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH))) { break; } @@ -237,7 +234,7 @@ private function captureRequestBody(Options $options, ServerRequestInterface $re } } - if ('application/json' === $request->getHeaderLine('Content-Type')) { + if ($request->getHeaderLine('Content-Type') === 'application/json') { try { return JSON::decode($requestBody); } catch (JsonException $exception) { @@ -283,15 +280,15 @@ private function isRequestBodySizeWithinReadBounds(int $requestBodySize, string return false; } - if ('none' === $maxRequestBodySize || 'never' === $maxRequestBodySize) { + if ($maxRequestBodySize === 'none' || $maxRequestBodySize === 'never') { return false; } - if ('small' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) { + if ($maxRequestBodySize === 'small' && $requestBodySize > self::REQUEST_BODY_SMALL_MAX_CONTENT_LENGTH) { return false; } - if ('medium' === $maxRequestBodySize && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) { + if ($maxRequestBodySize === 'medium' && $requestBodySize > self::REQUEST_BODY_MEDIUM_MAX_CONTENT_LENGTH) { return false; } diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index d7c775fce..1ed22d753 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -28,11 +28,11 @@ public function setupOnce(): void // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out - if (null === $integration) { + if ($integration === null) { return $event; } - if (null !== $event->getTransaction()) { + if ($event->getTransaction() !== null) { return $event; } diff --git a/src/Logger/DebugFileLogger.php b/src/Logger/DebugFileLogger.php new file mode 100644 index 000000000..da406c44f --- /dev/null +++ b/src/Logger/DebugFileLogger.php @@ -0,0 +1,29 @@ +filePath = $filePath; + } + + /** + * @param mixed $level + * @param mixed[] $context + */ + public function log($level, \Stringable|string $message, array $context = []): void + { + file_put_contents($this->filePath, sprintf("sentry/sentry: [%s] %s\n", $level, (string) $message), \FILE_APPEND); + } +} diff --git a/src/Logger/DebugStdOutLogger.php b/src/Logger/DebugStdOutLogger.php new file mode 100644 index 000000000..fc1212931 --- /dev/null +++ b/src/Logger/DebugStdOutLogger.php @@ -0,0 +1,19 @@ +getMonologContextData($record['context']); - if ([] !== $monologContextData) { + if ($monologContextData !== []) { $scope->setExtra('monolog.context', $monologContextData); } $monologExtraData = $this->getMonologExtraData($record['extra']); - if ([] !== $monologExtraData) { + if ($monologExtraData !== []) { $scope->setExtra('monolog.extra', $monologExtraData); } @@ -98,7 +98,7 @@ private function getMonologContextData(array $context): array foreach ($context as $key => $value) { // We skip the `exception` field because it goes in its own context - if (self::CONTEXT_EXCEPTION_KEY === $key) { + if ($key === self::CONTEXT_EXCEPTION_KEY) { continue; } diff --git a/src/Options.php b/src/Options.php index c78416252..87665ac90 100644 --- a/src/Options.php +++ b/src/Options.php @@ -4,8 +4,11 @@ namespace Sentry; +use Psr\Log\LoggerInterface; +use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Integration\IntegrationInterface; +use Sentry\Transport\TransportInterface; use Symfony\Component\OptionsResolver\Options as SymfonyOptions; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -57,41 +60,11 @@ public function __construct(array $options = []) $this->options = $this->resolver->resolve($options); - if (true === $this->options['enable_tracing'] && null === $this->options['traces_sample_rate']) { + if ($this->options['enable_tracing'] === true && $this->options['traces_sample_rate'] === null) { $this->options = array_merge($this->options, ['traces_sample_rate' => 1]); } } - /** - * Gets the number of attempts to resend an event that failed to be sent. - * - * @deprecated since version 3.5, to be removed in 4.0 - */ - public function getSendAttempts(/*bool $triggerDeprecation = true*/): int - { - if (0 === \func_num_args() || false !== func_get_arg(0)) { - @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - - return $this->options['send_attempts']; - } - - /** - * Sets the number of attempts to resend an event that failed to be sent. - * - * @param int $attemptsCount The number of attempts - * - * @deprecated since version 3.5, to be removed in 4.0 - */ - public function setSendAttempts(int $attemptsCount): void - { - @trigger_error(sprintf('Method %s() is deprecated since version 3.5 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - $options = array_merge($this->options, ['send_attempts' => $attemptsCount]); - - $this->options = $this->resolver->resolve($options); - } - /** * Gets the prefixes which should be stripped from filenames to create * relative paths. @@ -109,11 +82,13 @@ public function getPrefixes(): array * * @param string[] $prefixes The prefixes */ - public function setPrefixes(array $prefixes): void + public function setPrefixes(array $prefixes): self { $options = array_merge($this->options, ['prefixes' => $prefixes]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -131,11 +106,13 @@ public function getSampleRate(): float * * @param float $sampleRate The sampling factor */ - public function setSampleRate(float $sampleRate): void + public function setSampleRate(float $sampleRate): self { $options = array_merge($this->options, ['sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -153,11 +130,13 @@ public function getTracesSampleRate(): ?float * * @param bool|null $enableTracing Boolean if tracing should be enabled or not */ - public function setEnableTracing(?bool $enableTracing): void + public function setEnableTracing(?bool $enableTracing): self { $options = array_merge($this->options, ['enable_tracing' => $enableTracing]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -176,11 +155,13 @@ public function getEnableTracing(): ?bool * * @param ?float $sampleRate The sampling factor */ - public function setTracesSampleRate(?float $sampleRate): void + public function setTracesSampleRate(?float $sampleRate): self { $options = array_merge($this->options, ['traces_sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } public function getProfilesSampleRate(): ?float @@ -191,11 +172,13 @@ public function getProfilesSampleRate(): ?float return $value ?? null; } - public function setProfilesSampleRate(?float $sampleRate): void + public function setProfilesSampleRate(?float $sampleRate): self { $options = array_merge($this->options, ['profiles_sample_rate' => $sampleRate]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -205,11 +188,11 @@ public function setProfilesSampleRate(?float $sampleRate): void */ public function isTracingEnabled(): bool { - if (null !== $this->getEnableTracing() && false === $this->getEnableTracing()) { + if ($this->getEnableTracing() !== null && $this->getEnableTracing() === false) { return false; } - return null !== $this->getTracesSampleRate() || null !== $this->getTracesSampler(); + return $this->getTracesSampleRate() !== null || $this->getTracesSampler() !== null; } /** @@ -225,11 +208,13 @@ public function shouldAttachStacktrace(): bool * * @param bool $enable Flag indicating if the stacktrace will be attached to captureMessage calls */ - public function setAttachStacktrace(bool $enable): void + public function setAttachStacktrace(bool $enable): self { $options = array_merge($this->options, ['attach_stacktrace' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -245,31 +230,13 @@ public function getContextLines(): ?int * * @param int|null $contextLines The number of lines of code */ - public function setContextLines(?int $contextLines): void + public function setContextLines(?int $contextLines): self { $options = array_merge($this->options, ['context_lines' => $contextLines]); $this->options = $this->resolver->resolve($options); - } - - /** - * Returns whether the requests should be compressed using GZIP or not. - */ - public function isCompressionEnabled(): bool - { - return $this->options['enable_compression']; - } - - /** - * Sets whether the request should be compressed using JSON or not. - * - * @param bool $enabled Flag indicating whether the request should be compressed - */ - public function setEnableCompression(bool $enabled): void - { - $options = array_merge($this->options, ['enable_compression' => $enabled]); - $this->options = $this->resolver->resolve($options); + return $this; } /** @@ -285,11 +252,13 @@ public function getEnvironment(): ?string * * @param string|null $environment The environment */ - public function setEnvironment(?string $environment): void + public function setEnvironment(?string $environment): self { $options = array_merge($this->options, ['environment' => $environment]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -307,11 +276,13 @@ public function getInAppExcludedPaths(): array * * @param string[] $paths The list of paths */ - public function setInAppExcludedPaths(array $paths): void + public function setInAppExcludedPaths(array $paths): self { $options = array_merge($this->options, ['in_app_exclude' => $paths]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -329,47 +300,37 @@ public function getInAppIncludedPaths(): array * * @param string[] $paths The list of paths */ - public function setInAppIncludedPaths(array $paths): void + public function setInAppIncludedPaths(array $paths): self { $options = array_merge($this->options, ['in_app_include' => $paths]); $this->options = $this->resolver->resolve($options); + + return $this; } /** - * Gets the logger used by Sentry. - * - * @deprecated since version 3.2, to be removed in 4.0 + * Gets a PSR-3 compatible logger to log internal debug messages. */ - public function getLogger(/*bool $triggerDeprecation = true*/): string + public function getLogger(): ?LoggerInterface { - if (0 === \func_num_args() || false !== func_get_arg(0)) { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - return $this->options['logger']; } /** - * Sets the logger used by Sentry. - * - * @param string $logger The logger - * - * @deprecated since version 3.2, to be removed in 4.0 + * Sets a PSR-3 compatible logger to log internal debug messages. */ - public function setLogger(string $logger): void + public function setLogger(LoggerInterface $logger): self { - @trigger_error(sprintf('Method %s() is deprecated since version 3.2 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - $options = array_merge($this->options, ['logger' => $logger]); $this->options = $this->resolver->resolve($options); + + return $this; } /** * Gets the release tag to be passed with every event sent to Sentry. - * - * @return string */ public function getRelease(): ?string { @@ -381,11 +342,13 @@ public function getRelease(): ?string * * @param string|null $release The release */ - public function setRelease(?string $release): void + public function setRelease(?string $release): self { $options = array_merge($this->options, ['release' => $release]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -409,17 +372,21 @@ public function getServerName(): string * * @param string $serverName The server name */ - public function setServerName(string $serverName): void + public function setServerName(string $serverName): self { $options = array_merge($this->options, ['server_name' => $serverName]); $this->options = $this->resolver->resolve($options); + + return $this; } /** * Gets a list of exceptions to be ignored and not sent to Sentry. * * @return string[] + * + * @psalm-return list> */ public function getIgnoreExceptions(): array { @@ -431,11 +398,13 @@ public function getIgnoreExceptions(): array * * @param string[] $ignoreErrors The list of exceptions to be ignored */ - public function setIgnoreExceptions(array $ignoreErrors): void + public function setIgnoreExceptions(array $ignoreErrors): self { $options = array_merge($this->options, ['ignore_exceptions' => $ignoreErrors]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -453,11 +422,13 @@ public function getIgnoreTransactions(): array * * @param string[] $ignoreTransaction The list of transaction names to be ignored */ - public function setIgnoreTransactions(array $ignoreTransaction): void + public function setIgnoreTransactions(array $ignoreTransaction): self { $options = array_merge($this->options, ['ignore_transactions' => $ignoreTransaction]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -479,11 +450,13 @@ public function getBeforeSendCallback(): callable * * @psalm-param callable(Event, ?EventHint): ?Event $callback */ - public function setBeforeSendCallback(callable $callback): void + public function setBeforeSendCallback(callable $callback): self { $options = array_merge($this->options, ['before_send' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -505,11 +478,13 @@ public function getBeforeSendTransactionCallback(): callable * * @psalm-param callable(Event, ?EventHint): ?Event $callback */ - public function setBeforeSendTransactionCallback(callable $callback): void + public function setBeforeSendTransactionCallback(callable $callback): self { $options = array_merge($this->options, ['before_send_transaction' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -527,11 +502,13 @@ public function getTracePropagationTargets(): ?array * * @param string[] $tracePropagationTargets Trace propagation targets */ - public function setTracePropagationTargets(array $tracePropagationTargets): void + public function setTracePropagationTargets(array $tracePropagationTargets): self { $options = array_merge($this->options, ['trace_propagation_targets' => $tracePropagationTargets]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -549,11 +526,13 @@ public function getTags(): array * * @param array $tags A list of tags */ - public function setTags(array $tags): void + public function setTags(array $tags): self { $options = array_merge($this->options, ['tags' => $tags]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -569,11 +548,13 @@ public function getErrorTypes(): int * * @param int $errorTypes The bit mask */ - public function setErrorTypes(int $errorTypes): void + public function setErrorTypes(int $errorTypes): self { $options = array_merge($this->options, ['error_types' => $errorTypes]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -589,11 +570,13 @@ public function getMaxBreadcrumbs(): int * * @param int $maxBreadcrumbs The maximum number of breadcrumbs */ - public function setMaxBreadcrumbs(int $maxBreadcrumbs): void + public function setMaxBreadcrumbs(int $maxBreadcrumbs): self { $options = array_merge($this->options, ['max_breadcrumbs' => $maxBreadcrumbs]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -617,11 +600,13 @@ public function getBeforeBreadcrumbCallback(): callable * * @psalm-param callable(Breadcrumb): ?Breadcrumb $callback */ - public function setBeforeBreadcrumbCallback(callable $callback): void + public function setBeforeBreadcrumbCallback(callable $callback): self { $options = array_merge($this->options, ['before_breadcrumb' => $callback]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -631,11 +616,13 @@ public function setBeforeBreadcrumbCallback(callable $callback): void * * @param IntegrationInterface[]|callable(IntegrationInterface[]): IntegrationInterface[] $integrations The list or callable */ - public function setIntegrations($integrations): void + public function setIntegrations($integrations): self { $options = array_merge($this->options, ['integrations' => $integrations]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -648,6 +635,34 @@ public function getIntegrations() return $this->options['integrations']; } + public function setTransport(TransportInterface $transport): self + { + $options = array_merge($this->options, ['transport' => $transport]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + public function getTransport(): ?TransportInterface + { + return $this->options['transport']; + } + + public function setHttpClient(HttpClientInterface $httpClient): self + { + $options = array_merge($this->options, ['http_client' => $httpClient]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + public function getHttpClient(): ?HttpClientInterface + { + return $this->options['http_client']; + } + /** * Should default PII be sent by default. */ @@ -661,11 +676,13 @@ public function shouldSendDefaultPii(): bool * * @param bool $enable Flag indicating if default PII will be sent */ - public function setSendDefaultPii(bool $enable): void + public function setSendDefaultPii(bool $enable): self { $options = array_merge($this->options, ['send_default_pii' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -681,11 +698,13 @@ public function hasDefaultIntegrations(): bool * * @param bool $enable Flag indicating whether the default integrations should be enabled */ - public function setDefaultIntegrations(bool $enable): void + public function setDefaultIntegrations(bool $enable): self { $options = array_merge($this->options, ['default_integrations' => $enable]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -701,11 +720,13 @@ public function getMaxValueLength(): int * * @param int $maxValueLength The number of characters after which the values containing text will be truncated */ - public function setMaxValueLength(int $maxValueLength): void + public function setMaxValueLength(int $maxValueLength): self { $options = array_merge($this->options, ['max_value_length' => $maxValueLength]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -721,11 +742,27 @@ public function getHttpProxy(): ?string * * @param string|null $httpProxy The http proxy */ - public function setHttpProxy(?string $httpProxy): void + public function setHttpProxy(?string $httpProxy): self { $options = array_merge($this->options, ['http_proxy' => $httpProxy]); $this->options = $this->resolver->resolve($options); + + return $this; + } + + public function getHttpProxyAuthentication(): ?string + { + return $this->options['http_proxy_authentication']; + } + + public function setHttpProxyAuthentication(?string $httpProxy): self + { + $options = array_merge($this->options, ['http_proxy_authentication' => $httpProxy]); + + $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -741,11 +778,13 @@ public function getHttpConnectTimeout(): float * * @param float $httpConnectTimeout The amount of time in seconds */ - public function setHttpConnectTimeout(float $httpConnectTimeout): void + public function setHttpConnectTimeout(float $httpConnectTimeout): self { $options = array_merge($this->options, ['http_connect_timeout' => $httpConnectTimeout]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -763,11 +802,47 @@ public function getHttpTimeout(): float * * @param float $httpTimeout The amount of time in seconds */ - public function setHttpTimeout(float $httpTimeout): void + public function setHttpTimeout(float $httpTimeout): self { $options = array_merge($this->options, ['http_timeout' => $httpTimeout]); $this->options = $this->resolver->resolve($options); + + return $this; + } + + public function getHttpSslVerifyPeer(): bool + { + return $this->options['http_ssl_verify_peer']; + } + + public function setHttpSslVerifyPeer(bool $httpSslVerifyPeer): self + { + $options = array_merge($this->options, ['http_ssl_verify_peer' => $httpSslVerifyPeer]); + + $this->options = $this->resolver->resolve($options); + + return $this; + } + + /** + * Returns whether the requests should be compressed using GZIP or not. + */ + public function isHttpCompressionEnabled(): bool + { + return $this->options['http_compression']; + } + + /** + * Sets whether the request should be compressed using JSON or not. + */ + public function setEnableHttpCompression(bool $enabled): self + { + $options = array_merge($this->options, ['http_compression' => $enabled]); + + $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -787,11 +862,13 @@ public function shouldCaptureSilencedErrors(): bool * @param bool $shouldCapture If set to true, errors silenced through the @ * operator will be reported, ignored otherwise */ - public function setCaptureSilencedErrors(bool $shouldCapture): void + public function setCaptureSilencedErrors(bool $shouldCapture): self { $options = array_merge($this->options, ['capture_silenced_errors' => $shouldCapture]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -821,11 +898,13 @@ public function getMaxRequestBodySize(): string * request body for as long as sentry can * make sense of it */ - public function setMaxRequestBodySize(string $maxRequestBodySize): void + public function setMaxRequestBodySize(string $maxRequestBodySize): self { $options = array_merge($this->options, ['max_request_body_size' => $maxRequestBodySize]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -846,11 +925,13 @@ public function getClassSerializers(): array * * @param array $serializers The list of serializer callbacks */ - public function setClassSerializers(array $serializers): void + public function setClassSerializers(array $serializers): self { $options = array_merge($this->options, ['class_serializers' => $serializers]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -871,11 +952,13 @@ public function getTracesSampler(): ?callable * * @psalm-param null|callable(\Sentry\Tracing\SamplingContext): float $sampler */ - public function setTracesSampler(?callable $sampler): void + public function setTracesSampler(?callable $sampler): self { $options = array_merge($this->options, ['traces_sampler' => $sampler]); $this->options = $this->resolver->resolve($options); + + return $this; } /** @@ -891,7 +974,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'integrations' => [], 'default_integrations' => true, - 'send_attempts' => 0, 'prefixes' => array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')), 'sample_rate' => 1, 'enable_tracing' => null, @@ -900,9 +982,8 @@ private function configureOptions(OptionsResolver $resolver): void 'profiles_sample_rate' => null, 'attach_stacktrace' => false, 'context_lines' => 5, - 'enable_compression' => true, 'environment' => $_SERVER['SENTRY_ENVIRONMENT'] ?? null, - 'logger' => 'php', + 'logger' => null, 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), @@ -914,7 +995,7 @@ private function configureOptions(OptionsResolver $resolver): void 'before_send_transaction' => static function (Event $transaction): Event { return $transaction; }, - 'trace_propagation_targets' => [], + 'trace_propagation_targets' => null, 'tags' => [], 'error_types' => null, 'max_breadcrumbs' => self::DEFAULT_MAX_BREADCRUMBS, @@ -925,15 +1006,19 @@ private function configureOptions(OptionsResolver $resolver): void 'in_app_include' => [], 'send_default_pii' => false, 'max_value_length' => 1024, + 'transport' => null, + 'http_client' => null, 'http_proxy' => null, + 'http_proxy_authentication' => null, 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, + 'http_ssl_verify_peer' => true, + 'http_compression' => true, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], ]); - $resolver->setAllowedTypes('send_attempts', 'int'); $resolver->setAllowedTypes('prefixes', 'string[]'); $resolver->setAllowedTypes('sample_rate', ['int', 'float']); $resolver->setAllowedTypes('enable_tracing', ['null', 'bool']); @@ -942,11 +1027,10 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']); $resolver->setAllowedTypes('attach_stacktrace', 'bool'); $resolver->setAllowedTypes('context_lines', ['null', 'int']); - $resolver->setAllowedTypes('enable_compression', 'bool'); $resolver->setAllowedTypes('environment', ['null', 'string']); $resolver->setAllowedTypes('in_app_exclude', 'string[]'); $resolver->setAllowedTypes('in_app_include', 'string[]'); - $resolver->setAllowedTypes('logger', ['null', 'string']); + $resolver->setAllowedTypes('logger', ['null', LoggerInterface::class]); $resolver->setAllowedTypes('release', ['null', 'string']); $resolver->setAllowedTypes('dsn', ['null', 'string', 'bool', Dsn::class]); $resolver->setAllowedTypes('server_name', 'string'); @@ -963,9 +1047,14 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('send_default_pii', 'bool'); $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); + $resolver->setAllowedTypes('transport', ['null', TransportInterface::class]); + $resolver->setAllowedTypes('http_client', ['null', HttpClientInterface::class]); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); + $resolver->setAllowedTypes('http_proxy_authentication', ['null', 'string']); $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); $resolver->setAllowedTypes('http_timeout', ['int', 'float']); + $resolver->setAllowedTypes('http_ssl_verify_peer', 'bool'); + $resolver->setAllowedTypes('http_compression', 'bool'); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); @@ -989,14 +1078,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('in_app_include', function (SymfonyOptions $options, array $value) { return array_map([$this, 'normalizeAbsolutePath'], $value); }); - - $resolver->setNormalizer('logger', function (SymfonyOptions $options, ?string $value): ?string { - if ('php' !== $value) { - @trigger_error('The option "logger" is deprecated.', \E_USER_DEPRECATED); - } - - return $value; - }); } /** @@ -1008,7 +1089,7 @@ private function normalizeAbsolutePath(string $value): string { $path = @realpath($value); - if (false === $path) { + if ($path === false) { $path = $value; } @@ -1024,7 +1105,7 @@ private function normalizeAbsolutePath(string $value): string */ private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn { - if (null === $value || \is_bool($value)) { + if ($value === null || \is_bool($value)) { return null; } @@ -1054,12 +1135,12 @@ private function normalizeDsnOption(SymfonyOptions $options, $value): ?Dsn */ private function validateDsnOption($dsn): bool { - if (null === $dsn || $dsn instanceof Dsn) { + if ($dsn === null || $dsn instanceof Dsn) { return true; } if (\is_bool($dsn)) { - return false === $dsn; + return $dsn === false; } switch (strtolower($dsn)) { @@ -1115,6 +1196,6 @@ private function validateClassSerializersOption(array $serializers): bool */ private function validateContextLinesOption(?int $contextLines): bool { - return null === $contextLines || $contextLines >= 0; + return $contextLines === null || $contextLines >= 0; } } diff --git a/src/Profiling/Profile.php b/src/Profiling/Profile.php index f92e3de09..cd1da1373 100644 --- a/src/Profiling/Profile.php +++ b/src/Profiling/Profile.php @@ -25,7 +25,6 @@ * module: string|null, * lineno: int|null, * } - * * @phpstan-type SentryProfile array{ * device: array{ * architecture: string, @@ -61,7 +60,6 @@ * stacks: array>, * }, * } - * * @phpstan-type ExcimerLogStackEntryTrace array{ * file: string, * line: int, @@ -69,7 +67,6 @@ * function?: string, * closure_line?: int, * } - * * @phpstan-type ExcimerLogStackEntry array{ * trace: array, * timestamp: float @@ -176,7 +173,7 @@ public function getFormattedData(Event $event): ?array $registerStack = static function (array $stack) use (&$stacks, &$stackHashMap): int { $stackHash = md5(serialize($stack)); - if (false === \array_key_exists($stackHash, $stackHashMap)) { + if (\array_key_exists($stackHash, $stackHashMap) === false) { $stackHashMap[$stackHash] = \count($stacks); $stacks[] = $stack; } @@ -200,7 +197,7 @@ public function getFormattedData(Event $event): ?array $frameIndex = $frameHashMap[$frameKey] ?? null; - if (null === $frameIndex) { + if ($frameIndex === null) { $file = $this->stripPrefixFromFilePath($this->options, $absolutePath); $module = null; @@ -245,7 +242,7 @@ public function getFormattedData(Event $event): ?array } $startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC')); - if (false === $startTime) { + if ($startTime === false) { return null; } @@ -314,7 +311,7 @@ private function validateExcimerLog(): bool $sampleCount = $this->excimerLog->count(); } - return self::MIN_SAMPLE_COUNT <= $sampleCount; + return $sampleCount >= self::MIN_SAMPLE_COUNT; } private function validateMaxDuration(float $duration): bool @@ -333,15 +330,15 @@ private function validateMaxDuration(float $duration): bool */ private function validateOsContext(?OsContext $osContext): bool { - if (null === $osContext) { + if ($osContext === null) { return false; } - if (null === $osContext->getVersion()) { + if ($osContext->getVersion() === null) { return false; } - if (null === $osContext->getMachineType()) { + if ($osContext->getMachineType() === null) { return false; } @@ -354,11 +351,11 @@ private function validateOsContext(?OsContext $osContext): bool */ private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool { - if (null === $runtimeContext) { + if ($runtimeContext === null) { return false; } - if (null === $runtimeContext->getVersion()) { + if ($runtimeContext->getVersion() === null) { return false; } @@ -371,11 +368,11 @@ private function validateRuntimeContext(?RuntimeContext $runtimeContext): bool */ private function validateEvent(Event $event): bool { - if (null === $event->getTransaction()) { + if ($event->getTransaction() === null) { return false; } - if (null === $event->getTraceId()) { + if ($event->getTraceId() === null) { return false; } diff --git a/src/Profiling/Profiler.php b/src/Profiling/Profiler.php index 956c213d0..dca039c7d 100644 --- a/src/Profiling/Profiler.php +++ b/src/Profiling/Profiler.php @@ -40,14 +40,14 @@ public function __construct(?Options $options = null) public function start(): void { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->start(); } } public function stop(): void { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->stop(); $this->profile->setExcimerLog($this->profiler->flush()); @@ -56,7 +56,7 @@ public function stop(): void public function getProfile(): ?Profile { - if (null === $this->profiler) { + if ($this->profiler === null) { return null; } diff --git a/src/SentrySdk.php b/src/SentrySdk.php index a8773f185..4b41fc30a 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -43,7 +43,7 @@ public static function init(): HubInterface */ public static function getCurrentHub(): HubInterface { - if (null === self::$currentHub) { + if (self::$currentHub === null) { self::$currentHub = new Hub(); } diff --git a/src/Serializer/AbstractSerializer.php b/src/Serializer/AbstractSerializer.php index 1e2a72b25..ac6741a52 100644 --- a/src/Serializer/AbstractSerializer.php +++ b/src/Serializer/AbstractSerializer.php @@ -20,7 +20,6 @@ namespace Sentry\Serializer; -use Sentry\Exception\InvalidArgumentException; use Sentry\Options; /** @@ -77,7 +76,7 @@ public function __construct(Options $options, int $maxDepth = 3, ?string $mbDete { $this->maxDepth = $maxDepth; - if (null != $mbDetectOrder) { + if ($mbDetectOrder != null) { $this->mbDetectOrder = $mbDetectOrder; } @@ -236,7 +235,7 @@ protected function serializeString($value): string */ protected function serializeValue($value) { - if ((null === $value) || \is_bool($value) || is_numeric($value)) { + if (($value === null) || \is_bool($value) || is_numeric($value)) { return $value; } @@ -254,7 +253,7 @@ protected function serializeValue($value) } } - return 'Object ' . $reflection->getName() . (is_scalar($objectId) ? '(#' . $objectId . ')' : ''); + return 'Object ' . $reflection->getName() . (\is_scalar($objectId) ? '(#' . $objectId . ')' : ''); } if (\is_resource($value)) { @@ -286,7 +285,7 @@ protected function serializeCallable($callable): string } if (!\is_callable($callable)) { - throw new InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); + throw new \InvalidArgumentException(sprintf('Expecting callable, got %s', \is_object($callable) ? \get_class($callable) : \gettype($callable))); } try { diff --git a/src/Serializer/EnvelopItems/CheckInItem.php b/src/Serializer/EnvelopItems/CheckInItem.php new file mode 100644 index 000000000..e3e2778bf --- /dev/null +++ b/src/Serializer/EnvelopItems/CheckInItem.php @@ -0,0 +1,46 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = []; + + $checkIn = $event->getCheckIn(); + if ($checkIn !== null) { + $payload = [ + 'check_in_id' => $checkIn->getId(), + 'monitor_slug' => $checkIn->getMonitorSlug(), + 'status' => (string) $checkIn->getStatus(), + 'duration' => $checkIn->getDuration(), + 'release' => $checkIn->getRelease(), + 'environment' => $checkIn->getEnvironment(), + ]; + + if ($checkIn->getMonitorConfig() !== null) { + $payload['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); + } + + if (!empty($event->getContexts()['trace'])) { + $payload['contexts']['trace'] = $event->getContexts()['trace']; + } + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } +} diff --git a/src/Serializer/EnvelopItems/EnvelopeItemInterface.php b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php new file mode 100644 index 000000000..d2b7d3712 --- /dev/null +++ b/src/Serializer/EnvelopItems/EnvelopeItemInterface.php @@ -0,0 +1,15 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = [ + 'timestamp' => $event->getTimestamp(), + 'platform' => 'php', + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + if ($event->getStartTimestamp() !== null) { + $payload['start_timestamp'] = $event->getStartTimestamp(); + } + + if ($event->getLevel() !== null) { + $payload['level'] = (string) $event->getLevel(); + } + + if ($event->getTransaction() !== null) { + $payload['transaction'] = $event->getTransaction(); + } + + if ($event->getServerName() !== null) { + $payload['server_name'] = $event->getServerName(); + } + + if ($event->getRelease() !== null) { + $payload['release'] = $event->getRelease(); + } + + if ($event->getEnvironment() !== null) { + $payload['environment'] = $event->getEnvironment(); + } + + if (!empty($event->getFingerprint())) { + $payload['fingerprint'] = $event->getFingerprint(); + } + + if (!empty($event->getModules())) { + $payload['modules'] = $event->getModules(); + } + + if (!empty($event->getExtra())) { + $payload['extra'] = $event->getExtra(); + } + + if (!empty($event->getTags())) { + $payload['tags'] = $event->getTags(); + } + + $user = $event->getUser(); + if ($user !== null) { + $payload['user'] = array_merge($user->getMetadata(), [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'ip_address' => $user->getIpAddress(), + 'segment' => $user->getSegment(), + ]); + } + + $osContext = $event->getOsContext(); + if ($osContext !== null) { + $payload['contexts']['os'] = [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build' => $osContext->getBuild(), + 'kernel_version' => $osContext->getKernelVersion(), + ]; + } + + $runtimeContext = $event->getRuntimeContext(); + if ($runtimeContext !== null) { + $payload['contexts']['runtime'] = [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ]; + } + + if (!empty($event->getContexts())) { + $payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts()); + } + + if (!empty($event->getBreadcrumbs())) { + $payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs()); + } + + if (!empty($event->getRequest())) { + $payload['request'] = $event->getRequest(); + } + + if ($event->getMessage() !== null) { + if (empty($event->getMessageParams())) { + $payload['message'] = $event->getMessage(); + } else { + $payload['message'] = [ + 'message' => $event->getMessage(), + 'params' => $event->getMessageParams(), + 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), + ]; + } + } + + $exceptions = $event->getExceptions(); + for ($i = \count($exceptions) - 1; $i >= 0; --$i) { + $payload['exception']['values'][] = self::serializeException($exceptions[$i]); + } + + $stacktrace = $event->getStacktrace(); + if ($stacktrace !== null) { + $payload['stacktrace'] = [ + 'frames' => array_map([self::class, 'serializeStacktraceFrame'], $stacktrace->getFrames()), + ]; + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } + + /** + * @return array + * + * @psalm-return array{ + * type: string, + * value: string, + * stacktrace?: array{ + * frames: array> + * }, + * mechanism?: array{ + * type: string, + * handled: boolean, + * data?: array + * } + * } + */ + protected static function serializeException(ExceptionDataBag $exception): array + { + $exceptionMechanism = $exception->getMechanism(); + $exceptionStacktrace = $exception->getStacktrace(); + $result = [ + 'type' => $exception->getType(), + 'value' => $exception->getValue(), + ]; + + if ($exceptionStacktrace !== null) { + $result['stacktrace'] = [ + 'frames' => array_map([self::class, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), + ]; + } + + if ($exceptionMechanism !== null) { + $result['mechanism'] = [ + 'type' => $exceptionMechanism->getType(), + 'handled' => $exceptionMechanism->isHandled(), + ]; + + if ($exceptionMechanism->getData() !== []) { + $result['mechanism']['data'] = $exceptionMechanism->getData(); + } + } + + return $result; + } + + /** + * @return array + * + * @psalm-return array{ + * filename: string, + * lineno: int, + * in_app: bool, + * abs_path?: string, + * function?: string, + * raw_function?: string, + * pre_context?: string[], + * context_line?: string, + * post_context?: string[], + * vars?: array + * } + */ + protected static function serializeStacktraceFrame(Frame $frame): array + { + $result = [ + 'filename' => $frame->getFile(), + 'lineno' => $frame->getLine(), + 'in_app' => $frame->isInApp(), + ]; + + if ($frame->getAbsoluteFilePath() !== null) { + $result['abs_path'] = $frame->getAbsoluteFilePath(); + } + + if ($frame->getFunctionName() !== null) { + $result['function'] = $frame->getFunctionName(); + } + + if ($frame->getRawFunctionName() !== null) { + $result['raw_function'] = $frame->getRawFunctionName(); + } + + if (!empty($frame->getPreContext())) { + $result['pre_context'] = $frame->getPreContext(); + } + + if ($frame->getContextLine() !== null) { + $result['context_line'] = $frame->getContextLine(); + } + + if (!empty($frame->getPostContext())) { + $result['post_context'] = $frame->getPostContext(); + } + + if (!empty($frame->getVars())) { + $result['vars'] = $frame->getVars(); + } + + return $result; + } +} diff --git a/src/Serializer/EnvelopItems/ProfileItem.php b/src/Serializer/EnvelopItems/ProfileItem.php new file mode 100644 index 000000000..90fdca9ec --- /dev/null +++ b/src/Serializer/EnvelopItems/ProfileItem.php @@ -0,0 +1,35 @@ + 'profile', + 'content_type' => 'application/json', + ]; + + $profile = $event->getSdkMetadata('profile'); + if (!$profile instanceof Profile) { + return ''; + } + + $payload = $profile->getFormattedData($event); + if ($payload === null) { + return ''; + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } +} diff --git a/src/Serializer/EnvelopItems/TransactionItem.php b/src/Serializer/EnvelopItems/TransactionItem.php new file mode 100644 index 000000000..f44dc38d0 --- /dev/null +++ b/src/Serializer/EnvelopItems/TransactionItem.php @@ -0,0 +1,181 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = [ + 'timestamp' => $event->getTimestamp(), + 'platform' => 'php', + 'sdk' => [ + 'name' => $event->getSdkIdentifier(), + 'version' => $event->getSdkVersion(), + ], + ]; + + if ($event->getStartTimestamp() !== null) { + $payload['start_timestamp'] = $event->getStartTimestamp(); + } + + if ($event->getLevel() !== null) { + $payload['level'] = (string) $event->getLevel(); + } + + if ($event->getTransaction() !== null) { + $payload['transaction'] = $event->getTransaction(); + } + + if ($event->getServerName() !== null) { + $payload['server_name'] = $event->getServerName(); + } + + if ($event->getRelease() !== null) { + $payload['release'] = $event->getRelease(); + } + + if ($event->getEnvironment() !== null) { + $payload['environment'] = $event->getEnvironment(); + } + + if (!empty($event->getFingerprint())) { + $payload['fingerprint'] = $event->getFingerprint(); + } + + if (!empty($event->getModules())) { + $payload['modules'] = $event->getModules(); + } + + if (!empty($event->getExtra())) { + $payload['extra'] = $event->getExtra(); + } + + if (!empty($event->getTags())) { + $payload['tags'] = $event->getTags(); + } + + $user = $event->getUser(); + if ($user !== null) { + $payload['user'] = array_merge($user->getMetadata(), [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'ip_address' => $user->getIpAddress(), + 'segment' => $user->getSegment(), + ]); + } + + $osContext = $event->getOsContext(); + if ($osContext !== null) { + $payload['contexts']['os'] = [ + 'name' => $osContext->getName(), + 'version' => $osContext->getVersion(), + 'build' => $osContext->getBuild(), + 'kernel_version' => $osContext->getKernelVersion(), + ]; + } + + $runtimeContext = $event->getRuntimeContext(); + if ($runtimeContext !== null) { + $payload['contexts']['runtime'] = [ + 'name' => $runtimeContext->getName(), + 'version' => $runtimeContext->getVersion(), + ]; + } + + if (!empty($event->getContexts())) { + $payload['contexts'] = array_merge($payload['contexts'] ?? [], $event->getContexts()); + } + + if (!empty($event->getBreadcrumbs())) { + $payload['breadcrumbs']['values'] = array_map([self::class, 'serializeBreadcrumb'], $event->getBreadcrumbs()); + } + + if (!empty($event->getRequest())) { + $payload['request'] = $event->getRequest(); + } + + $payload['spans'] = array_values(array_map([self::class, 'serializeSpan'], $event->getSpans())); + + $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); + if ($transactionMetadata instanceof TransactionMetadata) { + $payload['transaction_info']['source'] = (string) $transactionMetadata->getSource(); + } + + return sprintf("%s\n%s", JSON::encode($header), JSON::encode($payload)); + } + + /** + * @return array + * + * @psalm-return array{ + * span_id: string, + * trace_id: string, + * parent_span_id?: string, + * start_timestamp: float, + * timestamp?: float, + * status?: string, + * description?: string, + * op?: string, + * data?: array, + * tags?: array + * } + */ + protected static function serializeSpan(Span $span): array + { + $result = [ + 'span_id' => (string) $span->getSpanId(), + 'trace_id' => (string) $span->getTraceId(), + 'start_timestamp' => $span->getStartTimestamp(), + ]; + + if ($span->getParentSpanId() !== null) { + $result['parent_span_id'] = (string) $span->getParentSpanId(); + } + + if ($span->getEndTimestamp() !== null) { + $result['timestamp'] = $span->getEndTimestamp(); + } + + if ($span->getStatus() !== null) { + $result['status'] = (string) $span->getStatus(); + } + + if ($span->getDescription() !== null) { + $result['description'] = $span->getDescription(); + } + + if ($span->getOp() !== null) { + $result['op'] = $span->getOp(); + } + + if (!empty($span->getData())) { + $result['data'] = $span->getData(); + } + + if (!empty($span->getTags())) { + $result['tags'] = $span->getTags(); + } + + return $result; + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index 7aec12421..ad1365b3a 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -4,16 +4,14 @@ namespace Sentry\Serializer; -use Sentry\Breadcrumb; use Sentry\Event; use Sentry\EventType; -use Sentry\ExceptionDataBag; -use Sentry\Frame; use Sentry\Options; -use Sentry\Profiling\Profile; +use Sentry\Serializer\EnvelopItems\CheckInItem; +use Sentry\Serializer\EnvelopItems\EventItem; +use Sentry\Serializer\EnvelopItems\ProfileItem; +use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; -use Sentry\Tracing\Span; -use Sentry\Tracing\TransactionMetadata; use Sentry\Util\JSON; /** @@ -38,227 +36,6 @@ public function __construct(Options $options) * {@inheritdoc} */ public function serialize(Event $event): string - { - if (EventType::transaction() === $event->getType()) { - $transactionEnvelope = $this->serializeAsEnvelope($event); - - // Attach a new envelope item containing the profile data - if (null !== $event->getSdkMetadata('profile')) { - $profileEnvelope = $this->seralizeProfileAsEnvelope($event); - if (null !== $profileEnvelope) { - return sprintf("%s\n%s", $transactionEnvelope, $profileEnvelope); - } - } - - return $transactionEnvelope; - } - - if (EventType::checkIn() === $event->getType()) { - return $this->serializeAsEnvelope($event); - } - - if ($this->options->isTracingEnabled()) { - return $this->serializeAsEnvelope($event); - } - - return $this->serializeAsEvent($event); - } - - private function serializeAsEvent(Event $event): string - { - $result = $this->toArray($event); - - return JSON::encode($result); - } - - private function serializeAsCheckInEvent(Event $event): string - { - $result = []; - - $checkIn = $event->getCheckIn(); - if (null !== $checkIn) { - $result = [ - 'check_in_id' => $checkIn->getId(), - 'monitor_slug' => $checkIn->getMonitorSlug(), - 'status' => (string) $checkIn->getStatus(), - 'duration' => $checkIn->getDuration(), - 'release' => $checkIn->getRelease(), - 'environment' => $checkIn->getEnvironment(), - ]; - - if (null !== $checkIn->getMonitorConfig()) { - $result['monitor_config'] = $checkIn->getMonitorConfig()->toArray(); - } - - if (!empty($event->getContexts()['trace'])) { - $result['contexts']['trace'] = $event->getContexts()['trace']; - } - } - - return JSON::encode($result); - } - - /** - * @return array - */ - public function toArray(Event $event): array - { - $result = [ - 'event_id' => (string) $event->getId(), - 'timestamp' => $event->getTimestamp(), - 'platform' => 'php', - 'sdk' => [ - 'name' => $event->getSdkIdentifier(), - 'version' => $event->getSdkVersion(), - ], - ]; - - if (null !== $event->getStartTimestamp()) { - $result['start_timestamp'] = $event->getStartTimestamp(); - } - - if (null !== $event->getLevel()) { - $result['level'] = (string) $event->getLevel(); - } - - if (null !== $event->getLogger()) { - $result['logger'] = $event->getLogger(); - } - - if (null !== $event->getTransaction()) { - $result['transaction'] = $event->getTransaction(); - } - - if (null !== $event->getServerName()) { - $result['server_name'] = $event->getServerName(); - } - - if (null !== $event->getRelease()) { - $result['release'] = $event->getRelease(); - } - - if (null !== $event->getEnvironment()) { - $result['environment'] = $event->getEnvironment(); - } - - if (!empty($event->getFingerprint())) { - $result['fingerprint'] = $event->getFingerprint(); - } - - if (!empty($event->getModules())) { - $result['modules'] = $event->getModules(); - } - - if (!empty($event->getExtra())) { - $result['extra'] = $event->getExtra(); - } - - if (!empty($event->getTags())) { - $result['tags'] = $event->getTags(); - } - - $user = $event->getUser(); - - if (null !== $user) { - $result['user'] = array_merge($user->getMetadata(), [ - 'id' => $user->getId(), - 'username' => $user->getUsername(), - 'email' => $user->getEmail(), - 'ip_address' => $user->getIpAddress(), - 'segment' => $user->getSegment(), - ]); - } - - $osContext = $event->getOsContext(); - $runtimeContext = $event->getRuntimeContext(); - - if (null !== $osContext) { - $result['contexts']['os'] = [ - 'name' => $osContext->getName(), - 'version' => $osContext->getVersion(), - 'build' => $osContext->getBuild(), - 'kernel_version' => $osContext->getKernelVersion(), - ]; - } - - if (null !== $runtimeContext) { - $result['contexts']['runtime'] = [ - 'name' => $runtimeContext->getName(), - 'version' => $runtimeContext->getVersion(), - ]; - } - - if (!empty($event->getContexts())) { - $result['contexts'] = array_merge($result['contexts'] ?? [], $event->getContexts()); - } - - if (!empty($event->getBreadcrumbs())) { - $result['breadcrumbs']['values'] = array_map([$this, 'serializeBreadcrumb'], $event->getBreadcrumbs()); - } - - if (!empty($event->getRequest())) { - $result['request'] = $event->getRequest(); - } - - if (null !== $event->getMessage()) { - if (empty($event->getMessageParams())) { - $result['message'] = $event->getMessage(); - } else { - $result['message'] = [ - 'message' => $event->getMessage(), - 'params' => $event->getMessageParams(), - 'formatted' => $event->getMessageFormatted() ?? vsprintf($event->getMessage(), $event->getMessageParams()), - ]; - } - } - - $exceptions = $event->getExceptions(); - - for ($i = \count($exceptions) - 1; $i >= 0; --$i) { - $result['exception']['values'][] = $this->serializeException($exceptions[$i]); - } - - if (EventType::transaction() === $event->getType()) { - $result['spans'] = array_values(array_map([$this, 'serializeSpan'], $event->getSpans())); - - $transactionMetadata = $event->getSdkMetadata('transaction_metadata'); - if ($transactionMetadata instanceof TransactionMetadata) { - $result['transaction_info']['source'] = (string) $transactionMetadata->getSource(); - } - } - - /** - * In case of error events, with tracing being disabled, we set the Replay ID - * as a context into the payload. - */ - if ( - EventType::event() === $event->getType() && - !$this->options->isTracingEnabled() - ) { - $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); - if ($dynamicSamplingContext instanceof DynamicSamplingContext) { - $replayId = $dynamicSamplingContext->get('replay_id'); - - if (null !== $replayId) { - $result['contexts']['replay'] = [ - 'replay_id' => $replayId, - ]; - } - } - } - - $stacktrace = $event->getStacktrace(); - - if (null !== $stacktrace) { - $result['stacktrace'] = [ - 'frames' => array_map([$this, 'serializeStacktraceFrame'], $stacktrace->getFrames()), - ]; - } - - return $result; - } - - private function serializeAsEnvelope(Event $event): string { // @see https://develop.sentry.dev/sdk/envelopes/#envelope-headers $envelopeHeader = [ @@ -272,7 +49,6 @@ private function serializeAsEnvelope(Event $event): string ]; $dynamicSamplingContext = $event->getSdkMetadata('dynamic_sampling_context'); - if ($dynamicSamplingContext instanceof DynamicSamplingContext) { $entries = $dynamicSamplingContext->getEntries(); @@ -281,224 +57,28 @@ private function serializeAsEnvelope(Event $event): string } } - $itemHeader = [ - 'type' => (string) $event->getType(), - 'content_type' => 'application/json', - ]; - - if (EventType::checkIn() === $event->getType()) { - $seralizedEvent = $this->serializeAsCheckInEvent($event); - } else { - $seralizedEvent = $this->serializeAsEvent($event); - } - - return sprintf("%s\n%s\n%s", JSON::encode($envelopeHeader), JSON::encode($itemHeader), $seralizedEvent); - } - - private function seralizeProfileAsEnvelope(Event $event): ?string - { - $itemHeader = [ - 'type' => 'profile', - 'content_type' => 'application/json', - ]; - - $profile = $event->getSdkMetadata('profile'); - if (!$profile instanceof Profile) { - return null; - } - - $profileData = $profile->getFormattedData($event); - if (null === $profileData) { - return null; - } - - return sprintf("%s\n%s", JSON::encode($itemHeader), JSON::encode($profileData)); - } - - /** - * @return array - * - * @psalm-return array{ - * type: string, - * category: string, - * level: string, - * timestamp: float, - * message?: string, - * data?: array - * } - */ - private function serializeBreadcrumb(Breadcrumb $breadcrumb): array - { - $result = [ - 'type' => $breadcrumb->getType(), - 'category' => $breadcrumb->getCategory(), - 'level' => $breadcrumb->getLevel(), - 'timestamp' => $breadcrumb->getTimestamp(), - ]; - - if (null !== $breadcrumb->getMessage()) { - $result['message'] = $breadcrumb->getMessage(); - } - - if (!empty($breadcrumb->getMetadata())) { - $result['data'] = $breadcrumb->getMetadata(); - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * type: string, - * value: string, - * stacktrace?: array{ - * frames: array> - * }, - * mechanism?: array{ - * type: string, - * handled: boolean, - * data?: array - * } - * } - */ - private function serializeException(ExceptionDataBag $exception): array - { - $exceptionMechanism = $exception->getMechanism(); - $exceptionStacktrace = $exception->getStacktrace(); - $result = [ - 'type' => $exception->getType(), - 'value' => $exception->getValue(), - ]; - - if (null !== $exceptionStacktrace) { - $result['stacktrace'] = [ - 'frames' => array_map([$this, 'serializeStacktraceFrame'], $exceptionStacktrace->getFrames()), - ]; - } - - if (null !== $exceptionMechanism) { - $result['mechanism'] = [ - 'type' => $exceptionMechanism->getType(), - 'handled' => $exceptionMechanism->isHandled(), - ]; - - if ([] !== $exceptionMechanism->getData()) { - $result['mechanism']['data'] = $exceptionMechanism->getData(); - } - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * filename: string, - * lineno: int, - * in_app: bool, - * abs_path?: string, - * function?: string, - * raw_function?: string, - * pre_context?: string[], - * context_line?: string, - * post_context?: string[], - * vars?: array - * } - */ - private function serializeStacktraceFrame(Frame $frame): array - { - $result = [ - 'filename' => $frame->getFile(), - 'lineno' => $frame->getLine(), - 'in_app' => $frame->isInApp(), - ]; - - if (null !== $frame->getAbsoluteFilePath()) { - $result['abs_path'] = $frame->getAbsoluteFilePath(); - } - - if (null !== $frame->getFunctionName()) { - $result['function'] = $frame->getFunctionName(); - } - - if (null !== $frame->getRawFunctionName()) { - $result['raw_function'] = $frame->getRawFunctionName(); - } - - if (!empty($frame->getPreContext())) { - $result['pre_context'] = $frame->getPreContext(); - } - - if (null !== $frame->getContextLine()) { - $result['context_line'] = $frame->getContextLine(); - } - - if (!empty($frame->getPostContext())) { - $result['post_context'] = $frame->getPostContext(); - } - - if (!empty($frame->getVars())) { - $result['vars'] = $frame->getVars(); - } - - return $result; - } - - /** - * @return array - * - * @psalm-return array{ - * span_id: string, - * trace_id: string, - * parent_span_id?: string, - * start_timestamp: float, - * timestamp?: float, - * status?: string, - * description?: string, - * op?: string, - * data?: array, - * tags?: array - * } - */ - private function serializeSpan(Span $span): array - { - $result = [ - 'span_id' => (string) $span->getSpanId(), - 'trace_id' => (string) $span->getTraceId(), - 'start_timestamp' => $span->getStartTimestamp(), - ]; - - if (null !== $span->getParentSpanId()) { - $result['parent_span_id'] = (string) $span->getParentSpanId(); - } - - if (null !== $span->getEndTimestamp()) { - $result['timestamp'] = $span->getEndTimestamp(); - } - - if (null !== $span->getStatus()) { - $result['status'] = (string) $span->getStatus(); - } - - if (null !== $span->getDescription()) { - $result['description'] = $span->getDescription(); - } - - if (null !== $span->getOp()) { - $result['op'] = $span->getOp(); - } - - if (!empty($span->getData())) { - $result['data'] = $span->getData(); - } - - if (!empty($span->getTags())) { - $result['tags'] = $span->getTags(); + $items = ''; + + switch ($event->getType()) { + case EventType::event(): + $items = EventItem::toEnvelopeItem($event); + break; + case EventType::transaction(): + $transactionItem = TransactionItem::toEnvelopeItem($event); + if ($event->getSdkMetadata('profile') !== null) { + $profileItem = ProfileItem::toEnvelopeItem($event); + if ($profileItem !== '') { + $items = sprintf("%s\n%s", $transactionItem, $profileItem); + break; + } + } + $items = $transactionItem; + break; + case EventType::checkIn(): + $items = CheckInItem::toEnvelopeItem($event); + break; } - return $result; + return sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); } } diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index f8b9e07e2..d7fc2d5ec 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -37,15 +37,15 @@ public function representationSerialize($value) */ protected function serializeValue($value) { - if (null === $value) { + if ($value === null) { return 'null'; } - if (false === $value) { + if ($value === false) { return 'false'; } - if (true === $value) { + if ($value === true) { return 'true'; } diff --git a/src/Serializer/Traits/BreadcrumbSeralizerTrait.php b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php new file mode 100644 index 000000000..7fe10ce25 --- /dev/null +++ b/src/Serializer/Traits/BreadcrumbSeralizerTrait.php @@ -0,0 +1,45 @@ + + * + * @psalm-return array{ + * type: string, + * category: string, + * level: string, + * timestamp: float, + * message?: string, + * data?: array + * } + */ + protected static function serializeBreadcrumb(Breadcrumb $breadcrumb): array + { + $result = [ + 'type' => $breadcrumb->getType(), + 'category' => $breadcrumb->getCategory(), + 'level' => $breadcrumb->getLevel(), + 'timestamp' => $breadcrumb->getTimestamp(), + ]; + + if ($breadcrumb->getMessage() !== null) { + $result['message'] = $breadcrumb->getMessage(); + } + + if (!empty($breadcrumb->getMetadata())) { + $result['data'] = $breadcrumb->getMetadata(); + } + + return $result; + } +} diff --git a/src/Severity.php b/src/Severity.php index 4e065d62f..8fdf161b3 100644 --- a/src/Severity.php +++ b/src/Severity.php @@ -83,34 +83,32 @@ public function __construct(string $value = self::INFO) * Translate a PHP Error constant into a Sentry log level group. * * @param int $severity PHP E_* error constant - * - * @return Severity */ public static function fromError(int $severity): self { switch ($severity) { - case \E_DEPRECATED: - case \E_USER_DEPRECATED: - case \E_WARNING: - case \E_USER_WARNING: - return self::warning(); - case \E_ERROR: - case \E_PARSE: - case \E_CORE_ERROR: - case \E_CORE_WARNING: - case \E_COMPILE_ERROR: - case \E_COMPILE_WARNING: - return self::fatal(); - case \E_RECOVERABLE_ERROR: - case \E_USER_ERROR: - return self::error(); - case \E_NOTICE: - case \E_USER_NOTICE: - case \E_STRICT: - return self::info(); - default: - return self::error(); - } + case \E_DEPRECATED: + case \E_USER_DEPRECATED: + case \E_WARNING: + case \E_USER_WARNING: + return self::warning(); + case \E_ERROR: + case \E_PARSE: + case \E_CORE_ERROR: + case \E_CORE_WARNING: + case \E_COMPILE_ERROR: + case \E_COMPILE_WARNING: + return self::fatal(); + case \E_RECOVERABLE_ERROR: + case \E_USER_ERROR: + return self::error(); + case \E_NOTICE: + case \E_USER_NOTICE: + case \E_STRICT: + return self::info(); + default: + return self::error(); + } } /** diff --git a/src/Stacktrace.php b/src/Stacktrace.php index 5750ced85..52633602c 100644 --- a/src/Stacktrace.php +++ b/src/Stacktrace.php @@ -69,9 +69,11 @@ public function getFrame(int $index): Frame * * @param Frame $frame The frame */ - public function addFrame(Frame $frame): void + public function addFrame(Frame $frame): self { array_unshift($this->frames, $frame); + + return $this; } /** @@ -81,16 +83,18 @@ public function addFrame(Frame $frame): void * * @throws \OutOfBoundsException If the index is out of range */ - public function removeFrame(int $index): void + public function removeFrame(int $index): self { if (!isset($this->frames[$index])) { throw new \OutOfBoundsException(sprintf('Cannot remove the frame at index %d.', $index)); } - if (1 === \count($this->frames)) { + if (\count($this->frames) === 1) { throw new \RuntimeException('Cannot remove all frames from the stacktrace.'); } array_splice($this->frames, $index, 1); + + return $this; } } diff --git a/src/State/Hub.php b/src/State/Hub.php index 5a5ed5ea6..9ac3ff0e4 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -78,11 +78,11 @@ public function pushScope(): Scope */ public function popScope(): bool { - if (1 === \count($this->stack)) { + if (\count($this->stack) === 1) { return false; } - return null !== array_pop($this->stack); + return array_pop($this->stack) !== null; } /** @@ -123,7 +123,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } @@ -137,7 +137,7 @@ public function captureException(\Throwable $exception, ?EventHint $hint = null) { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } @@ -151,7 +151,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureEvent($event, $hint, $this->getScope()); } @@ -165,7 +165,7 @@ public function captureLastError(?EventHint $hint = null): ?EventId { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } @@ -181,7 +181,7 @@ public function captureCheckIn(string $slug, CheckInStatus $status, $duration = { $client = $this->getClient(); - if (null === $client) { + if ($client === null) { return null; } @@ -209,7 +209,7 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool { $client = $this->getClient(); - if (null === $client) { + if ($client === null) { return false; } @@ -223,11 +223,11 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool $breadcrumb = $beforeBreadcrumbCallback($breadcrumb); - if (null !== $breadcrumb) { + if ($breadcrumb !== null) { $this->getScope()->addBreadcrumb($breadcrumb, $maxBreadcrumbs); } - return null !== $breadcrumb; + return $breadcrumb !== null; } /** @@ -237,7 +237,7 @@ public function getIntegration(string $className): ?IntegrationInterface { $client = $this->getClient(); - if (null !== $client) { + if ($client !== null) { return $client->getIntegration($className); } @@ -253,9 +253,9 @@ public function startTransaction(TransactionContext $context, array $customSampl { $transaction = new Transaction($context, $this); $client = $this->getClient(); - $options = null !== $client ? $client->getOptions() : null; + $options = $client !== null ? $client->getOptions() : null; - if (null === $options || !$options->isTracingEnabled()) { + if ($options === null || !$options->isTracingEnabled()) { $transaction->setSampled(false); return $transaction; @@ -266,8 +266,8 @@ public function startTransaction(TransactionContext $context, array $customSampl $tracesSampler = $options->getTracesSampler(); - if (null === $transaction->getSampled()) { - if (null !== $tracesSampler) { + if ($transaction->getSampled() === null) { + if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); } else { $sampleRate = $this->getSampleRate( @@ -284,7 +284,7 @@ public function startTransaction(TransactionContext $context, array $customSampl $transaction->getMetadata()->setSamplingRate($sampleRate); - if (0.0 === $sampleRate) { + if ($sampleRate === 0.0) { $transaction->setSampled(false); return $transaction; @@ -303,7 +303,7 @@ public function startTransaction(TransactionContext $context, array $customSampl if ($this->sample($profilesSampleRate)) { $transaction->initProfiler(); $profiler = $transaction->getProfiler(); - if (null !== $profiler) { + if ($profiler !== null) { $profiler->start(); } } @@ -355,11 +355,11 @@ private function getStackTop(): Layer private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float { - if (true === $hasParentBeenSampled) { + if ($hasParentBeenSampled === true) { return 1; } - if (false === $hasParentBeenSampled) { + if ($hasParentBeenSampled === false) { return 0; } @@ -371,11 +371,11 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl */ private function sample($sampleRate): bool { - if (0.0 === $sampleRate) { + if ($sampleRate === 0.0) { return false; } - if (1.0 === $sampleRate) { + if ($sampleRate === 1.0) { return true; } diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index 8f637860f..a2a5c79e7 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -42,7 +42,7 @@ private function __construct() */ public static function getInstance(): self { - if (null === self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } @@ -165,8 +165,6 @@ public function getIntegration(string $className): ?IntegrationInterface /** * {@inheritdoc} - * - * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { diff --git a/src/State/HubInterface.php b/src/State/HubInterface.php index 9b4c26ceb..44d9e70e3 100644 --- a/src/State/HubInterface.php +++ b/src/State/HubInterface.php @@ -13,18 +13,10 @@ use Sentry\Integration\IntegrationInterface; use Sentry\MonitorConfig; use Sentry\Severity; -use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -/** - * This interface represent the class which is responsible for maintaining a - * stack of pairs of clients and scopes. It is the main entry point to talk - * with the Sentry client. - * - * @method string|null captureCheckIn(string $slug, CheckInStatus $status, int|float|null $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null) Captures a check-in - */ interface HubInterface { /** @@ -72,61 +64,48 @@ public function withScope(callable $callback); /** * Calls the given callback passing to it the current scope so that any * operation can be run within its context. - * - * @param callable $callback The callback to be executed */ public function configureScope(callable $callback): void; /** * Binds the given client to the current scope. - * - * @param ClientInterface $client The client */ public function bindClient(ClientInterface $client): void; /** * Captures a message event and sends it to Sentry. - * - * @param string $message The message - * @param Severity|null $level The severity level of the message - * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null/*, ?EventHint $hint = null*/): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId; /** * Captures an exception event and sends it to Sentry. - * - * @param \Throwable $exception The exception - * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception/*, ?EventHint $hint = null*/): ?EventId; + public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. - * - * @param Event $event The event being captured - * @param EventHint|null $hint May contain additional information about the event */ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId; /** * Captures an event that logs the last occurred error. - * - * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(/*?EventHint $hint = null*/): ?EventId; + public function captureLastError(?EventHint $hint = null): ?EventId; /** * Records a new breadcrumb which will be attached to future events. They * will be added to subsequent events to provide more context on user's * actions prior to an error or crash. - * - * @param Breadcrumb $breadcrumb The breadcrumb to record - * - * @return bool Whether the breadcrumb was actually added to the current scope */ public function addBreadcrumb(Breadcrumb $breadcrumb): bool; + /** + * Captures a check-in. + * + * @param int|float|null $duration + */ + public function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ?MonitorConfig $monitorConfig = null, ?string $checkInId = null): ?string; + /** * Gets the integration whose FQCN matches the given one if it's available on the current client. * @@ -155,10 +134,9 @@ public function getIntegration(string $className): ?IntegrationInterface; * which point the transaction with all its finished child spans will be sent to * Sentry. * - * @param TransactionContext $context Properties of the new transaction * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(TransactionContext $context/*, array $customSamplingContext = []*/): Transaction; + public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction; /** * Returns the transaction that is on the Hub. @@ -172,8 +150,6 @@ public function getSpan(): ?Span; /** * Sets the span on the Hub. - * - * @param Span|null $span The span */ public function setSpan(?Span $span): HubInterface; } diff --git a/src/State/Scope.php b/src/State/Scope.php index 904b6804b..0f3984aea 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -82,7 +82,7 @@ final class Scope */ private static $globalEventProcessors = []; - public function __construct(PropagationContext $propagationContext = null) + public function __construct(?PropagationContext $propagationContext = null) { $this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults(); } @@ -215,7 +215,7 @@ public function setUser($user): self $user = UserDataBag::createFromArray($user); } - if (null === $this->user) { + if ($this->user === null) { $this->user = $user; } else { $this->user = $this->user->merge($user); @@ -351,7 +351,7 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setBreadcrumb($this->breadcrumbs); } - if (null !== $this->level) { + if ($this->level !== null) { $event->setLevel($this->level); } @@ -363,10 +363,10 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op $event->setExtra(array_merge($this->extra, $event->getExtra())); } - if (null !== $this->user) { + if ($this->user !== null) { $user = $event->getUser(); - if (null === $user) { + if ($user === null) { $user = $this->user; } else { $user = $this->user->merge($user); @@ -379,19 +379,19 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op * Apply the trace context to errors if there is a Span on the Scope. * Else fallback to the propagation context. */ - if (null !== $this->span) { + if ($this->span !== null) { $event->setContext('trace', $this->span->getTraceContext()); // Apply the dynamic sampling context to errors if there is a Transaction on the Scope $transaction = $this->span->getTransaction(); - if (null !== $transaction) { + if ($transaction !== null) { $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); } } else { $event->setContext('trace', $this->propagationContext->getTraceContext()); $dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext(); - if (null === $dynamicSamplingContext && null !== $options) { + if ($dynamicSamplingContext === null && $options !== null) { $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this); } $event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext); @@ -402,14 +402,14 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op } // We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one - if (null === $hint) { + if ($hint === null) { $hint = new EventHint(); } foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { $event = $processor($event, $hint); - if (null === $event) { + if ($event === null) { return null; } @@ -448,7 +448,7 @@ public function setSpan(?Span $span): self */ public function getTransaction(): ?Transaction { - if (null !== $this->span) { + if ($this->span !== null) { return $this->span->getTransaction(); } @@ -469,10 +469,10 @@ public function setPropagationContext(PropagationContext $propagationContext): s public function __clone() { - if (null !== $this->user) { + if ($this->user !== null) { $this->user = clone $this->user; } - if (null !== $this->propagationContext) { + if ($this->propagationContext !== null) { $this->propagationContext = clone $this->propagationContext; } } diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index a9ac69ade..c3a704a53 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -40,13 +40,15 @@ private function __construct() * @param string $key the list member key * @param string $value the list member value */ - public function set(string $key, string $value): void + public function set(string $key, string $value): self { if ($this->isFrozen) { - return; + return $this; } $this->entries[$key] = $value; + + return $this; } /** @@ -73,9 +75,11 @@ public function get(string $key, ?string $default = null): ?string /** * Mark the dsc as frozen. */ - public function freeze(): void + public function freeze(): self { $this->isFrozen = true; + + return $this; } /** @@ -127,7 +131,7 @@ public static function fromHeader(string $header): self [$key, $value] = explode('=', $keyValue, 2); - if (str_starts_with($key, self::SENTRY_ENTRY_PREFIX)) { + if (mb_substr($key, 0, mb_strlen(self::SENTRY_ENTRY_PREFIX)) === self::SENTRY_ENTRY_PREFIX) { $samplingContext->set(rawurldecode(mb_substr($key, mb_strlen(self::SENTRY_ENTRY_PREFIX))), rawurldecode($value)); } } @@ -151,7 +155,7 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $samplingContext->set('trace_id', (string) $transaction->getTraceId()); $sampleRate = $transaction->getMetaData()->getSamplingRate(); - if (null !== $sampleRate) { + if ($sampleRate !== null) { $samplingContext->set('sample_rate', (string) $sampleRate); } @@ -162,29 +166,29 @@ public static function fromTransaction(Transaction $transaction, HubInterface $h $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) { $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } - if (null !== $options->getRelease()) { + if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); } - if (null !== $options->getEnvironment()) { + if ($options->getEnvironment() !== null) { $samplingContext->set('environment', $options->getEnvironment()); } } $hub->configureScope(static function (Scope $scope) use ($samplingContext): void { - if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { $samplingContext->set('user_segment', $scope->getUser()->getSegment()); } }); - if (null !== $transaction->getSampled()) { + if ($transaction->getSampled() !== null) { $samplingContext->set('sampled', $transaction->getSampled() ? 'true' : 'false'); } @@ -198,23 +202,23 @@ public static function fromOptions(Options $options, Scope $scope): self $samplingContext = new self(); $samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId()); - if (null !== $options->getTracesSampleRate()) { + if ($options->getTracesSampleRate() !== null) { $samplingContext->set('sample_rate', (string) $options->getTracesSampleRate()); } - if (null !== $options->getDsn() && null !== $options->getDsn()->getPublicKey()) { + if ($options->getDsn() !== null && $options->getDsn()->getPublicKey() !== null) { $samplingContext->set('public_key', $options->getDsn()->getPublicKey()); } - if (null !== $options->getRelease()) { + if ($options->getRelease() !== null) { $samplingContext->set('release', $options->getRelease()); } - if (null !== $options->getEnvironment()) { + if ($options->getEnvironment() !== null) { $samplingContext->set('environment', $options->getEnvironment()); } - if (null !== $scope->getUser() && null !== $scope->getUser()->getSegment()) { + if ($scope->getUser() !== null && $scope->getUser()->getSegment() !== null) { $samplingContext->set('user_segment', $scope->getUser()->getSegment()); } diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index fa534b7b6..10690abb0 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -4,7 +4,6 @@ namespace Sentry\Tracing; -use Closure; use GuzzleHttp\Exception\RequestException as GuzzleRequestException; use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\RequestInterface; @@ -13,6 +12,7 @@ use Sentry\ClientInterface; use Sentry\SentrySdk; use Sentry\State\HubInterface; + use function Sentry\getBaggage; use function Sentry\getTraceparent; @@ -21,15 +21,15 @@ */ final class GuzzleTracingMiddleware { - public static function trace(?HubInterface $hub = null): Closure + public static function trace(?HubInterface $hub = null): \Closure { - return static function (callable $handler) use ($hub): Closure { + return static function (callable $handler) use ($hub): \Closure { return static function (RequestInterface $request, array $options) use ($hub, $handler) { $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); $span = $hub->getSpan(); - if (null === $span) { + if ($span === null) { if (self::shouldAttachTracingHeaders($client, $request)) { $request = $request ->withHeader('sentry-trace', getTraceparent()) @@ -82,14 +82,14 @@ public static function trace(?HubInterface $hub = null): Closure 'http.request.method' => $request->getMethod(), 'http.request.body.size' => $request->getBody()->getSize(), ]; - if ('' !== $request->getUri()->getQuery()) { + if ($request->getUri()->getQuery() !== '') { $breadcrumbData['http.query'] = $request->getUri()->getQuery(); } - if ('' !== $request->getUri()->getFragment()) { + if ($request->getUri()->getFragment() !== '') { $breadcrumbData['http.fragment'] = $request->getUri()->getFragment(); } - if (null !== $response) { + if ($response !== null) { $childSpan->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); $breadcrumbData['http.response.status_code'] = $response->getStatusCode(); @@ -120,17 +120,13 @@ public static function trace(?HubInterface $hub = null): Closure private static function shouldAttachTracingHeaders(?ClientInterface $client, RequestInterface $request): bool { - if (null !== $client) { + if ($client !== null) { $sdkOptions = $client->getOptions(); // Check if the request destination is allow listed in the trace_propagation_targets option. if ( - null !== $sdkOptions->getTracePropagationTargets() && - // Due to BC, we treat an empty array (the default) as all hosts are allow listed - ( - [] === $sdkOptions->getTracePropagationTargets() || - \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) - ) + $sdkOptions->getTracePropagationTargets() === null + || \in_array($request->getUri()->getHost(), $sdkOptions->getTracePropagationTargets()) ) { return true; } diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 486cf9417..851ecc527 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -70,14 +70,14 @@ public function toTraceparent(): string */ public function toBaggage(): string { - if (null === $this->dynamicSamplingContext) { + if ($this->dynamicSamplingContext === null) { $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options) { + if ($options !== null) { $hub->configureScope(function (Scope $scope) use ($options) { $this->dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope); }); @@ -98,7 +98,7 @@ public function getTraceContext(): array 'span_id' => (string) $this->spanId, ]; - if (null !== $this->parentSpanId) { + if ($this->parentSpanId !== null) { $result['parent_span_id'] = (string) $this->parentSpanId; } @@ -130,9 +130,11 @@ public function getSpanId(): SpanId return $this->spanId; } - public function setSpanId(SpanId $spanId): void + public function setSpanId(SpanId $spanId): self { $this->spanId = $spanId; + + return $this; } public function getDynamicSamplingContext(): ?DynamicSamplingContext @@ -140,9 +142,11 @@ public function getDynamicSamplingContext(): ?DynamicSamplingContext return $this->dynamicSamplingContext; } - public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): void + public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplingContext): self { $this->dynamicSamplingContext = $dynamicSamplingContext; + + return $this; } private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self diff --git a/src/Tracing/SamplingContext.php b/src/Tracing/SamplingContext.php index f698286a7..bee540bfb 100644 --- a/src/Tracing/SamplingContext.php +++ b/src/Tracing/SamplingContext.php @@ -49,9 +49,11 @@ public function getParentSampled(): ?bool /** * Sets the sampling decision from the parent transaction, if any. */ - public function setParentSampled(?bool $parentSampled): void + public function setParentSampled(?bool $parentSampled): self { $this->parentSampled = $parentSampled; + + return $this; } /** @@ -59,9 +61,11 @@ public function setParentSampled(?bool $parentSampled): void * * @param array|null $additionalContext */ - public function setAdditionalContext(?array $additionalContext): void + public function setAdditionalContext(?array $additionalContext): self { $this->additionalContext = $additionalContext; + + return $this; } /** diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index 1b503482f..3db829d3a 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -85,7 +85,7 @@ class Span */ public function __construct(?SpanContext $context = null) { - if (null === $context) { + if ($context === null) { $this->traceId = TraceId::generate(); $this->spanId = SpanId::generate(); $this->startTimestamp = microtime(true); @@ -111,9 +111,11 @@ public function __construct(?SpanContext $context = null) * * @param SpanId $spanId The ID */ - public function setSpanId(SpanId $spanId): void + public function setSpanId(SpanId $spanId): self { $this->spanId = $spanId; + + return $this; } /** @@ -128,10 +130,14 @@ public function getTraceId(): TraceId * Sets the ID that determines which trace the span belongs to. * * @param TraceId $traceId The ID + * + * @return $this */ - public function setTraceId(TraceId $traceId): void + public function setTraceId(TraceId $traceId) { $this->traceId = $traceId; + + return $this; } /** @@ -146,10 +152,14 @@ public function getParentSpanId(): ?SpanId * Sets the ID that determines which span is the parent of the current one. * * @param SpanId|null $parentSpanId The ID + * + * @return $this */ - public function setParentSpanId(?SpanId $parentSpanId): void + public function setParentSpanId(?SpanId $parentSpanId) { $this->parentSpanId = $parentSpanId; + + return $this; } /** @@ -164,10 +174,14 @@ public function getStartTimestamp(): float * Sets the timestamp representing when the measuring started. * * @param float $startTimestamp The timestamp + * + * @return $this */ - public function setStartTimestamp(float $startTimestamp): void + public function setStartTimestamp(float $startTimestamp) { $this->startTimestamp = $startTimestamp; + + return $this; } /** @@ -192,10 +206,14 @@ public function getDescription(): ?string * the span but is consistent across instances of the span. * * @param string|null $description The description + * + * @return $this */ - public function setDescription(?string $description): void + public function setDescription(?string $description) { $this->description = $description; + + return $this; } /** @@ -210,10 +228,14 @@ public function getOp(): ?string * Sets a short code identifying the type of operation the span is measuring. * * @param string|null $op The short code + * + * @return $this */ - public function setOp(?string $op): void + public function setOp(?string $op) { $this->op = $op; + + return $this; } /** @@ -228,18 +250,24 @@ public function getStatus(): ?SpanStatus * Sets the status of the span/transaction. * * @param SpanStatus|null $status The status + * + * @return $this */ - public function setStatus(?SpanStatus $status): void + public function setStatus(?SpanStatus $status) { $this->status = $status; + + return $this; } /** * Sets the HTTP status code and the status of the span/transaction. * * @param int $statusCode The HTTP status code + * + * @return $this */ - public function setHttpStatus(int $statusCode): void + public function setHttpStatus(int $statusCode) { $this->tags['http.status_code'] = (string) $statusCode; @@ -248,6 +276,8 @@ public function setHttpStatus(int $statusCode): void if ($status !== SpanStatus::unknownError()) { $this->status = $status; } + + return $this; } /** @@ -261,13 +291,18 @@ public function getTags(): array } /** - * Sets a map of tags for this event. + * Sets a map of tags for this event. This method will merge the given tags with + * the existing ones. * * @param array $tags The tags + * + * @return $this */ - public function setTags(array $tags): void + public function setTags(array $tags) { $this->tags = array_merge($this->tags, $tags); + + return $this; } /** @@ -290,10 +325,14 @@ public function getSampled(): ?bool * Sets the flag determining whether this span should be sampled or not. * * @param bool $sampled Whether to sample or not this span + * + * @return $this */ - public function setSampled(?bool $sampled): void + public function setSampled(?bool $sampled) { $this->sampled = $sampled; + + return $this; } /** @@ -311,10 +350,14 @@ public function getData(): array * the existing one. * * @param array $data The data + * + * @return $this */ - public function setData(array $data): void + public function setData(array $data) { $this->data = array_merge($this->data, $data); + + return $this; } /** @@ -340,19 +383,19 @@ public function getTraceContext(): array 'trace_id' => (string) $this->traceId, ]; - if (null !== $this->parentSpanId) { + if ($this->parentSpanId !== null) { $result['parent_span_id'] = (string) $this->parentSpanId; } - if (null !== $this->description) { + if ($this->description !== null) { $result['description'] = $this->description; } - if (null !== $this->op) { + if ($this->op !== null) { $result['op'] = $this->op; } - if (null !== $this->status) { + if ($this->status !== null) { $result['status'] = (string) $this->status; } @@ -398,7 +441,7 @@ public function startChild(SpanContext $context): self $span->transaction = $this->transaction; $span->spanRecorder = $this->spanRecorder; - if (null != $span->spanRecorder) { + if ($span->spanRecorder !== null) { $span->spanRecorder->add($span); } @@ -417,10 +460,14 @@ public function getSpanRecorder(): ?SpanRecorder /** * Detaches the span recorder from this instance. + * + * @return $this */ - public function detachSpanRecorder(): void + public function detachSpanRecorder() { $this->spanRecorder = null; + + return $this; } /** @@ -438,7 +485,7 @@ public function toTraceparent(): string { $sampled = ''; - if (null !== $this->sampled) { + if ($this->sampled !== null) { $sampled = $this->sampled ? '-1' : '-0'; } @@ -452,7 +499,7 @@ public function toBaggage(): string { $transaction = $this->getTransaction(); - if (null !== $transaction) { + if ($transaction !== null) { return (string) $transaction->getDynamicSamplingContext(); } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 0aa336ee2..65df6dab9 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -6,11 +6,6 @@ class SpanContext { - /** - * @deprecated since version 3.1, to be removed in 4.0 - */ - private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?[0-9a-f]{32})?-?(?[0-9a-f]{16})?-?(?[01])?[ \\t]*$/i'; - /** * @var string|null Description of the Span */ @@ -71,9 +66,14 @@ public function getDescription(): ?string return $this->description; } - public function setDescription(?string $description): void + /** + * @return $this + */ + public function setDescription(?string $description) { $this->description = $description; + + return $this; } public function getOp(): ?string @@ -81,9 +81,14 @@ public function getOp(): ?string return $this->op; } - public function setOp(?string $op): void + /** + * @return $this + */ + public function setOp(?string $op) { $this->op = $op; + + return $this; } public function getStatus(): ?SpanStatus @@ -91,9 +96,14 @@ public function getStatus(): ?SpanStatus return $this->status; } - public function setStatus(?SpanStatus $status): void + /** + * @return $this + */ + public function setStatus(?SpanStatus $status) { $this->status = $status; + + return $this; } public function getParentSpanId(): ?SpanId @@ -101,9 +111,14 @@ public function getParentSpanId(): ?SpanId return $this->parentSpanId; } - public function setParentSpanId(?SpanId $parentSpanId): void + /** + * @return $this + */ + public function setParentSpanId(?SpanId $parentSpanId) { $this->parentSpanId = $parentSpanId; + + return $this; } public function getSampled(): ?bool @@ -111,9 +126,14 @@ public function getSampled(): ?bool return $this->sampled; } - public function setSampled(?bool $sampled): void + /** + * @return $this + */ + public function setSampled(?bool $sampled) { $this->sampled = $sampled; + + return $this; } public function getSpanId(): ?SpanId @@ -121,9 +141,14 @@ public function getSpanId(): ?SpanId return $this->spanId; } - public function setSpanId(?SpanId $spanId): void + /** + * @return $this + */ + public function setSpanId(?SpanId $spanId) { $this->spanId = $spanId; + + return $this; } public function getTraceId(): ?TraceId @@ -131,9 +156,14 @@ public function getTraceId(): ?TraceId return $this->traceId; } - public function setTraceId(?TraceId $traceId): void + /** + * @return $this + */ + public function setTraceId(?TraceId $traceId) { $this->traceId = $traceId; + + return $this; } /** @@ -146,10 +176,14 @@ public function getTags(): array /** * @param array $tags + * + * @return $this */ - public function setTags(array $tags): void + public function setTags(array $tags) { $this->tags = $tags; + + return $this; } /** @@ -162,10 +196,14 @@ public function getData(): array /** * @param array $data + * + * @return $this */ - public function setData(array $data): void + public function setData(array $data) { $this->data = $data; + + return $this; } public function getStartTimestamp(): ?float @@ -173,9 +211,14 @@ public function getStartTimestamp(): ?float return $this->startTimestamp; } - public function setStartTimestamp(?float $startTimestamp): void + /** + * @return $this + */ + public function setStartTimestamp(?float $startTimestamp) { $this->startTimestamp = $startTimestamp; + + return $this; } public function getEndTimestamp(): ?float @@ -183,42 +226,13 @@ public function getEndTimestamp(): ?float return $this->endTimestamp; } - public function setEndTimestamp(?float $endTimestamp): void - { - $this->endTimestamp = $endTimestamp; - } - /** - * Returns a context populated with the data of the given header. - * - * @param string $header The sentry-trace header from the request - * - * @return static - * - * @deprecated since version 3.1, to be removed in 4.0 + * @return $this */ - public static function fromTraceparent(string $header) + public function setEndTimestamp(?float $endTimestamp) { - @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.', __METHOD__), \E_USER_DEPRECATED); - - $context = new static(); - - if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { - return $context; - } - - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - } - - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - } - - if (isset($matches['sampled'])) { - $context->sampled = '1' === $matches['sampled']; - } + $this->endTimestamp = $endTimestamp; - return $context; + return $this; } } diff --git a/src/Tracing/SpanRecorder.php b/src/Tracing/SpanRecorder.php index 560763044..7f9f70b88 100644 --- a/src/Tracing/SpanRecorder.php +++ b/src/Tracing/SpanRecorder.php @@ -31,13 +31,15 @@ public function __construct(int $maxSpans = 1000) * Adds a span to the list of recorded spans or detaches the recorder if the * maximum number of spans to store has been reached. */ - public function add(Span $span): void + public function add(Span $span): self { if (\count($this->spans) > $this->maxSpans) { $span->detachSpanRecorder(); } else { $this->spans[] = $span; } + + return $this; } /** diff --git a/src/Tracing/SpanStatus.php b/src/Tracing/SpanStatus.php index cd0e878fc..7cad0f216 100644 --- a/src/Tracing/SpanStatus.php +++ b/src/Tracing/SpanStatus.php @@ -152,23 +152,23 @@ public static function unknownError(): self public static function createFromHttpStatusCode(int $statusCode): self { switch (true) { - case 401 === $statusCode: + case $statusCode === 401: return self::unauthenticated(); - case 403 === $statusCode: + case $statusCode === 403: return self::permissionDenied(); - case 404 === $statusCode: + case $statusCode === 404: return self::notFound(); - case 409 === $statusCode: + case $statusCode === 409: return self::alreadyExists(); - case 413 === $statusCode: + case $statusCode === 413: return self::failedPrecondition(); - case 429 === $statusCode: + case $statusCode === 429: return self::resourceExchausted(); - case 501 === $statusCode: + case $statusCode === 501: return self::unimplemented(); - case 503 === $statusCode: + case $statusCode === 503: return self::unavailable(); - case 504 === $statusCode: + case $statusCode === 504: return self::deadlineExceeded(); case $statusCode < 400: return self::ok(); diff --git a/src/Tracing/Transaction.php b/src/Tracing/Transaction.php index b4dcc09c1..4c832d369 100644 --- a/src/Tracing/Transaction.php +++ b/src/Tracing/Transaction.php @@ -38,7 +38,7 @@ final class Transaction extends Span /** * @var Profiler|null Reference instance to the {@see Profiler} */ - protected $profiler = null; + protected $profiler; /** * Span constructor. @@ -89,7 +89,7 @@ public function getMetadata(): TransactionMetadata */ public function getDynamicSamplingContext(): DynamicSamplingContext { - if (null !== $this->metadata->getDynamicSamplingContext()) { + if ($this->metadata->getDynamicSamplingContext() !== null) { return $this->metadata->getDynamicSamplingContext(); } @@ -104,28 +104,27 @@ public function getDynamicSamplingContext(): DynamicSamplingContext * * @param int $maxSpans The maximum number of spans that can be recorded */ - public function initSpanRecorder(int $maxSpans = 1000): void + public function initSpanRecorder(int $maxSpans = 1000): self { - if (null === $this->spanRecorder) { + if ($this->spanRecorder === null) { $this->spanRecorder = new SpanRecorder($maxSpans); } $this->spanRecorder->add($this); - } - public function detachSpanRecorder(): void - { - $this->spanRecorder = null; + return $this; } - public function initProfiler(): void + public function initProfiler(): self { - if (null === $this->profiler) { + if ($this->profiler === null) { $client = $this->hub->getClient(); - $options = null !== $client ? $client->getOptions() : null; + $options = $client !== null ? $client->getOptions() : null; $this->profiler = new Profiler($options); } + + return $this; } public function getProfiler(): ?Profiler @@ -133,9 +132,11 @@ public function getProfiler(): ?Profiler return $this->profiler; } - public function detachProfiler(): void + public function detachProfiler(): self { $this->profiler = null; + + return $this; } /** @@ -143,26 +144,26 @@ public function detachProfiler(): void */ public function finish(?float $endTimestamp = null): ?EventId { - if (null !== $this->profiler) { + if ($this->profiler !== null) { $this->profiler->stop(); } - if (null !== $this->endTimestamp) { + if ($this->endTimestamp !== null) { // Transaction was already finished once and we don't want to re-flush it return null; } parent::finish($endTimestamp); - if (true !== $this->sampled) { + if ($this->sampled !== true) { return null; } $finishedSpans = []; - if (null !== $this->spanRecorder) { + if ($this->spanRecorder !== null) { foreach ($this->spanRecorder->getSpans() as $span) { - if ($span->getSpanId() !== $this->getSpanId() && null !== $span->getEndTimestamp()) { + if ($span->getSpanId() !== $this->getSpanId() && $span->getEndTimestamp() !== null) { $finishedSpans[] = $span; } } @@ -178,9 +179,9 @@ public function finish(?float $endTimestamp = null): ?EventId $event->setSdkMetadata('dynamic_sampling_context', $this->getDynamicSamplingContext()); $event->setSdkMetadata('transaction_metadata', $this->getMetadata()); - if (null !== $this->profiler) { + if ($this->profiler !== null) { $profile = $this->profiler->getProfile(); - if (null !== $profile) { + if ($profile !== null) { $event->setSdkMetadata('profile', $profile); } } diff --git a/src/Tracing/TransactionContext.php b/src/Tracing/TransactionContext.php index dd70441dd..e452de72a 100644 --- a/src/Tracing/TransactionContext.php +++ b/src/Tracing/TransactionContext.php @@ -55,9 +55,11 @@ public function getName(): string * * @param string $name The name */ - public function setName(string $name): void + public function setName(string $name): self { $this->name = $name; + + return $this; } /** @@ -73,9 +75,11 @@ public function getParentSampled(): ?bool * * @param bool|null $parentSampled The decision */ - public function setParentSampled(?bool $parentSampled): void + public function setParentSampled(?bool $parentSampled): self { $this->parentSampled = $parentSampled; + + return $this; } /** @@ -91,9 +95,11 @@ public function getMetadata(): TransactionMetadata * * @param TransactionMetadata $metadata The transaction metadata */ - public function setMetadata(TransactionMetadata $metadata): void + public function setMetadata(TransactionMetadata $metadata): self { $this->metadata = $metadata; + + return $this; } /** @@ -101,39 +107,11 @@ public function setMetadata(TransactionMetadata $metadata): void * * @param TransactionSource $transactionSource The transaction source */ - public function setSource(TransactionSource $transactionSource): void + public function setSource(TransactionSource $transactionSource): self { $this->metadata->setSource($transactionSource); - } - - /** - * Returns a context populated with the data of the given header. - * - * @param string $header The sentry-trace header from the request - * - * @deprecated since version 3.9, to be removed in 4.0 - */ - public static function fromSentryTrace(string $header): self - { - $context = new self(); - - if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { - return $context; - } - - if (!empty($matches['trace_id'])) { - $context->traceId = new TraceId($matches['trace_id']); - } - if (!empty($matches['span_id'])) { - $context->parentSpanId = new SpanId($matches['span_id']); - } - - if (isset($matches['sampled'])) { - $context->parentSampled = '1' === $matches['sampled']; - } - - return $context; + return $this; } /** @@ -175,7 +153,7 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag } if (isset($matches['sampled'])) { - $context->parentSampled = '1' === $matches['sampled']; + $context->parentSampled = $matches['sampled'] === '1'; $hasSentryTrace = true; } } diff --git a/src/Tracing/TransactionMetadata.php b/src/Tracing/TransactionMetadata.php index a217fc831..2a10923bc 100644 --- a/src/Tracing/TransactionMetadata.php +++ b/src/Tracing/TransactionMetadata.php @@ -49,9 +49,11 @@ public function getSamplingRate() /** * @param float|int|null $samplingRate */ - public function setSamplingRate($samplingRate): void + public function setSamplingRate($samplingRate): self { $this->samplingRate = $samplingRate; + + return $this; } public function getDynamicSamplingContext(): ?DynamicSamplingContext @@ -59,9 +61,11 @@ public function getDynamicSamplingContext(): ?DynamicSamplingContext return $this->dynamicSamplingContext; } - public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): void + public function setDynamicSamplingContext(?DynamicSamplingContext $dynamicSamplingContext): self { $this->dynamicSamplingContext = $dynamicSamplingContext; + + return $this; } public function getSource(): ?TransactionSource @@ -69,8 +73,10 @@ public function getSource(): ?TransactionSource return $this->source; } - public function setSource(?TransactionSource $source): void + public function setSource(?TransactionSource $source): self { $this->source = $source; + + return $this; } } diff --git a/src/Transport/DefaultTransportFactory.php b/src/Transport/DefaultTransportFactory.php deleted file mode 100644 index 2d0c1f0b8..000000000 --- a/src/Transport/DefaultTransportFactory.php +++ /dev/null @@ -1,74 +0,0 @@ -streamFactory = $streamFactory; - $this->requestFactory = $requestFactory; - $this->httpClientFactory = $httpClientFactory; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function create(Options $options): TransportInterface - { - if (null === $options->getDsn()) { - return new NullTransport(); - } - - return new HttpTransport( - $options, - $this->httpClientFactory->create($options), - $this->streamFactory, - $this->requestFactory, - new PayloadSerializer($options), - $this->logger - ); - } -} diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 9aa844edb..f666af971 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -4,50 +4,29 @@ namespace Sentry\Transport; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Promise\RejectedPromise; -use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; -use Sentry\EventType; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Request; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; /** - * This transport sends the events using a syncronous HTTP client that will - * delay sending of the requests until the shutdown of the application. - * - * @author Stefano Arlandini + * @internal */ -final class HttpTransport implements TransportInterface +class HttpTransport implements TransportInterface { /** - * @var Options The Sentry client options + * @var Options */ private $options; /** - * @var HttpAsyncClientInterface The HTTP client + * @var HttpClientInterface The HTTP client */ private $httpClient; - /** - * @var StreamFactoryInterface The PSR-7 stream factory - */ - private $streamFactory; - - /** - * @var RequestFactoryInterface The PSR-7 request factory - */ - private $requestFactory; - /** * @var PayloadSerializerInterface The event serializer */ @@ -64,27 +43,19 @@ final class HttpTransport implements TransportInterface private $rateLimiter; /** - * Constructor. - * - * @param Options $options The Sentry client configuration - * @param HttpAsyncClientInterface $httpClient The HTTP client - * @param StreamFactoryInterface $streamFactory The PSR-7 stream factory - * @param RequestFactoryInterface $requestFactory The PSR-7 request factory + * @param Options $options The options + * @param HttpClientInterface $httpClient The HTTP client * @param PayloadSerializerInterface $payloadSerializer The event serializer * @param LoggerInterface|null $logger An instance of a PSR-3 logger */ public function __construct( Options $options, - HttpAsyncClientInterface $httpClient, - StreamFactoryInterface $streamFactory, - RequestFactoryInterface $requestFactory, + HttpClientInterface $httpClient, PayloadSerializerInterface $payloadSerializer, ?LoggerInterface $logger = null ) { $this->options = $options; $this->httpClient = $httpClient; - $this->streamFactory = $streamFactory; - $this->requestFactory = $requestFactory; $this->payloadSerializer = $payloadSerializer; $this->logger = $logger ?? new NullLogger(); $this->rateLimiter = new RateLimiter($this->logger); @@ -93,65 +64,56 @@ public function __construct( /** * {@inheritdoc} */ - public function send(Event $event): PromiseInterface + public function send(Event $event): Result { - $dsn = $this->options->getDsn(); - - if (null === $dsn) { - throw new \RuntimeException(sprintf('The DSN option must be set to use the "%s" transport.', self::class)); + if ($this->options->getDsn() === null) { + return new Result(ResultStatus::skipped(), $event); } $eventType = $event->getType(); - if ($this->rateLimiter->isRateLimited($eventType)) { $this->logger->warning( sprintf('Rate limit exceeded for sending requests of type "%s".', (string) $eventType), ['event' => $event] ); - return new RejectedPromise(new Response(ResponseStatus::rateLimit(), $event)); + return new Result(ResultStatus::rateLimit()); } - if ( - $this->options->isTracingEnabled() || - EventType::transaction() === $eventType || - EventType::checkIn() === $eventType - ) { - $request = $this->requestFactory->createRequest('POST', $dsn->getEnvelopeApiEndpointUrl()) - ->withHeader('Content-Type', 'application/x-sentry-envelope') - ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); - } else { - $request = $this->requestFactory->createRequest('POST', $dsn->getStoreApiEndpointUrl()) - ->withHeader('Content-Type', 'application/json') - ->withBody($this->streamFactory->createStream($this->payloadSerializer->serialize($event))); - } + $request = new Request(); + $request->setStringBody($this->payloadSerializer->serialize($event)); try { - /** @var ResponseInterface $response */ - $response = $this->httpClient->sendAsyncRequest($request)->wait(); + $response = $this->httpClient->sendRequest($request, $this->options); } catch (\Throwable $exception) { $this->logger->error( sprintf('Failed to send the event to Sentry. Reason: "%s".', $exception->getMessage()), ['exception' => $exception, 'event' => $event] ); - return new RejectedPromise(new Response(ResponseStatus::failed(), $event)); + return new Result(ResultStatus::failed()); } - $sendResponse = $this->rateLimiter->handleResponse($event, $response); + $response = $this->rateLimiter->handleResponse($event, $response); + if ($response->isSuccess()) { + return new Result(ResultStatus::success(), $event); + } - if (ResponseStatus::success() === $sendResponse->getStatus()) { - return new FulfilledPromise($sendResponse); + if ($response->hasError()) { + $this->logger->error( + sprintf('Failed to send the event to Sentry. Reason: "%s".', $response->getError()), + ['event' => $event] + ); } - return new RejectedPromise($sendResponse); + return new Result(ResultStatus::createFromHttpStatusCode($response->getStatusCode())); } /** * {@inheritdoc} */ - public function close(?int $timeout = null): PromiseInterface + public function close(?int $timeout = null): Result { - return new FulfilledPromise(true); + return new Result(ResultStatus::success()); } } diff --git a/src/Transport/NullTransport.php b/src/Transport/NullTransport.php deleted file mode 100644 index 0c067e104..000000000 --- a/src/Transport/NullTransport.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ -final class NullTransport implements TransportInterface -{ - /** - * {@inheritdoc} - */ - public function send(Event $event): PromiseInterface - { - return new FulfilledPromise(new Response(ResponseStatus::skipped(), $event)); - } - - /** - * {@inheritdoc} - */ - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } -} diff --git a/src/Transport/RateLimiter.php b/src/Transport/RateLimiter.php index 4f75f234c..f21d1e83b 100644 --- a/src/Transport/RateLimiter.php +++ b/src/Transport/RateLimiter.php @@ -4,13 +4,11 @@ namespace Sentry\Transport; -use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Event; use Sentry\EventType; -use Sentry\Response; -use Sentry\ResponseStatus; +use Sentry\HttpClient\Response; final class RateLimiter { @@ -47,10 +45,8 @@ public function __construct(?LoggerInterface $logger = null) $this->logger = $logger ?? new NullLogger(); } - public function handleResponse(Event $event, ResponseInterface $response): Response + public function handleResponse(Event $event, Response $response): Response { - $sendResponse = new Response(ResponseStatus::createFromHttpStatusCode($response->getStatusCode()), $event); - if ($this->handleRateLimit($response)) { $eventType = $event->getType(); $disabledUntil = $this->getDisabledUntil($eventType); @@ -61,7 +57,7 @@ public function handleResponse(Event $event, ResponseInterface $response): Respo ); } - return $sendResponse; + return $response; } public function isRateLimited(EventType $eventType): bool @@ -82,7 +78,7 @@ private function getDisabledUntil(EventType $eventType): int return max($this->rateLimits['all'] ?? 0, $this->rateLimits[$category] ?? 0); } - private function handleRateLimit(ResponseInterface $response): bool + private function handleRateLimit(Response $response): bool { $now = time(); @@ -113,13 +109,13 @@ private function handleRateLimit(ResponseInterface $response): bool private function parseRetryAfterHeader(int $currentTime, string $header): int { - if (1 === preg_match('/^\d+$/', $header)) { + if (preg_match('/^\d+$/', $header) === 1) { return (int) $header; } $headerDate = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC1123, $header); - if (false !== $headerDate && $headerDate->getTimestamp() >= $currentTime) { + if ($headerDate !== false && $headerDate->getTimestamp() >= $currentTime) { return $headerDate->getTimestamp() - $currentTime; } diff --git a/src/Response.php b/src/Transport/Result.php similarity index 74% rename from src/Response.php rename to src/Transport/Result.php index 3a2d54173..a4a6b8238 100644 --- a/src/Response.php +++ b/src/Transport/Result.php @@ -2,16 +2,20 @@ declare(strict_types=1); -namespace Sentry; +namespace Sentry\Transport; + +use Sentry\Event; /** * This class contains the details of the sending operation of an event, e.g. * if it was sent successfully or if it was skipped because of some reason. + * + * @internal */ -final class Response +class Result { /** - * @var ResponseStatus The status of the sending operation of the event + * @var ResultStatus The status of the sending operation of the event */ private $status; @@ -21,7 +25,7 @@ final class Response */ private $event; - public function __construct(ResponseStatus $status, ?Event $event = null) + public function __construct(ResultStatus $status, ?Event $event = null) { $this->status = $status; $this->event = $event; @@ -30,7 +34,7 @@ public function __construct(ResponseStatus $status, ?Event $event = null) /** * Gets the status of the sending operation of the event. */ - public function getStatus(): ResponseStatus + public function getStatus(): ResultStatus { return $this->status; } diff --git a/src/ResponseStatus.php b/src/Transport/ResultStatus.php similarity index 96% rename from src/ResponseStatus.php rename to src/Transport/ResultStatus.php index 2a2292a4f..130bc56a5 100644 --- a/src/ResponseStatus.php +++ b/src/Transport/ResultStatus.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Sentry; +namespace Sentry\Transport; /** * This enum represents all possible reasons an event sending operation succeeded * or failed. */ -final class ResponseStatus implements \Stringable +class ResultStatus implements \Stringable { /** * @var string The value of the enum instance @@ -94,7 +94,7 @@ public static function createFromHttpStatusCode(int $statusCode): self switch (true) { case $statusCode >= 200 && $statusCode < 300: return self::success(); - case 429 === $statusCode: + case $statusCode === 429: return self::rateLimit(); case $statusCode >= 400 && $statusCode < 500: return self::invalid(); diff --git a/src/Transport/TransportFactoryInterface.php b/src/Transport/TransportFactoryInterface.php deleted file mode 100644 index e38f12b1f..000000000 --- a/src/Transport/TransportFactoryInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ interface TransportInterface { - /** - * Sends the given event. - * - * @param Event $event The event - * - * @return PromiseInterface Returns the ID of the event or `null` if it failed to be sent - */ - public function send(Event $event): PromiseInterface; + public function send(Event $event): Result; - /** - * Waits until all pending requests have been sent or the timeout expires. - * - * @param int|null $timeout Maximum time in seconds before the sending - * operation is interrupted - */ - public function close(?int $timeout = null): PromiseInterface; + public function close(?int $timeout = null): Result; } diff --git a/src/UserDataBag.php b/src/UserDataBag.php index fa043b571..5e08dbb5b 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -130,13 +130,15 @@ public function getId() * * @param string|int|null $id The ID */ - public function setId($id): void + public function setId($id): self { - if (null !== $id && !\is_string($id) && !\is_int($id)) { + if ($id !== null && !\is_string($id) && !\is_int($id)) { throw new \UnexpectedValueException(sprintf('Expected an integer or string value for the $id argument. Got: "%s".', get_debug_type($id))); } $this->id = $id; + + return $this; } /** @@ -152,9 +154,11 @@ public function getUsername(): ?string * * @param string|null $username The username */ - public function setUsername(?string $username): void + public function setUsername(?string $username): self { $this->username = $username; + + return $this; } /** @@ -170,9 +174,11 @@ public function getEmail(): ?string * * @param string|null $email The email */ - public function setEmail(?string $email): void + public function setEmail(?string $email): self { $this->email = $email; + + return $this; } /** @@ -188,9 +194,11 @@ public function getSegment(): ?string * * @param string|null $segment The segment */ - public function setSegment(?string $segment): void + public function setSegment(?string $segment): self { $this->segment = $segment; + + return $this; } /** @@ -206,13 +214,15 @@ public function getIpAddress(): ?string * * @param string|null $ipAddress The ip address */ - public function setIpAddress(?string $ipAddress): void + public function setIpAddress(?string $ipAddress): self { - if (null !== $ipAddress && false === filter_var($ipAddress, \FILTER_VALIDATE_IP)) { + if ($ipAddress !== null && filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) { throw new \InvalidArgumentException(sprintf('The "%s" value is not a valid IP address.', $ipAddress)); } $this->ipAddress = $ipAddress; + + return $this; } /** @@ -231,9 +241,11 @@ public function getMetadata(): array * @param string $name The name of the field * @param mixed $value The value */ - public function setMetadata(string $name, $value): void + public function setMetadata(string $name, $value): self { $this->metadata[$name] = $value; + + return $this; } /** @@ -241,9 +253,11 @@ public function setMetadata(string $name, $value): void * * @param string $name The name of the field */ - public function removeMetadata(string $name): void + public function removeMetadata(string $name): self { unset($this->metadata[$name]); + + return $this; } /** diff --git a/src/Util/Http.php b/src/Util/Http.php new file mode 100644 index 000000000..efe903ad7 --- /dev/null +++ b/src/Util/Http.php @@ -0,0 +1,56 @@ +getPublicKey(), + ]; + + return [ + 'Content-Type: application/x-sentry-envelope', + 'X-Sentry-Auth: Sentry ' . implode(', ', $authHeader), + ]; + } + + /** + * @param string[][] $headers + * + * @param-out string[][] $headers + */ + public static function parseResponseHeaders(string $headerLine, array &$headers): int + { + if (strpos($headerLine, ':') === false) { + return \strlen($headerLine); + } + + [$name, $value] = explode(':', trim($headerLine), 2); + + $name = trim($name); + $value = trim($value); + + if (isset($headers[$name])) { + $headers[$name][] = $value; + } else { + $headers[$name] = (array) $value; + } + + return \strlen($headerLine); + } +} diff --git a/src/Util/JSON.php b/src/Util/JSON.php index 3c9360c69..9c568c52f 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -36,9 +36,9 @@ public static function encode($data, int $options = 0, int $maxDepth = 512): str $allowedErrors = [\JSON_ERROR_NONE, \JSON_ERROR_RECURSION, \JSON_ERROR_INF_OR_NAN, \JSON_ERROR_UNSUPPORTED_TYPE]; - $encounteredAnyError = \JSON_ERROR_NONE !== json_last_error(); + $encounteredAnyError = json_last_error() !== \JSON_ERROR_NONE; - if (($encounteredAnyError && ('null' === $encodedData || false === $encodedData)) || !\in_array(json_last_error(), $allowedErrors, true)) { + if (($encounteredAnyError && ($encodedData === 'null' || $encodedData === false)) || !\in_array(json_last_error(), $allowedErrors, true)) { throw new JsonException(sprintf('Could not encode value into JSON format. Error was: "%s".', json_last_error_msg())); } @@ -58,7 +58,7 @@ public static function decode(string $data) { $decodedData = json_decode($data, true); - if (\JSON_ERROR_NONE !== json_last_error()) { + if (json_last_error() !== \JSON_ERROR_NONE) { throw new JsonException(sprintf('Could not decode value from JSON format. Error was: "%s".', json_last_error_msg())); } diff --git a/src/Util/PrefixStripper.php b/src/Util/PrefixStripper.php index 35c261312..eccee711b 100644 --- a/src/Util/PrefixStripper.php +++ b/src/Util/PrefixStripper.php @@ -13,12 +13,12 @@ trait PrefixStripper */ protected function stripPrefixFromFilePath(?Options $options, string $filePath): string { - if (null === $options) { + if ($options === null) { return $filePath; } foreach ($options->getPrefixes() as $prefix) { - if (str_starts_with($filePath, $prefix)) { + if (mb_substr($filePath, 0, mb_strlen($prefix)) === $prefix) { return mb_substr($filePath, mb_strlen($prefix)); } } diff --git a/src/Util/SentryUid.php b/src/Util/SentryUid.php index 50c117f69..7adbc1cca 100644 --- a/src/Util/SentryUid.php +++ b/src/Util/SentryUid.php @@ -17,7 +17,7 @@ final class SentryUid public static function generate(): string { if (\function_exists('uuid_create')) { - return strtolower(str_replace('-', '', uuid_create(UUID_TYPE_RANDOM))); + return strtolower(str_replace('-', '', uuid_create(\UUID_TYPE_RANDOM))); } $uuid = bin2hex(random_bytes(16)); diff --git a/src/functions.php b/src/functions.php index 4956b1226..9dba70ab1 100644 --- a/src/functions.php +++ b/src/functions.php @@ -85,11 +85,20 @@ function captureCheckIn(string $slug, CheckInStatus $status, $duration = null, ? * will be added to subsequent events to provide more context on user's * actions prior to an error or crash. * - * @param Breadcrumb $breadcrumb The breadcrumb to record + * @param Breadcrumb|string $category The category of the breadcrumb, can be a Breadcrumb instance as well (in which case the other parameters are ignored) + * @param string|null $message Breadcrumb message + * @param array $metadata Additional information about the breadcrumb + * @param string $level The error level of the breadcrumb + * @param string $type The type of the breadcrumb + * @param float|null $timestamp Optional timestamp of the breadcrumb */ -function addBreadcrumb(Breadcrumb $breadcrumb): void +function addBreadcrumb($category, ?string $message = null, array $metadata = [], string $level = Breadcrumb::LEVEL_INFO, string $type = Breadcrumb::TYPE_DEFAULT, ?float $timestamp = null): void { - SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb); + SentrySdk::getCurrentHub()->addBreadcrumb( + $category instanceof Breadcrumb + ? $category + : new Breadcrumb($level, $type, $category, $message, $metadata, $timestamp) + ); } /** @@ -165,7 +174,7 @@ function trace(callable $trace, SpanContext $context) // If there's a span set on the scope there is a transaction // active currently. If that is the case we create a child span // and set it on the scope. Otherwise we only execute the callable - if (null !== $parentSpan) { + if ($parentSpan !== null) { $span = $parentSpan->startChild($context); $scope->setSpan($span); @@ -194,12 +203,12 @@ function getTraceparent(): string $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options && $options->isTracingEnabled()) { + if ($options !== null && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); - if (null !== $span) { + if ($span !== null) { return $span->toTraceparent(); } } @@ -224,12 +233,12 @@ function getBaggage(): string $hub = SentrySdk::getCurrentHub(); $client = $hub->getClient(); - if (null !== $client) { + if ($client !== null) { $options = $client->getOptions(); - if (null !== $options && $options->isTracingEnabled()) { + if ($options !== null && $options->isTracingEnabled()) { $span = SentrySdk::getCurrentHub()->getSpan(); - if (null !== $span) { + if ($span !== null) { return $span->toBaggage(); } } diff --git a/tests/Benchmark/SpanBench.php b/tests/Benchmark/SpanBench.php index a4414373b..0e0878d92 100644 --- a/tests/Benchmark/SpanBench.php +++ b/tests/Benchmark/SpanBench.php @@ -9,6 +9,8 @@ use Sentry\Tracing\Span; use Sentry\Tracing\TransactionContext; +use function Sentry\continueTrace; + final class SpanBench { /** @@ -23,13 +25,14 @@ final class SpanBench public function __construct() { - $this->context = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); - $this->contextWithTimestamp = TransactionContext::fromSentryTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0'); + $this->context = continueTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''); + $this->contextWithTimestamp = continueTrace('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', ''); $this->contextWithTimestamp->setStartTimestamp(microtime(true)); } /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructor(): void @@ -39,6 +42,7 @@ public function benchConstructor(): void /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructorWithInjectedContext(): void @@ -48,6 +52,7 @@ public function benchConstructorWithInjectedContext(): void /** * @Revs(100000) + * * @Iterations(10) */ public function benchConstructorWithInjectedContextAndStartTimestamp(): void diff --git a/tests/ClientBuilderTest.php b/tests/ClientBuilderTest.php index 153986970..ae74beabb 100644 --- a/tests/ClientBuilderTest.php +++ b/tests/ClientBuilderTest.php @@ -8,10 +8,15 @@ use Sentry\Client; use Sentry\ClientBuilder; use Sentry\Event; +use Sentry\HttpClient\HttpClient; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Request; +use Sentry\HttpClient\Response; use Sentry\Integration\IntegrationInterface; use Sentry\Options; use Sentry\Transport\HttpTransport; -use Sentry\Transport\NullTransport; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; final class ClientBuilderTest extends TestCase @@ -24,24 +29,6 @@ public function testGetOptions() $this->assertSame($options, $clientBuilder->getOptions()); } - public function testHttpTransportIsUsedWhenServerIsConfigured(): void - { - $clientBuilder = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/sentry/1']); - - $transport = $this->getTransport($clientBuilder->getClient()); - - $this->assertInstanceOf(HttpTransport::class, $transport); - } - - public function testNullTransportIsUsedWhenNoServerIsConfigured(): void - { - $clientBuilder = new ClientBuilder(); - - $transport = $this->getTransport($clientBuilder->getClient()); - - $this->assertInstanceOf(NullTransport::class, $transport); - } - public function testClientBuilderFallbacksToDefaultSdkIdentifierAndVersion(): void { $callbackCalled = false; @@ -92,15 +79,39 @@ public function testCreateWithNoOptionsIsTheSameAsDefaultOptions(): void ); } - private function getTransport(Client $client): TransportInterface + public function testDefaultHttpClientAndTransport() + { + $options = new Options(); + $clientBuilder = new ClientBuilder($options); + + $this->assertInstanceOf(HttpClient::class, $clientBuilder->getHttpClient()); + $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); + } + + public function testSettingCustomHttpClinet() + { + $httpClient = new CustomHttpClient(); + + $options = new Options([ + 'http_client' => $httpClient, + ]); + $clientBuilder = new ClientBuilder($options); + + $this->assertSame($httpClient, $clientBuilder->getHttpClient()); + $this->assertInstanceOf(HttpTransport::class, $clientBuilder->getTransport()); + } + + public function testSettingCustomTransport() { - $property = new \ReflectionProperty(Client::class, 'transport'); + $transport = new CustomTransport(); - $property->setAccessible(true); - $value = $property->getValue($client); - $property->setAccessible(false); + $options = new Options([ + 'transport' => $transport, + ]); + $clientBuilder = new ClientBuilder($options); - return $value; + $this->assertInstanceOf(HttpClient::class, $clientBuilder->getHttpClient()); + $this->assertSame($transport, $clientBuilder->getTransport()); } } @@ -110,3 +121,24 @@ public function setupOnce(): void { } } + +final class CustomHttpClient implements HttpClientInterface +{ + public function sendRequest(Request $request, Options $options): Response + { + return new Response(0, [], ''); + } +} + +final class CustomTransport implements TransportInterface +{ + public function send(Event $event): Result + { + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 749b25836..30d2842a5 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,8 +4,6 @@ namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -18,15 +16,13 @@ use Sentry\Frame; use Sentry\Integration\IntegrationInterface; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; -use Sentry\Serializer\SerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; use Sentry\State\Scope; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Tests\Fixtures\code\CustomException; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; @@ -73,7 +69,6 @@ public function setupOnce(): void null, null, null, - null, $logger ); @@ -94,12 +89,12 @@ public function testCaptureMessage(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureMessage('foo', Severity::fatal())); @@ -145,12 +140,12 @@ public function testCaptureException(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureException($exception)); @@ -207,12 +202,12 @@ public function testCaptureEvent(array $options, Event $event, Event $expectedEv $transport->expects($this->once()) ->method('send') ->with($expectedEvent) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create($options) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertSame($event->getId(), $client->captureEvent($event)); @@ -222,7 +217,6 @@ public static function captureEventDataProvider(): \Generator { $event = Event::createEvent(); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setServerName('example.com'); $expectedEvent->setRelease('0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'); $expectedEvent->setEnvironment('development'); @@ -246,7 +240,6 @@ public static function captureEventDataProvider(): \Generator $event->setTags(['context' => 'production']); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setTags(['context' => 'production', 'ios_version' => '14.0']); yield 'Options set && event properties set => event properties override options' => [ @@ -264,7 +257,6 @@ public static function captureEventDataProvider(): \Generator $event->setServerName('example.com'); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setEnvironment('production'); yield 'Environment option set to null && no event property set => fallback to default value' => [ @@ -279,7 +271,6 @@ public static function captureEventDataProvider(): \Generator $event->setExceptions([new ExceptionDataBag(new \ErrorException())]); $expectedEvent = clone $event; - $expectedEvent->setLogger('php'); $expectedEvent->setEnvironment('production'); yield 'Error level is set && exception is instance of ErrorException => preserve the error level set by the user' => [ @@ -321,22 +312,22 @@ public function testCaptureEventAttachesStacktraceAccordingToAttachStacktraceOpt $transport->expects($this->once()) ->method('send') ->with($this->callback(static function (Event $event) use ($shouldAttachStacktrace): bool { - if ($shouldAttachStacktrace && null === $event->getStacktrace()) { + if ($shouldAttachStacktrace && $event->getStacktrace() === null) { return false; } - if (!$shouldAttachStacktrace && null !== $event->getStacktrace()) { + if (!$shouldAttachStacktrace && $event->getStacktrace() !== null) { return false; } return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => $attachStacktraceOption]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), $hint)); @@ -386,12 +377,12 @@ public function testCaptureEventPrefersExplicitStacktrace(): void ->with($this->callback(static function (Event $event) use ($stacktrace): bool { return $stacktrace === $event->getStacktrace(); })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureEvent(Event::createEvent(), EventHint::fromArray([ @@ -413,12 +404,12 @@ public function testCaptureLastError(): void return true; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); @trigger_error('foo', \E_USER_NOTICE); @@ -464,7 +455,7 @@ public function testCaptureLastErrorDoesNothingWhenThereIsNoError(): void ->with($this->anything()); $client = ClientBuilder::create(['dsn' => 'http://public:secret@example.com/1']) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); error_clear_last(); @@ -553,7 +544,7 @@ public function testProcessEventDiscardsEventWhenSampleRateOptionIsZero(): void })); $client = ClientBuilder::create(['sample_rate' => 0]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->setLogger($logger) ->getClient(); @@ -568,7 +559,7 @@ public function testProcessEventCapturesEventWhenSampleRateOptionIsAboveZero(): ->with($this->anything()); $client = ClientBuilder::create(['sample_rate' => 1]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $client->captureEvent(Event::createEvent()); @@ -597,6 +588,29 @@ public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void $client->captureException($exception); } + public function testProcessEventDiscardsEventWhenParentHierarchyOfIgnoreExceptionsMatches(): void + { + $exception = new CustomException('Some foo error'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_exceptions' => [\RuntimeException::class], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureException($exception); + } + public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): void { $event = Event::createTransaction(); @@ -698,14 +712,14 @@ public function testAttachStacktrace(): void ->with($this->callback(function (Event $event): bool { $result = $event->getStacktrace(); - return null !== $result; + return $result !== null; })) - ->willReturnCallback(static function (Event $event): FulfilledPromise { - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); + ->willReturnCallback(static function (Event $event): Result { + return new Result(ResultStatus::success(), $event); }); $client = ClientBuilder::create(['attach_stacktrace' => true]) - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); $this->assertNotNull($client->captureMessage('test')); @@ -718,16 +732,15 @@ public function testFlush(): void $transport->expects($this->once()) ->method('close') ->with(10) - ->willReturn(new FulfilledPromise(true)); + ->willReturn(new Result(ResultStatus::success())); $client = ClientBuilder::create() - ->setTransportFactory($this->createTransportFactory($transport)) + ->setTransport($transport) ->getClient(); - $promise = $client->flush(10); + $response = $client->flush(10); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); + $this->assertSame(ResultStatus::success(), $response->getStatus()); } public function testBuildEventInCLIDoesntSetTransaction(): void @@ -747,7 +760,6 @@ public function testBuildEventInCLIDoesntSetTransaction(): void $transport, 'sentry.sdk.identifier', '1.2.3', - $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class) ); @@ -786,7 +798,6 @@ public function testBuildEventWithException(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -821,7 +832,6 @@ public function testBuildEventWithExceptionAndMechansim(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -850,7 +860,6 @@ public function testBuildWithErrorException(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -889,7 +898,6 @@ public function testBuildWithStacktrace(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -925,7 +933,6 @@ public function testBuildWithCustomStacktrace(): void $transport, 'sentry.sdk.identifier', '1.2.3', - new Serializer($options), $this->createMock(RepresentationSerializerInterface::class) ); @@ -947,7 +954,6 @@ public function testGetCspReportUrl(array $options, ?string $expectedUrl): void $this->createMock(TransportInterface::class), 'sentry.sdk.identifier', '1.2.3', - $this->createMock(SerializerInterface::class), $this->createMock(RepresentationSerializerInterface::class) ); @@ -992,24 +998,4 @@ public static function getCspReportUrlDataProvider(): \Generator 'https://example.com/api/1/security/?sentry_key=public&sentry_release=dev-release&sentry_environment=development', ]; } - - private function createTransportFactory(TransportInterface $transport): TransportFactoryInterface - { - return new class($transport) implements TransportFactoryInterface { - /** - * @var TransportInterface - */ - private $transport; - - public function __construct(TransportInterface $transport) - { - $this->transport = $transport; - } - - public function create(Options $options): TransportInterface - { - return $this->transport; - } - }; - } } diff --git a/tests/DsnTest.php b/tests/DsnTest.php index 0ebf8ef42..724d00538 100644 --- a/tests/DsnTest.php +++ b/tests/DsnTest.php @@ -21,7 +21,6 @@ public function testCreateFromString( string $expectedHost, int $expectedPort, string $expectedPublicKey, - ?string $expectedSecretKey, string $expectedProjectId, string $expectedPath ): void { @@ -31,7 +30,6 @@ public function testCreateFromString( $this->assertSame($expectedHost, $dsn->getHost()); $this->assertSame($expectedPort, $dsn->getPort()); $this->assertSame($expectedPublicKey, $dsn->getPublicKey()); - $this->assertSame($expectedSecretKey, $dsn->getSecretKey()); $this->assertSame($expectedProjectId, $dsn->getProjectId(true)); $this->assertSame($expectedPath, $dsn->getPath()); } @@ -44,7 +42,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '/sentry', ]; @@ -55,7 +52,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '', ]; @@ -66,7 +62,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - 'secret', '1', '', ]; @@ -77,7 +72,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 80, 'public', - null, '1', '', ]; @@ -88,7 +82,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 8080, 'public', - null, '1', '', ]; @@ -99,7 +92,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 443, 'public', - null, '1', '', ]; @@ -110,7 +102,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 443, 'public', - null, '1', '', ]; @@ -121,7 +112,6 @@ public static function createFromStringDataProvider(): \Generator 'example.com', 4343, 'public', - null, '1', '', ]; @@ -155,11 +145,6 @@ public static function createFromStringThrowsExceptionIfValueIsInvalidDataProvid 'The "http://:secret@example.com/sentry/1" DSN must contain a scheme, a host, a user and a path component.', ]; - yield 'missing secret key' => [ - 'http://public:@example.com/sentry/1', - 'The "http://public:@example.com/sentry/1" DSN must contain a valid secret key.', - ]; - yield 'missing host' => [ '/sentry/1', 'The "/sentry/1" DSN must contain a scheme, a host, a user and a path component.', @@ -176,16 +161,6 @@ public static function createFromStringThrowsExceptionIfValueIsInvalidDataProvid ]; } - /** - * @dataProvider getStoreApiEndpointUrlDataProvider - */ - public function testGetStoreApiEndpointUrl(string $value, string $expectedUrl): void - { - $dsn = Dsn::createFromString($value); - - $this->assertSame($expectedUrl, $dsn->getStoreApiEndpointUrl()); - } - public static function getStoreApiEndpointUrlDataProvider(): \Generator { yield [ @@ -264,23 +239,10 @@ public static function toStringDataProvider(): array { return [ ['http://public@example.com/sentry/1'], - ['http://public:secret@example.com/sentry/1'], ['http://public@example.com/1'], ['http://public@example.com:8080/sentry/1'], ['https://public@example.com/sentry/1'], ['https://public@example.com:4343/sentry/1'], ]; } - - /** - * @group legacy - */ - public function testGetProjectIdTriggersDeprecationErrorIfReturningInteger(): void - { - $dsn = Dsn::createFromString('https://public@example.com/sentry/1'); - - $this->expectDeprecation('Calling the method Sentry\\Dsn::getProjectId() and expecting it to return an integer is deprecated since version 3.4 and will stop working in 4.0.'); - - $this->assertSame(1, $dsn->getProjectId()); - } } diff --git a/tests/Fixtures/code/CustomException.php b/tests/Fixtures/code/CustomException.php new file mode 100644 index 000000000..f47296725 --- /dev/null +++ b/tests/Fixtures/code/CustomException.php @@ -0,0 +1,9 @@ +assertSame($transaction, $hub->getSpan()); } } @@ -343,9 +343,9 @@ public function testTraceparentWithTracingEnabled(): void SentrySdk::setCurrentHub($hub); - $spanContext = new SpanContext(); - $spanContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); - $spanContext->setSpanId(new SpanId('566e3688a61d4bc8')); + $spanContext = (new SpanContext()) + ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) + ->setSpanId(new SpanId('566e3688a61d4bc8')); $span = new Span($spanContext); diff --git a/tests/HttpClient/Authentication/SentryAuthenticationTest.php b/tests/HttpClient/Authentication/SentryAuthenticationTest.php deleted file mode 100644 index 726903a10..000000000 --- a/tests/HttpClient/Authentication/SentryAuthenticationTest.php +++ /dev/null @@ -1,61 +0,0 @@ - 'http://public:secret@example.com/sentry/1']); - $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $expectedHeader = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public, sentry_secret=secret', - Client::PROTOCOL_VERSION, - 'sentry.php.test/1.2.3' - ); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - - $request = $authentication->authenticate($request); - - $this->assertTrue($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); - } - - public function testAuthenticateWithoutSecretKey(): void - { - $configuration = new Options(['dsn' => 'http://public@example.com/sentry/1']); - $authentication = new SentryAuthentication($configuration, 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $expectedHeader = sprintf( - 'Sentry sentry_version=%s, sentry_client=%s, sentry_key=public', - Client::PROTOCOL_VERSION, - 'sentry.php.test/1.2.3' - ); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - - $request = $authentication->authenticate($request); - - $this->assertTrue($request->hasHeader('X-Sentry-Auth')); - $this->assertSame($expectedHeader, $request->getHeaderLine('X-Sentry-Auth')); - } - - public function testAuthenticateWithoutDsnOptionSet(): void - { - $authentication = new SentryAuthentication(new Options(), 'sentry.php.test', '1.2.3'); - $request = new Request('POST', 'http://www.example.com', []); - $request = $authentication->authenticate($request); - - $this->assertFalse($request->hasHeader('X-Sentry-Auth')); - } -} diff --git a/tests/HttpClient/HttpClientFactoryTest.php b/tests/HttpClient/HttpClientFactoryTest.php deleted file mode 100644 index b0218c817..000000000 --- a/tests/HttpClient/HttpClientFactoryTest.php +++ /dev/null @@ -1,104 +0,0 @@ -create(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - 'default_integrations' => false, - 'enable_compression' => $isCompressionEnabled, - ])); - - $request = Psr17FactoryDiscovery::findRequestFactory() - ->createRequest('POST', 'http://example.com/sentry/foo') - ->withBody($streamFactory->createStream('foo bar')); - - $httpClient->sendAsyncRequest($request); - - $httpRequest = $mockHttpClient->getLastRequest(); - - $this->assertSame('http://example.com/sentry/foo', (string) $httpRequest->getUri()); - $this->assertSame('sentry.php.test/1.2.3', $httpRequest->getHeaderLine('User-Agent')); - $this->assertSame('Sentry sentry_version=7, sentry_client=sentry.php.test/1.2.3, sentry_key=public', $httpRequest->getHeaderLine('X-Sentry-Auth')); - $this->assertSame($expectedRequestBody, (string) $httpRequest->getBody()); - } - - public static function createDataProvider(): \Generator - { - yield [ - false, - 'foo bar', - ]; - - yield [ - true, - gzcompress('foo bar', -1, \ZLIB_ENCODING_GZIP), - ]; - } - - public function testCreateThrowsIfDsnOptionIsNotConfigured(): void - { - $httpClientFactory = new HttpClientFactory( - null, - null, - Psr17FactoryDiscovery::findStreamFactory(), - null, - 'sentry.php.test', - '1.2.3' - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Cannot create an HTTP client without the Sentry DSN set in the options.'); - - $httpClientFactory->create(new Options(['default_integrations' => false])); - } - - public function testCreateThrowsIfHttpProxyOptionIsUsedWithCustomHttpClient(): void - { - $httpClientFactory = new HttpClientFactory( - null, - null, - Psr17FactoryDiscovery::findStreamFactory(), - $this->createMock(HttpAsyncClientInterface::class), - 'sentry.php.test', - '1.2.3' - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('The "http_proxy" option does not work together with a custom HTTP client.'); - - $httpClientFactory->create(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - 'default_integrations' => false, - 'http_proxy' => 'http://example.com', - ])); - } -} diff --git a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php b/tests/HttpClient/Plugin/GzipEncoderPluginTest.php deleted file mode 100644 index 0e5423015..000000000 --- a/tests/HttpClient/Plugin/GzipEncoderPluginTest.php +++ /dev/null @@ -1,40 +0,0 @@ -createMock(PromiseInterface::class); - $request = Psr17FactoryDiscovery::findRequestFactory() - ->createRequest('POST', 'http://www.example.com') - ->withBody($streamFactory->createStream('foo')); - - $this->assertSame('foo', (string) $request->getBody()); - $this->assertSame($expectedPromise, $plugin->handleRequest( - $request, - function (RequestInterface $requestArg) use ($expectedPromise): PromiseInterface { - $this->assertSame('gzip', $requestArg->getHeaderLine('Content-Encoding')); - $this->assertSame(gzcompress('foo', -1, \ZLIB_ENCODING_GZIP), (string) $requestArg->getBody()); - - return $expectedPromise; - }, - static function (): void {} - )); - } -} diff --git a/tests/HttpClient/ResponseTest.php b/tests/HttpClient/ResponseTest.php new file mode 100644 index 000000000..44f50d9fb --- /dev/null +++ b/tests/HttpClient/ResponseTest.php @@ -0,0 +1,71 @@ + [ + 'application/json', + ], + ], + '' + ); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertTrue($response->isSuccess()); + $this->assertTrue($response->hasHeader('content-type')); + $this->assertSame(['application/json'], $response->getHeader('content-type')); + $this->assertSame(['application/json'], $response->getHeader('Content-Type')); + $this->assertSame('application/json', $response->getHeaderLine('content-type')); + $this->assertSame('application/json', $response->getHeaderLine('Content-Type')); + $this->assertSame('', $response->getError()); + $this->assertFalse($response->hasError()); + } + + public function testResponseFailure() + { + $response = new Response( + 500, + [], + 'Something went wrong!' + ); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertFalse($response->isSuccess()); + $this->assertFalse($response->hasHeader('content-type')); + $this->assertSame([], $response->getHeader('content-type')); + $this->assertSame([], $response->getHeader('Content-Type')); + $this->assertSame('', $response->getHeaderLine('content-type')); + $this->assertSame('', $response->getHeaderLine('Content-Type')); + $this->assertSame('Something went wrong!', $response->getError()); + $this->assertTrue($response->hasError()); + } + + public function testResponseMultiValueHeader() + { + $response = new Response( + 200, + [ + 'X-Foo' => [ + 'one', + 'two', + 'three', + ], + ], + '' + ); + + $this->assertSame(['one', 'two', 'three'], $response->getHeader('x-foo')); + $this->assertSame('one,two,three', $response->getHeaderLine('x-foo')); + } +} diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index d530d8900..02c195170 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -14,6 +14,7 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Util\PHPVersion; + use function Sentry\withScope; final class EnvironmentIntegrationTest extends TestCase @@ -46,14 +47,14 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR $runtimeContext = $event->getRuntimeContext(); $osContext = $event->getOsContext(); - if (null === $expectedRuntimeContext) { + if ($expectedRuntimeContext === null) { $this->assertNull($runtimeContext); } else { $this->assertSame($expectedRuntimeContext->getName(), $runtimeContext->getName()); $this->assertSame($expectedRuntimeContext->getVersion(), $runtimeContext->getVersion()); } - if (null === $expectedOsContext) { + if ($expectedOsContext === null) { $this->assertNull($expectedOsContext); } else { $this->assertSame($expectedOsContext->getName(), $osContext->getName()); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index 1a1eea5d1..e4458be4c 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -15,6 +15,7 @@ use Sentry\SentrySdk; use Sentry\Stacktrace; use Sentry\State\Scope; + use function Sentry\withScope; final class FrameContextifierIntegrationTest extends TestCase @@ -159,7 +160,7 @@ private function getFixtureFileContent(string $file): string { $fileContent = file_get_contents($file); - if (false === $fileContent) { + if ($fileContent === false) { throw new \RuntimeException(sprintf('The fixture file at path "%s" could not be read.', $file)); } diff --git a/tests/Integration/IgnoreErrorsIntegrationTest.php b/tests/Integration/IgnoreErrorsIntegrationTest.php deleted file mode 100644 index d56f40c50..000000000 --- a/tests/Integration/IgnoreErrorsIntegrationTest.php +++ /dev/null @@ -1,128 +0,0 @@ -setupOnce(); - - /** @var ClientInterface&MockObject $client */ - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getIntegration') - ->willReturn($isIntegrationEnabled ? $integration : null); - - SentrySdk::getCurrentHub()->bindClient($client); - - withScope(function (Scope $scope) use ($event, $expectedEventToBeDropped): void { - $event = $scope->applyToEvent($event); - - if ($expectedEventToBeDropped) { - $this->assertNull($event); - } else { - $this->assertNotNull($event); - } - }); - } - - public static function invokeDataProvider(): \Generator - { - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'Integration disabled' => [ - Event::createEvent(), - false, - [ - 'ignore_exceptions' => [], - ], - false, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'No exceptions to check' => [ - Event::createEvent(), - true, - [ - 'ignore_exceptions' => [], - ], - false, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'The exception is matching exactly the "ignore_exceptions" option' => [ - $event, - true, - [ - 'ignore_exceptions' => [ - \RuntimeException::class, - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setExceptions([new ExceptionDataBag(new \RuntimeException())]); - - yield 'The exception is matching the "ignore_exceptions" option' => [ - $event, - true, - [ - 'ignore_exceptions' => [ - \Exception::class, - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setTags(['route' => 'foo']); - - yield 'The tag is matching the "ignore_tags" option' => [ - $event, - true, - [ - 'ignore_tags' => [ - 'route' => 'foo', - ], - ], - true, - ]; - - $event = Event::createEvent(); - $event->setTags(['route' => 'bar']); - - yield 'The tag is not matching the "ignore_tags" option' => [ - $event, - true, - [ - 'ignore_tags' => [ - 'route' => 'foo', - ], - ], - false, - ]; - } -} diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 864dc738d..4fcd4dd02 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -11,6 +11,7 @@ use Sentry\Integration\ModulesIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; + use function Sentry\withScope; final class ModulesIntegrationTest extends TestCase diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index 0f904c8ba..266405d90 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -18,6 +18,7 @@ use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\UserDataBag; + use function Sentry\withScope; final class RequestIntegrationTest extends TestCase @@ -53,7 +54,7 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra $user = $event->getUser(); - if (null !== $expectedUser) { + if ($expectedUser !== null) { $this->assertNotNull($user); $this->assertEquals($expectedUser, $user); } else { @@ -105,7 +106,7 @@ public static function invokeDataProvider(): iterable [ 'send_default_pii' => true, ], - (new ServerRequest('GET', 'http://www.example.com:1234/foo')), + new ServerRequest('GET', 'http://www.example.com:1234/foo'), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', @@ -122,7 +123,7 @@ public static function invokeDataProvider(): iterable [ 'send_default_pii' => false, ], - (new ServerRequest('GET', 'http://www.example.com:1234/foo')), + new ServerRequest('GET', 'http://www.example.com:1234/foo'), [ 'url' => 'http://www.example.com:1234/foo', 'method' => 'GET', diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index fcb99a79b..ab46c6a1b 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -12,6 +12,7 @@ use Sentry\Integration\TransactionIntegration; use Sentry\SentrySdk; use Sentry\State\Scope; + use function Sentry\withScope; final class TransactionIntegrationTest extends TestCase diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php index b302c509b..39e2b67d6 100644 --- a/tests/Monolog/RecordFactory.php +++ b/tests/Monolog/RecordFactory.php @@ -4,7 +4,6 @@ namespace Sentry\Tests\Monolog; -use DateTimeImmutable; use Monolog\Logger; use Monolog\LogRecord; @@ -24,7 +23,7 @@ public static function create(string $message, int $level, string $channel, arra { if (Logger::API >= 3) { return new LogRecord( - new DateTimeImmutable(), + new \DateTimeImmutable(), $channel, Logger::toMonologLevel($level), $message, diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 4f47fbbe8..09bec7b9b 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -5,8 +5,12 @@ namespace Sentry\Tests; use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; use Sentry\Dsn; +use Sentry\HttpClient\HttpClient; use Sentry\Options; +use Sentry\Serializer\PayloadSerializer; +use Sentry\Transport\HttpTransport; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; @@ -42,13 +46,8 @@ public function testConstructor( string $option, $value, string $getterMethod, - ?string $setterMethod, - ?string $expectedGetterDeprecationMessage + ?string $setterMethod ): void { - if (null !== $expectedGetterDeprecationMessage) { - $this->expectDeprecation($expectedGetterDeprecationMessage); - } - $options = new Options([$option => $value]); $this->assertEquals($value, $options->$getterMethod()); @@ -63,21 +62,11 @@ public function testGettersAndSetters( string $option, $value, string $getterMethod, - ?string $setterMethod, - ?string $expectedGetterDeprecationMessage, - ?string $expectedSetterDeprecationMessage + ?string $setterMethod ): void { - if (null !== $expectedSetterDeprecationMessage) { - $this->expectDeprecation($expectedSetterDeprecationMessage); - } - - if (null !== $expectedGetterDeprecationMessage) { - $this->expectDeprecation($expectedGetterDeprecationMessage); - } - $options = new Options(); - if (null !== $setterMethod) { + if ($setterMethod !== null) { $options->$setterMethod($value); } @@ -86,22 +75,11 @@ public function testGettersAndSetters( public static function optionsDataProvider(): \Generator { - yield [ - 'send_attempts', - 1, - 'getSendAttempts', - 'setSendAttempts', - 'Method Sentry\\Options::getSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', - 'Method Sentry\\Options::setSendAttempts() is deprecated since version 3.5 and will be removed in 4.0.', - ]; - yield [ 'prefixes', ['foo', 'bar'], 'getPrefixes', 'setPrefixes', - null, - null, ]; yield [ @@ -109,8 +87,6 @@ public static function optionsDataProvider(): \Generator 0.5, 'getSampleRate', 'setSampleRate', - null, - null, ]; yield [ @@ -118,8 +94,6 @@ public static function optionsDataProvider(): \Generator 0.5, 'getTracesSampleRate', 'setTracesSampleRate', - null, - null, ]; yield [ @@ -127,8 +101,6 @@ public static function optionsDataProvider(): \Generator null, 'getTracesSampleRate', 'setTracesSampleRate', - null, - null, ]; yield [ @@ -136,8 +108,6 @@ public static function optionsDataProvider(): \Generator static function (): void {}, 'getTracesSampler', 'setTracesSampler', - null, - null, ]; yield [ @@ -145,8 +115,6 @@ static function (): void {}, true, 'getEnableTracing', 'setEnableTracing', - null, - null, ]; yield [ @@ -154,8 +122,6 @@ static function (): void {}, 0.5, 'getProfilesSampleRate', 'setProfilesSampleRate', - null, - null, ]; yield [ @@ -163,8 +129,6 @@ static function (): void {}, false, 'shouldAttachStacktrace', 'setAttachStacktrace', - null, - null, ]; yield [ @@ -172,17 +136,6 @@ static function (): void {}, 3, 'getContextLines', 'setContextLines', - null, - null, - ]; - - yield [ - 'enable_compression', - false, - 'isCompressionEnabled', - 'setEnableCompression', - null, - null, ]; yield [ @@ -190,8 +143,6 @@ static function (): void {}, 'foo', 'getEnvironment', 'setEnvironment', - null, - null, ]; yield [ @@ -199,8 +150,6 @@ static function (): void {}, ['foo', 'bar'], 'getInAppExcludedPaths', 'setInAppExcludedPaths', - null, - null, ]; yield [ @@ -208,17 +157,13 @@ static function (): void {}, ['foo', 'bar'], 'getInAppIncludedPaths', 'setInAppIncludedPaths', - null, - null, ]; yield [ 'logger', - 'foo', + new NullLogger(), 'getLogger', 'setLogger', - 'Method Sentry\\Options::getLogger() is deprecated since version 3.2 and will be removed in 4.0.', - 'Method Sentry\\Options::setLogger() is deprecated since version 3.2 and will be removed in 4.0.', ]; yield [ @@ -226,8 +171,6 @@ static function (): void {}, 'dev', 'getRelease', 'setRelease', - null, - null, ]; yield [ @@ -235,8 +178,6 @@ static function (): void {}, 'foo', 'getServerName', 'setServerName', - null, - null, ]; yield [ @@ -244,8 +185,6 @@ static function (): void {}, ['foo', 'bar'], 'getTags', 'setTags', - null, - null, ]; yield [ @@ -253,8 +192,6 @@ static function (): void {}, 0, 'getErrorTypes', 'setErrorTypes', - null, - null, ]; yield [ @@ -262,8 +199,6 @@ static function (): void {}, 50, 'getMaxBreadcrumbs', 'setMaxBreadcrumbs', - null, - null, ]; yield [ @@ -271,8 +206,6 @@ static function (): void {}, ['foo', 'bar'], 'getIgnoreExceptions', 'setIgnoreExceptions', - null, - null, ]; yield [ @@ -280,8 +213,6 @@ static function (): void {}, ['foo', 'bar'], 'getIgnoreTransactions', 'setIgnoreTransactions', - null, - null, ]; yield [ @@ -289,8 +220,6 @@ static function (): void {}, static function (): void {}, 'getBeforeSendCallback', 'setBeforeSendCallback', - null, - null, ]; yield [ @@ -298,8 +227,6 @@ static function (): void {}, static function (): void {}, 'getBeforeSendTransactionCallback', 'setBeforeSendTransactionCallback', - null, - null, ]; yield [ @@ -307,8 +234,6 @@ static function (): void {}, ['www.example.com'], 'getTracePropagationTargets', 'setTracePropagationTargets', - null, - null, ]; yield [ @@ -316,8 +241,6 @@ static function (): void {}, static function (): void {}, 'getBeforeBreadcrumbCallback', 'setBeforeBreadcrumbCallback', - null, - null, ]; yield [ @@ -325,8 +248,6 @@ static function (): void {}, true, 'shouldSendDefaultPii', 'setSendDefaultPii', - null, - null, ]; yield [ @@ -334,8 +255,6 @@ static function (): void {}, false, 'hasDefaultIntegrations', 'setDefaultIntegrations', - null, - null, ]; yield [ @@ -343,8 +262,20 @@ static function (): void {}, 50, 'getMaxValueLength', 'setMaxValueLength', - null, - null, + ]; + + yield [ + 'transport', + new HttpTransport(new Options(), new HttpClient('foo', 'bar'), new PayloadSerializer(new Options())), + 'getTransport', + 'setTransport', + ]; + + yield [ + 'http_client', + new HttpClient('foo', 'bar'), + 'getHttpClient', + 'setHttpClient', ]; yield [ @@ -352,8 +283,13 @@ static function (): void {}, '127.0.0.1', 'getHttpProxy', 'setHttpProxy', - null, - null, + ]; + + yield [ + 'http_proxy_authentication', + 'username:password', + 'getHttpProxyAuthentication', + 'setHttpProxyAuthentication', ]; yield [ @@ -361,8 +297,6 @@ static function (): void {}, 1, 'getHttpTimeout', 'setHttpTimeout', - null, - null, ]; yield [ @@ -370,8 +304,6 @@ static function (): void {}, 1.2, 'getHttpTimeout', 'setHttpTimeout', - null, - null, ]; yield [ @@ -379,8 +311,6 @@ static function (): void {}, 1, 'getHttpConnectTimeout', 'setHttpConnectTimeout', - null, - null, ]; yield [ @@ -388,8 +318,20 @@ static function (): void {}, 1.2, 'getHttpConnectTimeout', 'setHttpConnectTimeout', - null, - null, + ]; + + yield [ + 'http_ssl_verify_peer', + false, + 'getHttpSslVerifyPeer', + 'setHttpSslVerifyPeer', + ]; + + yield [ + 'http_compression', + false, + 'isHttpCompressionEnabled', + 'setEnableHttpCompression', ]; yield [ @@ -397,8 +339,6 @@ static function (): void {}, true, 'shouldCaptureSilencedErrors', 'setCaptureSilencedErrors', - null, - null, ]; yield [ @@ -406,8 +346,6 @@ static function (): void {}, 'small', 'getMaxRequestBodySize', 'setMaxRequestBodySize', - null, - null, ]; } @@ -575,7 +513,7 @@ public static function maxBreadcrumbsOptionIsValidatedCorrectlyDataProvider(): a */ public function testContextLinesOptionValidatesInputValue(?int $value, ?string $expectedExceptionMessage): void { - if (null !== $expectedExceptionMessage) { + if ($expectedExceptionMessage !== null) { $this->expectException(InvalidOptionsException::class); $this->expectExceptionMessage($expectedExceptionMessage); } else { diff --git a/tests/Profiling/ProfileTest.php b/tests/Profiling/ProfileTest.php index a30f68e6d..513210c23 100644 --- a/tests/Profiling/ProfileTest.php +++ b/tests/Profiling/ProfileTest.php @@ -24,7 +24,7 @@ public function testGetFormattedData(Event $event, array $excimerLog, $expectedD $profile->setStartTimeStamp(1677573660.0000); $profile->setExcimerLog($excimerLog); - $profile->setEventId((new EventId('815e57b4bb134056ab1840919834689d'))); + $profile->setEventId(new EventId('815e57b4bb134056ab1840919834689d')); $this->assertSame($expectedData, $profile->getFormattedData($event)); } diff --git a/tests/Serializer/AbstractSerializerTest.php b/tests/Serializer/AbstractSerializerTest.php index 10e26f513..43fadc722 100644 --- a/tests/Serializer/AbstractSerializerTest.php +++ b/tests/Serializer/AbstractSerializerTest.php @@ -468,7 +468,7 @@ public function serializableCallableProvider(): array 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass param1d]', ], [ - 'callable' => function (\stdClass $param1e = null) { + 'callable' => function (?\stdClass $param1e = null) { throw new \Exception('Don\'t even think about invoke me'); }, 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [stdClass|null [param1e]]', @@ -480,7 +480,7 @@ public function serializableCallableProvider(): array 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array ¶m1f]', ], [ - 'callable' => function (array &$param1g = null) { + 'callable' => function (?array &$param1g = null) { throw new \Exception('Don\'t even think about invoke me'); }, 'expected' => 'Lambda ' . __NAMESPACE__ . '\\{closure} [array|null [¶m1g]]', @@ -564,7 +564,7 @@ public function testSerializeCallable($callable, string $expected): void /** * @dataProvider serializationForBadStringsDataProvider */ - public function testSerializationForBadStrings(string $string, string $expected, string $mbDetectOrder = null): void + public function testSerializationForBadStrings(string $string, string $expected, ?string $mbDetectOrder = null): void { $serializer = $this->createSerializer(); diff --git a/tests/Serializer/PayloadSerializerTest.php b/tests/Serializer/PayloadSerializerTest.php index 292fd9944..f212776b6 100644 --- a/tests/Serializer/PayloadSerializerTest.php +++ b/tests/Serializer/PayloadSerializerTest.php @@ -13,7 +13,6 @@ use Sentry\Context\RuntimeContext; use Sentry\Event; use Sentry\EventId; -use Sentry\EventType; use Sentry\ExceptionDataBag; use Sentry\ExceptionMechanism; use Sentry\Frame; @@ -39,34 +38,6 @@ */ final class PayloadSerializerTest extends TestCase { - /** - * @dataProvider serializeAsJsonDataProvider - */ - public function testSerializeAsJson(Event $event, string $expectedResult, bool $isOutputJson): void - { - ClockMock::withClockMock(1597790835); - - $serializer = new PayloadSerializer(new Options([ - 'dsn' => 'http://public@example.com/sentry/1', - ])); - - $result = $serializer->serialize($event); - - if ( - EventType::transaction() !== $event->getType() && - EventType::checkIn() !== $event->getType() - ) { - $resultArray = $serializer->toArray($event); - $this->assertJsonStringEqualsJsonString($result, json_encode($resultArray)); - } - - if ($isOutputJson) { - $this->assertJsonStringEqualsJsonString($expectedResult, $result); - } else { - $this->assertSame($expectedResult, $result); - } - } - /** * @dataProvider serializeAsEnvelopeDataProvider */ @@ -76,7 +47,6 @@ public function testSerializeAsEnvelope(Event $event, string $expectedResult): v $serializer = new PayloadSerializer(new Options([ 'dsn' => 'http://public@example.com/sentry/1', - 'enable_tracing' => true, ])); $result = $serializer->serialize($event); @@ -84,7 +54,7 @@ public function testSerializeAsEnvelope(Event $event, string $expectedResult): v $this->assertSame($expectedResult, $result); } - public static function serializeAsJsonDataProvider(): iterable + public static function serializeAsEnvelopeDataProvider(): iterable { ClockMock::withClockMock(1597790835); @@ -92,24 +62,16 @@ public static function serializeAsJsonDataProvider(): iterable yield [ Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')), - <<setLevel(Severity::error()); - $event->setLogger('app.php'); $event->setTransaction('/users//'); $event->setServerName('foo.example.com'); $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); @@ -122,8 +84,6 @@ public static function serializeAsJsonDataProvider(): iterable new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), ]); - $event->setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-replay_id=12312012123120121231201212312012')); - $event->setUser(UserDataBag::createFromArray([ 'id' => 'unique_id', 'username' => 'my_user', @@ -205,154 +165,11 @@ public static function serializeAsJsonDataProvider(): iterable yield [ $event, - <</", - "server_name": "foo.example.com", - "release": "721e41770371db95eee98ca2707686226b993eda", - "environment": "production", - "fingerprint": [ - "myrpc", - "POST", - "/foo.bar" - ], - "modules": { - "my.module.name": "1.0" - }, - "extra": { - "my_key": 1, - "some_other_value": "foo bar" - }, - "tags": { - "ios_version": "4.0", - "context": "production" - }, - "user": { - "id": "unique_id", - "username": "my_user", - "email": "foo@example.com", - "ip_address": "127.0.0.1", - "segment": "my_segment" - }, - "contexts": { - "os": { - "name": "Linux", - "version": "4.19.104-microsoft-standard", - "build": "#1 SMP Wed Feb 19 06:37:35 UTC 2020", - "kernel_version": "Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64" - }, - "runtime": { - "name": "php", - "version": "7.4.3" - }, - "electron": { - "type": "runtime", - "name": "Electron", - "version": "4.0" - }, - "replay": { - "replay_id": "12312012123120121231201212312012" - } - }, - "breadcrumbs": { - "values": [ - { - "type": "user", - "category": "log", - "level": "info", - "timestamp": 1597790835 - }, - { - "type": "navigation", - "category": "log", - "level": "info", - "timestamp": 1597790835, - "data": { - "from": "/login", - "to": "/dashboard" - } - } - ] - }, - "request": { - "method": "POST", - "url": "http://absolute.uri/foo", - "query_string": "query=foobar&page=2", - "data": { - "foo": "bar" - }, - "cookies": { - "PHPSESSID": "298zf09hf012fh2" - }, - "headers": { - "content-type": "text/html" - }, - "env": { - "REMOTE_ADDR": "127.0.0.1" - } - }, - "exception": { - "values": [ - { - "type": "Exception", - "value": "chained exception", - "stacktrace": { - "frames": [ - { - "filename": "file/name.py", - "lineno": 3, - "in_app": true - }, - { - "filename": "file/name.py", - "lineno": 3, - "in_app": false, - "abs_path": "absolute/file/name.py", - "function": "myfunction", - "raw_function": "raw_function_name", - "pre_context": [ - "def foo():", - " my_var = 'foo'" - ], - "context_line": " raise ValueError()", - "post_context": [ - "", - "def main():" - ], - "vars": { - "my_var": "value" - } - } - ] - }, - "mechanism": { - "type": "generic", - "handled": true, - "data": { - "code": 123 - } - } - }, - { - "type": "Exception", - "value": "initial exception" - } - ] - } -} -JSON - , - true, + <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} +TEXT ]; $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); @@ -360,20 +177,12 @@ public static function serializeAsJsonDataProvider(): iterable yield [ $event, - <<setCheckIn($checkIn); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); yield [ $event, <<setCheckIn($checkIn); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); yield [ $event, <<setLevel(Severity::error()); - $event->setLogger('app.php'); - $event->setTransaction('/users//'); - $event->setServerName('foo.example.com'); - $event->setRelease('721e41770371db95eee98ca2707686226b993eda'); - $event->setEnvironment('production'); - $event->setFingerprint(['myrpc', 'POST', '/foo.bar']); - $event->setModules(['my.module.name' => '1.0']); - $event->setStartTimestamp(1597790835); - $event->setBreadcrumb([ - new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_USER, 'log'), - new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_NAVIGATION, 'log', null, ['from' => '/login', 'to' => '/dashboard']), - ]); - - $event->setUser(UserDataBag::createFromArray([ - 'id' => 'unique_id', - 'username' => 'my_user', - 'email' => 'foo@example.com', - 'ip_address' => '127.0.0.1', - 'segment' => 'my_segment', - ])); - - $event->setTags([ - 'ios_version' => '4.0', - 'context' => 'production', - ]); - - $event->setExtra([ - 'my_key' => 1, - 'some_other_value' => 'foo bar', - ]); - - $event->setRequest([ - 'method' => 'POST', - 'url' => 'http://absolute.uri/foo', - 'query_string' => 'query=foobar&page=2', - 'data' => [ - 'foo' => 'bar', - ], - 'cookies' => [ - 'PHPSESSID' => '298zf09hf012fh2', - ], - 'headers' => [ - 'content-type' => 'text/html', - ], - 'env' => [ - 'REMOTE_ADDR' => '127.0.0.1', - ], - ]); - - $event->setOsContext(new OsContext( - 'Linux', - '4.19.104-microsoft-standard', - '#1 SMP Wed Feb 19 06:37:35 UTC 2020', - 'Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64' - )); - - $event->setRuntimeContext(new RuntimeContext( - 'php', - '7.4.3' - )); - - $event->setContext('electron', [ - 'type' => 'runtime', - 'name' => 'Electron', - 'version' => '4.0', - ]); - - $frame1 = new Frame(null, 'file/name.py', 3); - $frame2 = new Frame('myfunction', 'file/name.py', 3, 'raw_function_name', 'absolute/file/name.py', ['my_var' => 'value'], false); - $frame2->setContextLine(' raise ValueError()'); - $frame2->setPreContext([ - 'def foo():', - ' my_var = \'foo\'', - ]); - - $frame2->setPostContext([ - '', - 'def main():', - ]); - - $event->setExceptions([ - new ExceptionDataBag(new \Exception('initial exception')), - new ExceptionDataBag( - new \Exception('chained exception'), - new Stacktrace([ - $frame1, - $frame2, - ]), - new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => 123]) - ), - ]); - - yield [ - $event, - <<\/","server_name":"foo.example.com","release":"721e41770371db95eee98ca2707686226b993eda","environment":"production","fingerprint":["myrpc","POST","\/foo.bar"],"modules":{"my.module.name":"1.0"},"extra":{"my_key":1,"some_other_value":"foo bar"},"tags":{"ios_version":"4.0","context":"production"},"user":{"id":"unique_id","username":"my_user","email":"foo@example.com","ip_address":"127.0.0.1","segment":"my_segment"},"contexts":{"os":{"name":"Linux","version":"4.19.104-microsoft-standard","build":"#1 SMP Wed Feb 19 06:37:35 UTC 2020","kernel_version":"Linux 7944782cd697 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64"},"runtime":{"name":"php","version":"7.4.3"},"electron":{"type":"runtime","name":"Electron","version":"4.0"}},"breadcrumbs":{"values":[{"type":"user","category":"log","level":"info","timestamp":1597790835},{"type":"navigation","category":"log","level":"info","timestamp":1597790835,"data":{"from":"\/login","to":"\/dashboard"}}]},"request":{"method":"POST","url":"http:\/\/absolute.uri\/foo","query_string":"query=foobar&page=2","data":{"foo":"bar"},"cookies":{"PHPSESSID":"298zf09hf012fh2"},"headers":{"content-type":"text\/html"},"env":{"REMOTE_ADDR":"127.0.0.1"}},"exception":{"values":[{"type":"Exception","value":"chained exception","stacktrace":{"frames":[{"filename":"file\/name.py","lineno":3,"in_app":true},{"filename":"file\/name.py","lineno":3,"in_app":false,"abs_path":"absolute\/file\/name.py","function":"myfunction","raw_function":"raw_function_name","pre_context":["def foo():"," my_var = 'foo'"],"context_line":" raise ValueError()","post_context":["","def main():"],"vars":{"my_var":"value"}}]},"mechanism":{"type":"generic","handled":true,"data":{"code":123}}},{"type":"Exception","value":"initial exception"}]}} -TEXT - ]; - - $event = Event::createEvent(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setMessage('My raw message with interpreted strings like this', []); - - yield [ - $event, - <<setMessage('My raw message with interpreted strings like %s', ['this']); - - yield [ - $event, - <<setMessage('My raw message with interpreted strings like %s', ['this'], 'My raw message with interpreted strings like that'); - - yield [ - $event, - <<setSpanId(new SpanId('5dd538dc297544cc')); - $span1->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); - - $span2 = new Span(); - $span2->setSpanId(new SpanId('b01b9f6349558cd1')); - $span2->setParentSpanId(new SpanId('b0e6f15b45c36b12')); - $span2->setTraceId(new TraceId('1e57b752bc6e4544bbaa246cd1d05dee')); - $span2->setOp('http'); - $span2->setDescription('GET /sockjs-node/info'); - $span2->setStatus(SpanStatus::ok()); - $span2->setStartTimestamp(1597790835); - $span2->setTags(['http.status_code' => '200']); - $span2->setData([ - 'url' => 'http://localhost:8080/sockjs-node/info?t=1588601703755', - 'status_code' => 200, - 'type' => 'xhr', - 'method' => 'GET', - ]); - - $span2->finish(1598659060); - - $event = Event::createTransaction(new EventId('fc9442f5aef34234bb22b9a615e30ccd')); - $event->setSpans([$span1, $span2]); - $event->setRelease('1.0.0'); - $event->setEnvironment('dev'); - $event->setTransaction('GET /'); - $event->setContext('trace', [ - 'trace_id' => '21160e9b836d479f81611368b2aa3d2c', - 'span_id' => '5dd538dc297544cc', - ]); - $event->setRuntimeContext(new RuntimeContext( - 'php', - '8.2.3' - )); - $event->setOsContext(new OsContext( - 'macOS', - '13.2.1', - '22D68', - 'Darwin Kernel Version 22.2.0', - 'aarch64' - )); - - $excimerLog = [ - [ - 'trace' => [ - [ - 'file' => '/var/www/html/index.php', - 'line' => 42, - ], - ], - 'timestamp' => 0.001, - ], - [ - 'trace' => [ - [ - 'file' => '/var/www/html/index.php', - 'line' => 42, - ], - [ - 'class' => 'Function', - 'function' => 'doStuff', - 'file' => '/var/www/html/function.php', - 'line' => 84, - ], - ], - 'timestamp' => 0.002, - ], - ]; - - $profile = new Profile(); - // 2022-02-28T09:41:00Z - $profile->setStartTimeStamp(1677573660.0000); - $profile->setExcimerLog($excimerLog); - $profile->setEventId($event->getId()); - - $event->setSdkMetadata('profile', $profile); - - yield [ - $event, - <<setSdkMetadata('dynamic_sampling_context', DynamicSamplingContext::fromHeader('sentry-public_key=public,sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-sample_rate=1')); - $event->setSdkMetadata('transaction_metadata', new TransactionMetadata()); - - yield [ - $event, - <<setStacktrace(new Stacktrace([new Frame(null, '', 0)])); - - yield [ - $event, - <<setCheckIn($checkIn); - - yield [ - $event, - <<setCheckIn($checkIn); - - yield [ - $event, - <<expectException(\OutOfBoundsException::class); $this->expectExceptionMessage($expectedExceptionMessage); } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 03010e950..54934cb43 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -141,7 +141,7 @@ public static function traceHeadersDataProvider(): iterable new Request('GET', 'https://www.example.com'), new Options([ 'traces_sample_rate' => 1, - 'trace_propagation_targets' => [], + 'trace_propagation_targets' => null, ]), true, ]; @@ -161,7 +161,7 @@ public static function traceHeadersDataProvider(): iterable new Request('GET', 'https://www.example.com'), new Options([ 'traces_sample_rate' => 1, - 'trace_propagation_targets' => null, + 'trace_propagation_targets' => [], ]), false, ]; diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index 9a3dd1cb9..a80d9d17f 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -32,12 +32,12 @@ public function testFromHeaders(string $sentryTraceHeader, string $baggageHeader $propagationContext = PropagationContext::fromHeaders($sentryTraceHeader, $baggageHeader); $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); - if (null !== $expectedTraceId) { + if ($expectedTraceId !== null) { $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); } $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); - if (null !== $expectedParentSpanId) { + if ($expectedParentSpanId !== null) { $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); } @@ -54,12 +54,12 @@ public function testFromEnvironment(string $sentryTrace, string $baggage, ?Trace $propagationContext = PropagationContext::fromEnvironment($sentryTrace, $baggage); $this->assertInstanceOf(TraceId::class, $propagationContext->getTraceId()); - if (null !== $expectedTraceId) { + if ($expectedTraceId !== null) { $this->assertSame((string) $expectedTraceId, (string) $propagationContext->getTraceId()); } $this->assertInstanceOf(SpanId::class, $propagationContext->getParentSpanId()); - if (null !== $expectedParentSpanId) { + if ($expectedParentSpanId !== null) { $this->assertSame((string) $expectedParentSpanId, (string) $propagationContext->getParentSpanId()); } diff --git a/tests/Tracing/SpanContextTest.php b/tests/Tracing/SpanContextTest.php deleted file mode 100644 index 0ca04622c..000000000 --- a/tests/Tracing/SpanContextTest.php +++ /dev/null @@ -1,76 +0,0 @@ -expectDeprecation('The Sentry\\Tracing\\SpanContext::fromTraceparent() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromHeaders() instead.'); - - $spanContext = SpanContext::fromTraceparent($header); - - if (null !== $expectedSpanId) { - $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); - } - - if (null !== $expectedTraceId) { - $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); - } - - $this->assertSame($expectedSampled, $spanContext->getSampled()); - } - - public static function fromTraceparentDataProvider(): iterable - { - yield [ - '0', - null, - null, - false, - ]; - - yield [ - '1', - null, - null, - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - false, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - null, - ]; - } -} diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 8244f18db..9c2387415 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -51,15 +51,15 @@ public function testStartChild(): void $spanContext2ParentSpanId = SpanId::generate(); $spanContext2TraceId = TraceId::generate(); - $spanContext1 = new SpanContext(); - $spanContext1->setSampled(false); - $spanContext1->setSpanId(SpanId::generate()); - $spanContext1->setTraceId(TraceId::generate()); - - $spanContext2 = new SpanContext(); - $spanContext2->setSampled(true); - $spanContext2->setParentSpanId($spanContext2ParentSpanId); - $spanContext2->setTraceId($spanContext2TraceId); + $spanContext1 = (new SpanContext()) + ->setSampled(false) + ->setSpanId(SpanId::generate()) + ->setTraceId(TraceId::generate()); + + $spanContext2 = (new SpanContext()) + ->setSampled(true) + ->setParentSpanId($spanContext2ParentSpanId) + ->setTraceId($spanContext2TraceId); $span1 = new Span($spanContext1); $span2 = $span1->startChild($spanContext2); diff --git a/tests/Tracing/TransactionContextTest.php b/tests/Tracing/TransactionContextTest.php index 0e9e3f5d3..c4c1176e2 100644 --- a/tests/Tracing/TransactionContextTest.php +++ b/tests/Tracing/TransactionContextTest.php @@ -37,58 +37,6 @@ public function testGettersAndSetters(): void $this->assertSame($transactionSource, $transactionContext->getMetadata()->getSource()); } - /** - * @dataProvider fromSentryTraceDataProvider - * - * @group legacy - */ - public function testFromTraceparent(string $header, ?SpanId $expectedSpanId, ?TraceId $expectedTraceId, ?bool $expectedParentSampled): void - { - $spanContext = TransactionContext::fromSentryTrace($header); - - $this->assertEquals($expectedSpanId, $spanContext->getParentSpanId()); - $this->assertEquals($expectedTraceId, $spanContext->getTraceId()); - $this->assertSame($expectedParentSampled, $spanContext->getParentSampled()); - } - - public static function fromSentryTraceDataProvider(): iterable - { - yield [ - '0', - null, - null, - false, - ]; - - yield [ - '1', - null, - null, - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-0', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - false, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - true, - ]; - - yield [ - '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', - new SpanId('566e3688a61d4bc8'), - new TraceId('566e3688a61d4bc888951642d6f14a19'), - null, - ]; - } - /** * @dataProvider tracingDataProvider */ diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index dc5023632..34cfe2b0e 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -4,7 +4,6 @@ namespace Sentry\Tests\Tracing; -use Generator; use PHPUnit\Framework\TestCase; use Sentry\ClientInterface; use Sentry\Event; @@ -29,10 +28,10 @@ public function testFinish(): void ClockMock::withClockMock(1600640877); $expectedEventId = null; - $transactionContext = new TransactionContext(); - $transactionContext->setTags(['ios_version' => '4.0']); - $transactionContext->setSampled(true); - $transactionContext->setStartTimestamp(1600640865); + $transactionContext = (new TransactionContext()) + ->setTags(['ios_version' => '4.0']) + ->setSampled(true) + ->setStartTimestamp(1600640865); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -107,7 +106,7 @@ public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions $this->assertSame($expectedSampled, $transaction->getSampled()); } - public static function parentTransactionContextDataProvider(): Generator + public static function parentTransactionContextDataProvider(): \Generator { yield [ new TransactionContext(TransactionContext::DEFAULT_NAME, true), @@ -150,7 +149,7 @@ public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(Transa $this->assertSame($expectedSampled, $transaction->getSampled()); } - public function parentTransactionContextDataProviderDisabled(): Generator + public function parentTransactionContextDataProviderDisabled(): \Generator { yield [ new TransactionContext(TransactionContext::DEFAULT_NAME, true), diff --git a/tests/Transport/DefaultTransportFactoryTest.php b/tests/Transport/DefaultTransportFactoryTest.php deleted file mode 100644 index 8ef47ddd4..000000000 --- a/tests/Transport/DefaultTransportFactoryTest.php +++ /dev/null @@ -1,50 +0,0 @@ -createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), - $this->createMock(HttpClientFactoryInterface::class) - ); - - $this->assertInstanceOf(NullTransport::class, $factory->create(new Options())); - } - - public function testCreateReturnsHttpTransportWhenDsnOptionIsConfigured(): void - { - $options = new Options(['dsn' => 'http://public@example.com/sentry/1']); - - /** @var HttpClientFactoryInterface&MockObject $httpClientFactory */ - $httpClientFactory = $this->createMock(HttpClientFactoryInterface::class); - $httpClientFactory->expects($this->once()) - ->method('create') - ->with($options) - ->willReturn($this->createMock(HttpAsyncClientInterface::class)); - - $factory = new DefaultTransportFactory( - $this->createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), - $httpClientFactory - ); - - $this->assertInstanceOf(HttpTransport::class, $factory->create($options)); - } -} diff --git a/tests/Transport/HttpTransportTest.php b/tests/Transport/HttpTransportTest.php index 95483aef3..dddf99a25 100644 --- a/tests/Transport/HttpTransportTest.php +++ b/tests/Transport/HttpTransportTest.php @@ -4,26 +4,16 @@ namespace Sentry\Tests\Transport; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Promise\RejectionException; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\Utils; -use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; -use Http\Promise\FulfilledPromise as HttpFullfilledPromise; -use Http\Promise\RejectedPromise as HttpRejectedPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; -use Sentry\Dsn; use Sentry\Event; +use Sentry\HttpClient\HttpClientInterface; +use Sentry\HttpClient\Response; use Sentry\Options; -use Sentry\ResponseStatus; use Sentry\Serializer\PayloadSerializerInterface; use Sentry\Transport\HttpTransport; +use Sentry\Transport\ResultStatus; use Symfony\Bridge\PhpUnit\ClockMock; final class HttpTransportTest extends TestCase @@ -33,16 +23,6 @@ final class HttpTransportTest extends TestCase */ private $httpClient; - /** - * @var MockObject&StreamFactoryInterface - */ - private $streamFactory; - - /** - * @var MockObject&RequestFactoryInterface - */ - private $requestFactory; - /** * @var MockObject&PayloadSerializerInterface */ @@ -50,82 +30,15 @@ final class HttpTransportTest extends TestCase protected function setUp(): void { - $this->httpClient = $this->createMock(HttpAsyncClientInterface::class); - $this->streamFactory = $this->createMock(StreamFactoryInterface::class); - $this->requestFactory = $this->createMock(RequestFactoryInterface::class); + $this->httpClient = $this->createMock(HttpClientInterface::class); $this->payloadSerializer = $this->createMock(PayloadSerializerInterface::class); } - public function testSendThrowsIfDsnOptionIsNotSet(): void - { - $transport = new HttpTransport( - new Options(), - $this->httpClient, - $this->streamFactory, - $this->requestFactory, - $this->payloadSerializer - ); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('The DSN option must be set to use the "Sentry\Transport\HttpTransport" transport.'); - - $transport->send(Event::createEvent()); - } - - public function testSendTransactionAsEnvelope(): void - { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); - $event = Event::createTransaction(); - - $this->payloadSerializer->expects($this->once()) - ->method('serialize') - ->with($event) - ->willReturn('{"foo":"bar"}'); - - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getEnvelopeApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); - - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (Request $requestArg): bool { - if ('application/x-sentry-envelope' !== $requestArg->getHeaderLine('Content-Type')) { - return false; - } - - if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { - return false; - } - - return true; - })) - ->willReturn(new HttpFullfilledPromise(new Response())); - - $transport = new HttpTransport( - new Options(['dsn' => $dsn]), - $this->httpClient, - $this->streamFactory, - $this->requestFactory, - $this->payloadSerializer - ); - - $transport->send($event); - } - /** * @dataProvider sendDataProvider */ - public function testSend(int $httpStatusCode, string $expectedPromiseStatus, ResponseStatus $expectedResponseStatus): void + public function testSend(int $httpStatusCode, ResultStatus $expectedResultStatus, bool $expectEventReturned): void { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $event = Event::createEvent(); $this->payloadSerializer->expects($this->once()) @@ -133,72 +46,55 @@ public function testSend(int $httpStatusCode, string $expectedPromiseStatus, Res ->with($event) ->willReturn('{"foo":"bar"}'); - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); - - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->with($this->callback(function (Request $requestArg): bool { - if ('application/json' !== $requestArg->getHeaderLine('Content-Type')) { - return false; - } - - if ('{"foo":"bar"}' !== $requestArg->getBody()->getContents()) { - return false; - } - - return true; - })) - ->willReturn(new HttpFullfilledPromise(new Response($httpStatusCode))); + ->method('sendRequest') + ->willReturn(new Response($httpStatusCode, [], '')); $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer ); - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); + $this->assertSame($expectedResultStatus, $result->getStatus()); + if ($expectEventReturned) { + $this->assertSame($event, $result->getEvent()); } - - $this->assertSame($expectedPromiseStatus, $promise->getState()); - $this->assertSame($expectedResponseStatus, $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); } public static function sendDataProvider(): iterable { yield [ 200, - PromiseInterface::FULFILLED, - ResponseStatus::success(), + ResultStatus::success(), + true, + ]; + + yield [ + 401, + ResultStatus::invalid(), + false, + ]; + + yield [ + 429, + ResultStatus::rateLimit(), + false, ]; yield [ 500, - PromiseInterface::REJECTED, - ResponseStatus::failed(), + ResultStatus::failed(), + false, ]; } - public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientException(): void + public function testSendFailsDueToHttpClientException(): void { - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $exception = new \Exception('foo'); $event = Event::createEvent(); @@ -213,52 +109,64 @@ public function testSendReturnsRejectedPromiseIfSendingFailedDueToHttpClientExce ->with($event) ->willReturn('{"foo":"bar"}'); - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); + $this->httpClient->expects($this->once()) + ->method('sendRequest') + ->will($this->throwException($exception)); + + $transport = new HttpTransport( + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), + $this->httpClient, + $this->payloadSerializer, + $logger + ); - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback(static function (string $content): StreamInterface { - return Utils::streamFor($content); - }); + $result = $transport->send($event); + + $this->assertSame(ResultStatus::failed(), $result->getStatus()); + } + + public function testSendFailsDueToCulrError(): void + { + $event = Event::createEvent(); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('error') + ->with('Failed to send the event to Sentry. Reason: "cURL Error (6) Could not resolve host: example.com".', ['event' => $event]); + + $this->payloadSerializer->expects($this->once()) + ->method('serialize') + ->with($event) + ->willReturn('{"foo":"bar"}'); $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn(new HttpRejectedPromise($exception)); + ->method('sendRequest') + ->willReturn(new Response(0, [], 'cURL Error (6) Could not resolve host: example.com')); $transport = new HttpTransport( - new Options(['dsn' => $dsn]), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer, $logger ); - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::failed(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::unknown(), $result->getStatus()); } /** * @group time-sensitive */ - public function testSendReturnsRejectedPromiseIfExceedingRateLimits(): void + public function testSendFailsDueToExceedingRateLimits(): void { ClockMock::withClockMock(1644105600); - $dsn = Dsn::createFromString('http://public@example.com/sentry/1'); $event = Event::createEvent(); /** @var LoggerInterface&MockObject $logger */ @@ -275,69 +183,40 @@ public function testSendReturnsRejectedPromiseIfExceedingRateLimits(): void ->with($event) ->willReturn('{"foo":"bar"}'); - $this->requestFactory->expects($this->once()) - ->method('createRequest') - ->with('POST', $dsn->getStoreApiEndpointUrl()) - ->willReturn(new Request('POST', 'http://www.example.com')); - - $this->streamFactory->expects($this->once()) - ->method('createStream') - ->with('{"foo":"bar"}') - ->willReturnCallback([Utils::class, 'streamFor']); - $this->httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturn(new HttpFullfilledPromise(new Response(429, ['Retry-After' => '60']))); + ->method('sendRequest') + ->willReturn(new Response(429, ['Retry-After' => ['60']], '')); $transport = new HttpTransport( - new Options(['dsn' => $dsn]), + new Options([ + 'dsn' => 'http://public@example.com/1', + ]), $this->httpClient, - $this->streamFactory, - $this->requestFactory, $this->payloadSerializer, $logger ); // Event should be sent, but the server should reply with a HTTP 429 - $promise = $transport->send($event); + $result = $transport->send($event); - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } - - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::rateLimit(), $result->getStatus()); // Event should not be sent at all because rate-limit is in effect - $promise = $transport->send($event); - - try { - $promiseResult = $promise->wait(); - } catch (RejectionException $exception) { - $promiseResult = $exception->getReason(); - } + $result = $transport->send($event); - $this->assertSame(PromiseInterface::REJECTED, $promise->getState()); - $this->assertSame(ResponseStatus::rateLimit(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); + $this->assertSame(ResultStatus::rateLimit(), $result->getStatus()); } public function testClose(): void { $transport = new HttpTransport( - new Options(['dsn' => 'http://public@example.com/sentry/1']), - $this->createMock(HttpAsyncClientInterface::class), - $this->createMock(StreamFactoryInterface::class), - $this->createMock(RequestFactoryInterface::class), + new Options(), + $this->createMock(HttpClientInterface::class), $this->createMock(PayloadSerializerInterface::class) ); - $promise = $transport->close(); + $result = $transport->close(); - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); + $this->assertSame(ResultStatus::success(), $result->getStatus()); } } diff --git a/tests/Transport/NullTransportTest.php b/tests/Transport/NullTransportTest.php deleted file mode 100644 index 954e3ee0b..000000000 --- a/tests/Transport/NullTransportTest.php +++ /dev/null @@ -1,44 +0,0 @@ -transport = new NullTransport(); - } - - public function testSend(): void - { - $event = Event::createEvent(); - - $promise = $this->transport->send($event); - $promiseResult = $promise->wait(); - - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertSame(ResponseStatus::skipped(), $promiseResult->getStatus()); - $this->assertSame($event, $promiseResult->getEvent()); - } - - public function testClose(): void - { - $promise = $this->transport->close(); - - $this->assertSame(PromiseInterface::FULFILLED, $promise->getState()); - $this->assertTrue($promise->wait()); - } -} diff --git a/tests/Transport/RateLimiterTest.php b/tests/Transport/RateLimiterTest.php index 22c3e5354..14315d401 100644 --- a/tests/Transport/RateLimiterTest.php +++ b/tests/Transport/RateLimiterTest.php @@ -4,14 +4,12 @@ namespace Sentry\Tests\Transport; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use Sentry\Event; use Sentry\EventType; -use Sentry\ResponseStatus; +use Sentry\HttpClient\Response; use Sentry\Transport\RateLimiter; use Symfony\Bridge\PhpUnit\ClockMock; @@ -39,56 +37,55 @@ protected function setUp(): void /** * @dataProvider handleResponseDataProvider */ - public function testHandleResponse(Event $event, ResponseInterface $response, ResponseStatus $responseStatus): void + public function testHandleResponse(Event $event, Response $response, int $responseStatusCode): void { ClockMock::withClockMock(1644105600); - $this->logger->expects($responseStatus === ResponseStatus::success() ? $this->never() : $this->once()) + $this->logger->expects($response->isSuccess() ? $this->never() : $this->once()) ->method('warning') ->with('Rate limited exceeded for requests of type "event", backing off until "2022-02-06T00:01:00+00:00".', ['event' => $event]); - $transportResponse = $this->rateLimiter->handleResponse($event, $response); + $rateLimiterResponse = $this->rateLimiter->handleResponse($event, $response); - $this->assertSame($responseStatus, $transportResponse->getStatus()); - $this->assertSame($event, $transportResponse->getEvent()); + $this->assertSame($responseStatusCode, $rateLimiterResponse->getStatusCode()); } public static function handleResponseDataProvider(): \Generator { yield 'Rate limits headers missing' => [ Event::createEvent(), - new Response(), - ResponseStatus::success(), + new Response(200, [], ''), + 200, ]; yield 'Back-off using X-Sentry-Rate-Limits header with single category' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], ''), + 429, ]; yield 'Back-off using X-Sentry-Rate-Limits header with multiple categories' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60:error;transaction:org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60:error;transaction:org']], ''), + 429, ]; yield 'Back-off using X-Sentry-Rate-Limits header with missing categories should lock them all' => [ Event::createEvent(), - new Response(429, ['X-Sentry-Rate-Limits' => '60::org']), - ResponseStatus::rateLimit(), + new Response(429, ['X-Sentry-Rate-Limits' => ['60::org']], ''), + 429, ]; yield 'Back-off using Retry-After header with number-based value' => [ Event::createEvent(), - new Response(429, ['Retry-After' => '60']), - ResponseStatus::rateLimit(), + new Response(429, ['Retry-After' => ['60']], ''), + 429, ]; yield 'Back-off using Retry-After header with date-based value' => [ Event::createEvent(), - new Response(429, ['Retry-After' => 'Sun, 02 February 2022 00:01:00 GMT']), - ResponseStatus::rateLimit(), + new Response(429, ['Retry-After' => ['Sun, 02 February 2022 00:01:00 GMT']], ''), + 429, ]; } @@ -102,7 +99,7 @@ public function testIsRateLimited(): void // Events should be rate-limited for 60 seconds, but transactions should // still be allowed to be sent - $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => '60:error:org'])); + $this->rateLimiter->handleResponse(Event::createEvent(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:error:org']], '')); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); $this->assertFalse($this->rateLimiter->isRateLimited(EventType::transaction())); @@ -115,7 +112,7 @@ public function testIsRateLimited(): void // Both events and transactions should be rate-limited if all categories // are - $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => '60:all:org'])); + $this->rateLimiter->handleResponse(Event::createTransaction(), new Response(429, ['X-Sentry-Rate-Limits' => ['60:all:org']], '')); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::event())); $this->assertTrue($this->rateLimiter->isRateLimited(EventType::transaction())); diff --git a/tests/ResponseStatusTest.php b/tests/Transport/ResultStatusTest.php similarity index 53% rename from tests/ResponseStatusTest.php rename to tests/Transport/ResultStatusTest.php index 3edfba746..cea73dc00 100644 --- a/tests/ResponseStatusTest.php +++ b/tests/Transport/ResultStatusTest.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace Sentry\Tests; +namespace Sentry\Tests\Transport; use PHPUnit\Framework\TestCase; -use Sentry\ResponseStatus; +use Sentry\Transport\ResultStatus; -final class ResponseStatusTest extends TestCase +final class ResultStatusTest extends TestCase { /** * @dataProvider toStringDataProvider */ - public function testToString(ResponseStatus $responseStatus, string $expectedStringRepresentation): void + public function testToString(ResultStatus $responseStatus, string $expectedStringRepresentation): void { $this->assertSame($expectedStringRepresentation, (string) $responseStatus); } @@ -20,32 +20,32 @@ public function testToString(ResponseStatus $responseStatus, string $expectedStr public static function toStringDataProvider(): iterable { yield [ - ResponseStatus::success(), + ResultStatus::success(), 'SUCCESS', ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 'FAILED', ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 'INVALID', ]; yield [ - ResponseStatus::skipped(), + ResultStatus::skipped(), 'SKIPPED', ]; yield [ - ResponseStatus::rateLimit(), + ResultStatus::rateLimit(), 'RATE_LIMIT', ]; yield [ - ResponseStatus::unknown(), + ResultStatus::unknown(), 'UNKNOWN', ]; } @@ -53,59 +53,59 @@ public static function toStringDataProvider(): iterable /** * @dataProvider createFromHttpStatusCodeDataProvider */ - public function testCreateFromHttpStatusCode(ResponseStatus $expectedResponseStatus, int $httpStatusCode): void + public function testCreateFromHttpStatusCode(ResultStatus $expectedResultStatus, int $httpStatusCode): void { - $this->assertSame($expectedResponseStatus, ResponseStatus::createFromHttpStatusCode($httpStatusCode)); + $this->assertSame($expectedResultStatus, ResultStatus::createFromHttpStatusCode($httpStatusCode)); } public static function createFromHttpStatusCodeDataProvider(): iterable { yield [ - ResponseStatus::success(), + ResultStatus::success(), 200, ]; yield [ - ResponseStatus::success(), + ResultStatus::success(), 299, ]; yield [ - ResponseStatus::rateLimit(), + ResultStatus::rateLimit(), 429, ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 400, ]; yield [ - ResponseStatus::invalid(), + ResultStatus::invalid(), 499, ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 500, ]; yield [ - ResponseStatus::failed(), + ResultStatus::failed(), 501, ]; yield [ - ResponseStatus::unknown(), + ResultStatus::unknown(), 199, ]; } public function testStrictComparison(): void { - $responseStatus1 = ResponseStatus::unknown(); - $responseStatus2 = ResponseStatus::unknown(); - $responseStatus3 = ResponseStatus::skipped(); + $responseStatus1 = ResultStatus::unknown(); + $responseStatus2 = ResultStatus::unknown(); + $responseStatus3 = ResultStatus::skipped(); $this->assertSame($responseStatus1, $responseStatus2); $this->assertNotSame($responseStatus1, $responseStatus3); diff --git a/tests/Util/HttpTest.php b/tests/Util/HttpTest.php new file mode 100644 index 000000000..342ad1b75 --- /dev/null +++ b/tests/Util/HttpTest.php @@ -0,0 +1,71 @@ +assertSame($expectedResult, Http::getRequestHeaders($dsn, $sdkIdentifier, $sdkVersion)); + } + + public static function getRequestHeadersDataProvider(): \Generator + { + yield [ + Dsn::createFromString('http://public@example.com/1'), + 'sentry.sdk.identifier', + '1.2.3', + [ + 'Content-Type: application/x-sentry-envelope', + 'X-Sentry-Auth: Sentry sentry_version=7, sentry_client=sentry.sdk.identifier/1.2.3, sentry_key=public', + ], + ]; + } + + /** + * @dataProvider parseResponseHeadersDataProvider + */ + public function testParseResponseHeaders(string $headerline, $expectedResult): void + { + $responseHeaders = []; + + Http::parseResponseHeaders($headerline, $responseHeaders); + + $this->assertSame($expectedResult, $responseHeaders); + } + + public static function parseResponseHeadersDataProvider(): \Generator + { + yield [ + 'Content-Type: application/json', + [ + 'Content-Type' => [ + 'application/json', + ], + ], + ]; + + yield [ + 'X-Sentry-Rate-Limits: 60:transaction:key,2700:default;error;security:organization', + [ + 'X-Sentry-Rate-Limits' => [ + '60:transaction:key,2700:default;error;security:organization', + ], + ], + ]; + + yield [ + 'Invalid', + [], + ]; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0632c8aa0..4766219f0 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,8 +2,6 @@ declare(strict_types=1); -use Http\Discovery\ClassDiscovery; -use Http\Discovery\Strategy\MockClientStrategy; use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Tracing\Span; @@ -12,8 +10,6 @@ require_once __DIR__ . '/../vendor/autoload.php'; -ClassDiscovery::appendStrategy(MockClientStrategy::class); - // According to the Symfony documentation the proper way to register the mocked // functions for a certain class would be to configure the listener in the // phpunit.xml file, however in our case it doesn't work because PHPUnit loads diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt index facb159b9..f52c12932 100644 --- a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -15,15 +15,12 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -34,22 +31,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -62,7 +54,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_captures_fatal_error.phpt b/tests/phpt/error_handler_captures_fatal_error.phpt index 2f684a12a..e7b457d2b 100644 --- a/tests/phpt/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/error_handler_captures_fatal_error.phpt @@ -7,16 +7,13 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\ErrorHandler; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +24,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -51,7 +43,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt index ac2c4e223..0063489b7 100644 --- a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt +++ b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt @@ -9,15 +9,12 @@ declare(strict_types=1); namespace Sentry\Tests; -use GuzzleHttp\Promise\FulfilledPromise; -use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -28,22 +25,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -53,7 +45,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt index 6b4d8e93c..f6017b8a5 100644 --- a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt +++ b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt @@ -15,10 +15,9 @@ use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -29,20 +28,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -57,7 +53,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt index e47750b5c..2ab9a423e 100644 --- a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -12,10 +12,9 @@ use GuzzleHttp\Promise\PromiseInterface; use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -26,22 +25,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; - - return new FulfilledPromise(new Response(ResponseStatus::success())); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + echo 'Transport called' . PHP_EOL; + + return new Result(ResultStatus::success()); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -53,7 +47,7 @@ $options = [ ]; $client = ClientBuilder::create($options) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index 870a0696f..4c66a5f9a 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -15,10 +15,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\ErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -29,22 +28,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -57,7 +51,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt index d264e9810..ba82e9bca 100644 --- a/tests/phpt/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/fatal_error_integration_captures_fatal_error.phpt @@ -13,10 +13,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +26,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -54,7 +48,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client); diff --git a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt index 00db34a07..8010b35b2 100644 --- a/tests/phpt/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/fatal_error_integration_respects_error_types_option.phpt @@ -13,10 +13,9 @@ use Sentry\ClientBuilder; use Sentry\Event; use Sentry\Integration\FatalErrorListenerIntegration; use Sentry\Options; -use Sentry\Response; -use Sentry\ResponseStatus; use Sentry\SentrySdk; -use Sentry\Transport\TransportFactoryInterface; +use Sentry\Transport\Result; +use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; $vendor = __DIR__; @@ -27,22 +26,17 @@ while (!file_exists($vendor . '/vendor')) { require $vendor . '/vendor/autoload.php'; -$transportFactory = new class implements TransportFactoryInterface { - public function create(Options $options): TransportInterface +$transport = new class implements TransportInterface { + public function send(Event $event): Result { - return new class implements TransportInterface { - public function send(Event $event): PromiseInterface - { - echo 'Transport called (it should not have been)' . PHP_EOL; + echo 'Transport called' . PHP_EOL; - return new FulfilledPromise(new Response(ResponseStatus::success())); - } + return new Result(ResultStatus::success()); + } - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); } }; @@ -55,7 +49,7 @@ $options = new Options([ ]); $client = (new ClientBuilder($options)) - ->setTransportFactory($transportFactory) + ->setTransport($transport) ->getClient(); SentrySdk::getCurrentHub()->bindClient($client);