diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/01-feature.yml similarity index 91% rename from .github/ISSUE_TEMPLATE/feature.yml rename to .github/ISSUE_TEMPLATE/01-feature.yml index 9f331d37..a7aab2fd 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/01-feature.yml @@ -1,6 +1,6 @@ name: πŸ’‘ Feature Request -description: Create a feature request for this SDK. -labels: 'enhancement' +description: Propose new functionality for the SDK +labels: ["Symfony", "Feature"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/02-improvement.yml b/.github/ISSUE_TEMPLATE/02-improvement.yml new file mode 100644 index 00000000..ab64fdca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-improvement.yml @@ -0,0 +1,30 @@ +name: πŸ’‘ Improvement +description: Propose an improvement for existing functionality of the SDK +labels: ["Symfony", "Improvement"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to file a request! Please fill out this form as completely as possible. + - type: textarea + id: problem + attributes: + label: Problem Statement + description: A clear and concise description of what you want and what your use case is. + placeholder: |- + I want to make whirled peas, but Sentry doesn't blend. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Solution Brainstorm + description: We know you have bright ideas to share ... share away, friend. + placeholder: |- + Add a blender to Sentry. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks πŸ™ + Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/03-bug.yml similarity index 97% rename from .github/ISSUE_TEMPLATE/bug.yml rename to .github/ISSUE_TEMPLATE/03-bug.yml index 7d62230e..a4a1d26b 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/03-bug.yml @@ -1,5 +1,6 @@ name: 🐞 Bug Report description: Tell us about something that's not working the way we (probably) intend. +labels: ["Symfony", "Bug"] body: - type: dropdown id: type diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b88a67a7..a0982451 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,4 +3,14 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: weekly + interval: monthly + - package-ecosystem: "composer" + directory: "/" + allow: + - dependency-name: "*phpstan*" + schedule: + interval: monthly + groups: + composer: + patterns: + - "*phpstan*" diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 9f380b6c..fbf88435 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -18,16 +18,23 @@ jobs: runs-on: ubuntu-latest name: Release version steps: + - name: Get auth token + id: token + uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + with: + app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} + private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 with: - token: ${{ secrets.GH_RELEASE_PAT }} + token: ${{ steps.token.outputs.token }} fetch-depth: 0 - name: Prepare release - uses: getsentry/action-prepare-release@3cea80dc3938c0baf5ec4ce752ecb311f8780cdc #v1.6.4 + uses: getsentry/action-prepare-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index e9012b41..91c0cee4 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -17,57 +17,57 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - name: Install dependencies - uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a # v3.0.0 + uses: ramsey/composer-install@v3 with: composer-options: --prefer-dist - name: Run script - run: composer phpcs + run: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run phpstan: name: PHPStan runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@v2 with: php-version: '8.3' - name: Install dependencies - uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a # v3.0.0 + uses: ramsey/composer-install@v3 with: composer-options: --prefer-dist - name: Run script - run: composer phpstan + run: vendor/bin/phpstan analyse psalm: name: Psalm runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@v2 with: php-version: '8.3' - name: Install dependencies - uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a # v3.0.0 + uses: ramsey/composer-install@v3 with: composer-options: --prefer-dist - name: Run script - run: composer psalm -- --php-version=8.0 + run: vendor/bin/psalm --php-version=8.0 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b9ff0431..8c3dabe9 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,11 +1,10 @@ -name: Continuous Integration +name: CI on: - pull_request: null + pull_request: push: branches: - master - - develop - release/** permissions: @@ -28,6 +27,7 @@ jobs: - '8.1' - '8.2' - '8.3' + - '8.4' symfony-version: - 4.4.* - 5.* @@ -52,6 +52,8 @@ jobs: symfony-version: 7.* - php: '8.1' symfony-version: 7.* + - php: '8.4' + symfony-version: 4.4.* include: - php: '7.2' symfony-version: 4.4.* @@ -68,12 +70,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: pcov @@ -82,12 +84,12 @@ jobs: - name: Setup Problem Matchers for PHPUnit run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Update PHPUnit - run: composer require --dev phpunit/phpunit ^9.3.9 --no-update - if: matrix.php == '8.0' && matrix.dependencies == 'lowest' + # These dependencies are not used running the tests but can cause deprecation warnings so we remove them before running the tests + - name: Remove unused dependencies + run: composer remove vimeo/psalm phpstan/phpstan friendsofphp/php-cs-fixer --dev --no-interaction --no-update - name: Install dependencies - uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a # v3.0.0 + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} composer-options: --prefer-dist @@ -96,7 +98,7 @@ jobs: run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml - name: Upload code coverage - uses: codecov/codecov-action@c16abc29c95fcf9174b58eb7e1abf4c866893bc8 # v4.1.1 + uses: codecov/codecov-action@v5 with: file: build/coverage-report.xml token: ${{ secrets.CODECOV_TOKEN }} @@ -118,15 +120,15 @@ jobs: - php: '8.0' dependencies: lowest symfony-version: 4.4.* - - php: '8.3' + - php: '8.4' dependencies: highest steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@v4 - name: Setup PHP - uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.2 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: pcov @@ -139,7 +141,7 @@ jobs: run: composer remove doctrine/dbal doctrine/doctrine-bundle symfony/messenger symfony/twig-bundle symfony/cache symfony/http-client --dev --no-update - name: Install dependencies - uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a # v3.0.0 + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} composer-options: --prefer-dist @@ -148,7 +150,7 @@ jobs: run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml - name: Upload code coverage - uses: codecov/codecov-action@c16abc29c95fcf9174b58eb7e1abf4c866893bc8 # v4.1.1 + uses: codecov/codecov-action@v5 with: file: build/coverage-report.xml token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5906a68d..3f0de29f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -31,6 +31,10 @@ 'method' => 'multi', 'property' => 'multi', ], + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => false, + 'elements' => ['arrays'], + ], ]) ->setFinder( PhpCsFixer\Finder::create() diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec8b451..d011c1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,470 +1,181 @@ -# Changelog +# CHANGELOG -## 4.14.0 +## 5.3.0 -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.14.0. +The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.3.0. ### Features -- Add support for `doctrine/dbal: ^4.0` [(#811)](https://github.com/getsentry/sentry-symfony/pull/811) +- Implement `NamespacedPoolInterface` for `TraceableCacheAdapterForV3` [(#927)](https://github.com/getsentry/sentry-symfony/pull/927) -### Bug Fixes - -- Fix overwritting `DbalTracingPass` [(#808)](https://github.com/getsentry/sentry-symfony/pull/808) -- Use `AbstractDriverMiddleware` [(#810)](https://github.com/getsentry/sentry-symfony/pull/810) - -## 4.13.2 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.13.2. - -### Bug Fixes - -- Fix detection of the installed version of `symfony/http-client` [(#797)](https://github.com/getsentry/sentry-symfony/pull/797) - -## 4.13.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.13.1. - -### Bug Fixes - -- Fix the HTTP client decoration when no `http_client` service is registered [(#792)](https://github.com/getsentry/sentry-symfony/pull/792) - -## 4.13.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.13.0. - -### Features - -- Add support for Symfony 7 [(#761)](https://github.com/getsentry/sentry-symfony/pull/761) +### Misc -### Bug Fixes +- Update minimum required version of `sentry/sentry` to `^4.14.1` +- Remove support for `traceparent` header [(#928)](https://github.com/getsentry/sentry-symfony/pull/928) -- Fix the decoration of the HTTP client when tracing is enabled [(#786)](https://github.com/getsentry/sentry-symfony/pull/786) +## 5.2.0 -## 4.12.0 +The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.2.0. ### Features -- Add support for `symfony/psr-http-message-bridge: ^6.4` [(#750)](https://github.com/getsentry/sentry-symfony/pull/750) -- Report individual exceptions from `DelayedMessageHandlingException` [(#760)](https://github.com/getsentry/sentry-symfony/pull/760) - -## 4.11.0 +- Allow to configure the logger via the `sentry.yaml` configuration file [(#899)](https://github.com/getsentry/sentry-symfony/pull/899) -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.11.0. + ```yaml + sentry: + dsn: "%env(SENTRY_DSN)%" + options: + logger: "sentry.logger" + + services: + sentry.logger: + class: 'Sentry\Logger\DebugFileLogger' + arguments: + $filePath: '../../var/log/sentry.log' + ``` ### Bug Fixes -- Silence `TokenInterface::isAuthenticated` deprecation in `LoginListener` [(#755)](https://github.com/getsentry/sentry-symfony/pull/755) +- Fixed updating the user context when a route is marked as stateless [(#910)](https://github.com/getsentry/sentry-symfony/pull/910) ### Misc -- Prefer the `SENTRY_RELEASE` environment variable over the package root version [(#753)](https://github.com/getsentry/sentry-symfony/pull/753) +- Remove `symfony/security-core` and `symfony/security-http` as dependencies [(#912)](https://github.com/getsentry/sentry-symfony/pull/912) -## 4.10.0 +## 5.1.0 -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.10.0. +The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.1.0. ### Features -- Tracing without Performance [(#742)](https://github.com/getsentry/sentry-symfony/pull/742) - - The SDK will now continue a trace from incoming HTTP requests, even if performance is not enabled. - 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(). - -- Add `ignore_exceptions` and `ignore_transactions` options [(#724)](https://github.com/getsentry/sentry-symfony/pull/724) - -### Misc - -- Improve setting logged-in users on the scope [(#720)](https://github.com/getsentry/sentry-symfony/pull/720) -- Move DB span tags to span data [(#743)](https://github.com/getsentry/sentry-symfony/pull/743) -- Set the span status when tracing an HTTP client request [(#748)](https://github.com/getsentry/sentry-symfony/pull/748) - -## 4.9.2 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.9.2. - -### Bug Fixes - -- We decided to revert two previous PRs that aimed to remove deprecation warnings during test runs [(#736)](https://github.com/getsentry/sentry-symfony/pull/736) - - - Revert: Add a new Doctrine DBAL tracing driver that does not implement the deprecated `VersionAwarePlatformDriver` [(#723)](https://github.com/getsentry/sentry-symfony/pull/723) - - Revert: Fix a regression in `TracingDriverForV32` by adding `VersionAwarePlatformDriver::createDatabasePlatformForVersion` [(#731)](https://github.com/getsentry/sentry-symfony/pull/731) - -We are sorry for the inconvenience caused by these changes. - -## 4.9.1 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.9.1. +- The SDK was updated to support PHP 8.4 [(#893)](https://github.com/getsentry/sentry-symfony/pull/893) +- Set the status for CLI command transactions based on the exit code [(#891)](https://github.com/getsentry/sentry-symfony/pull/891) ### Bug Fixes -- Fix a regression in `TracingDriverForV32` by adding `VersionAwarePlatformDriver::createDatabasePlatformForVersion` [(#731)](https://github.com/getsentry/sentry-symfony/pull/731) +- Fix including request data on transactions [(#879)](https://github.com/getsentry/sentry-symfony/pull/879) -## 4.9.0 +## 5.0.1 -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.9.0. - -### Features - -- Add a new Doctrine DBAL tracing driver that does not implement the deprecated `VersionAwarePlatformDriver` [(#723)](https://github.com/getsentry/sentry-symfony/pull/723) - - The driver is automatically picked if `doctrine/dbal` version `3.2.0` or higher is installed. +The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.0.1. ### Bug Fixes -- Fix config type of `http_connect_timeout`and `http_timeout` options [(#721)](https://github.com/getsentry/sentry-symfony/pull/721) - -### Misc - -- Bump the underlying PHP SDK to version `^3.19` [(#725)](https://github.com/getsentry/sentry-symfony/pull/725) - -## 4.8.0 - -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.8.0. +- Add missing `setCallbackWrapper` method to `TraceableCacheAdapterTrait` [(#841)](https://github.com/getsentry/sentry-symfony/pull/841) +- Fix detection of the `symfony/http-client` being installed [(#858)](https://github.com/getsentry/sentry-symfony/pull/858) -### Features +## 5.0.0 -- Set cache keys as span descriptions [(#677)](https://github.com/getsentry/sentry-symfony/pull/677) +The Sentry SDK team is thrilled to announce the immediate availability of Sentry Symfony SDK v5.0.0. - To better identify the source of a cache operation, we now set the cache key as the description of `cache` op spans. +### Breaking Change -### Bug Fixes +Please refer to the [UPGRADE-5.0.md](https://github.com/getsentry/sentry-symfony/blob/master/UPGRADE-5.0.md) guide for a complete list of breaking changes. -- Add direct dependency for `guzzlehttp/psr7` [(#708)](https://github.com/getsentry/sentry-symfony/pull/708) -- Drop `kernel.build_dir` param below Symfony 5.2 [(#711)](https://github.com/getsentry/sentry-symfony/pull/711) +This version adds support for the underlying [Sentry PHP SDK v4.0](https://github.com/getsentry/sentry-php). +Please refer to the PHP SDK [sentry-php/UPGRADE-4.0.md](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-4.0.md) guide for a complete list of breaking changes. -## 4.7.0 +- This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry. -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.7.0. + 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`. -### Features +- You need to have `ext-curl` installed to use the SDK. -- Add `profiles_sample_rate` config option [(#698)](https://github.com/getsentry/sentry-symfony/pull/698) +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead. + Previously, both `Symfony\Component\ErrorHandler\Error\FatalError` and `Symfony\Component\Debug\Exception\FatalErrorException` were ignored by default. + To continue ignoring these exceptions, make the following changes to the config file: - With this new config option, you can now use our new profiling feature in Symfony as well. - Please consult https://github.com/getsentry/sentry-php/releases/3.15.0 for setup instructions. + ```yaml + // config/packages/sentry.yaml -## 4.6.0 + sentry: + options: + ignore_exceptions: + - 'Symfony\Component\ErrorHandler\Error\FatalError' + - 'Symfony\Component\Debug\Exception\FatalErrorException' + ``` -The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.6.0. -This release contains a colorful bouquet of new features. + 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. ### Features -- Report exceptions to Sentry as unhandled by default [(#674)](https://github.com/getsentry/sentry-symfony/pull/674) +- Add support for Sentry Developer Metrics [(#1619)](https://github.com/getsentry/sentry-php/pull/1619) - All unhandled exceptions will now be marked as `handled: false`. You can query for such events on the issues list page, - by searching for `error.handled:false`. - -- Exceptions from messages which will be retried are sent to Sentry as handled [(#674)](https://github.com/getsentry/sentry-symfony/pull/674) + ```php + use function Sentry\metrics; - All unhandled exceptions happening on the message bus will now be unpacked and reported individually. - The `WorkerMessageFailedEvent::willRetry` property is used to determine the `handled` value of the event sent to Sentry. + // Add 4 to a counter named hits + metrics()->increment(key: 'hits', value: 4); -- Add `register_error_handler` config option [(#687)](https://github.com/getsentry/sentry-symfony/pull/687) + // Add 25 to a distribution named response_time with unit milliseconds + metrics()->distribution(key: 'response_time', value: 25, unit: MetricsUnit::millisecond()); - With this option, you can disable the global error and exception handlers of the base PHP SDK. - If disabled, only events logged by Monolog will be sent to Sentry. + // Add 2 to gauge named parallel_requests, tagged with type: "a" + metrics()->gauge(key: 'parallel_requests', value: 2, tags: ['type': 'a']); - ```yaml - sentry: - dsn: '%env(SENTRY_DSN)%' - register_error_listener: false - register_error_handler: false - - monolog: - handlers: - sentry: - type: sentry - level: !php/const Monolog\Logger::ERROR - hub_id: Sentry\State\HubInterface + // Add a user's email to a set named users.sessions, tagged with role: "admin" + metrics()->set('users.sessions', 'jane.doe@example.com', null, ['role' => User::admin()]); ``` -- Add `before_send_transaction` [(#691)](https://github.com/getsentry/sentry-symfony/pull/691) - - Similar to `before_send`, you can now apply additional logic for `transaction` events. - You can mutate the `transaction` event before it is sent to Sentry. If your callback returns `null`, - the event is dropped. + Metrics are automatically sent to Sentry at the end of a request, hooking into Symfony's `kernel.terminate` event. - ```yaml - sentry: - options: - before_send_transaction: 'sentry.callback.before_send_transaction' - - services: - sentry.callback.before_send_transaction: - class: 'App\Service\Sentry' - factory: [ '@App\Service\Sentry', 'getBeforeSendTransaction' ] - ``` +- Add new fluent APIs [(#1601)](https://github.com/getsentry/sentry-php/pull/1601) ```php - setName('GET /example'); + $transactionContext->setOp('http.server'); + + // After + $transactionContext = (new TransactionContext()) + ->setName('GET /example'); + ->setOp('http.server'); ``` -- Use the `_route` attribute as the transaction name [(#692)](https://github.com/getsentry/sentry-symfony/pull/692) - - If you're using named routes, the SDK will default to use this attribute as the transaction name. - With this change, you should be able to see a full list of your transactions on the performance page, - instead of `<< unparameterized >>`. - - You may need to update your starred transactions as well as your dashboards due to this change. - -### Bug Fixes - -- Sanatize HTTP client spans [(#690)](https://github.com/getsentry/sentry-symfony/pull/690) - -## 4.5.0 (2022-11-28) - -- Symfony version 3.4 is no longer supported - - Drop Symfony support below 4.4 (#643) -- feat: Add support for tracing of Symfony HTTP client requests (#606) - - feat: Add support for HTTP client baggage propagation (#663) - - ref: Add proper HTTP client span descriptions (#680) -- feat: Support logging the impersonator user, if any (#647) -- ref: Use a constant for the SDK version (#662) - -## 4.4.0 (2022-10-20) - -- feat: Add support for Dynamic Sampling (#665) - -## 4.3.1 (2022-10-10) - -- fix: Update span ops (#655) - -## 4.3.0 (2022-05-30) - -- Fix compatibility issue with Symfony `>= 6.1.0` (#635) -- Add `TracingDriverConnectionInterface::getNativeConnection()` method to get the original driver connection (#597) -- Add `options.http_timeout` and `options.http_connect_timeout` configuration options (#593) - -## 4.2.10 (2022-05-17) - -- Fix compatibility issue with Twig >= 3.4.0 (#628) - -## 4.2.9 (2022-05-03) +- Simplify the breadcrumb API [(#1603)](https://github.com/getsentry/sentry-php/pull/1603) -- Fix deprecation notice thrown when instrumenting the `PDOStatement::bindParam()` method and passing `$length = null` on DBAL `2.x` (#613) - -## 4.2.8 (2022-03-31) - -- Fix compatibility issue with Doctrine Bundle `>= 2.6.0` (#608) - -## 4.2.7 (2022-02-18) - -- Fix deprecation notice thrown when instrumenting the `PDOStatement::bindParam()` method and passing `$length = null` (#586) - -## 4.2.6 (2022-01-10) - -- Add support for `symfony/cache-contracts` package version `3.x` (#588) - -## 4.2.5 (2021-12-13) - -- Add support for Symfony 6 (#566) -- Fix fatal errors logged twice on Symfony `3.4` (#570) - -## 4.2.4 (2021-10-20) - -- Add return typehints to the methods of the `SentryExtension` class to prepare for Symfony 6 (#563) -- Fix setting the IP address on the user context when it's not available (#565) -- Fix wrong method existence check in `TracingDriverConnection::errorCode()` (#568) -- Fix decoration of the Doctrine DBAL connection when it implemented the `ServerInfoAwareConnection` interface (#567) - -## 4.2.3 (2021-09-21) - -- Fix: Test if `TracingStatement` exists before attempting to create the class alias, otherwise it breaks when opcache is enabled. (#552) -- Fix: Pass logger from `logger` config option to `TransportFactory` (#555) -- Improve the compatibility layer with Doctrine DBAL to avoid deprecations notices (#553) - -## 4.2.2 (2021-08-30) - -- Fix missing instrumentation of the `Statement::execute()` method of Doctrine DBAL (#548) - -## 4.2.1 (2021-08-24) - -- Fix return type for `TracingDriver::getDatabase()` method (#541) -- Avoid throwing exception from the `TraceableCacheAdapterTrait::prune()` and `TraceableCacheAdapterTrait::reset()` methods when the decorated adapter does not implement the respective interfaces (#543) - -## 4.2.0 (2021-08-12) - -- Log the bus name, receiver name and message class name as event tags when using Symfony Messenger (#492) -- Make the transport factory configurable in the bundle's config (#504) -- Add the `sentry_trace_meta()` Twig function to print the `sentry-trace` HTML meta tag (#510) -- Make the list of commands for which distributed tracing is active configurable (#515) -- Introduce `TracingDriverConnection::getWrappedConnection()` (#536) -- Add the `logger` config option to ease setting a PSR-3 logger to debug the SDK (#538) -- Bump requirement for DBAL tracing to `^2.13|^3`; simplify the DBAL tracing feature (#527) - -## 4.1.4 (2021-06-18) - -- Fix decoration of cache adapters inheriting parent service (#525) -- Fix extraction of the username of the logged-in user in Symfony `5.3` (#518) - -## 4.1.3 (2021-05-31) - -- Fix missing require of the `symfony/cache-contracts` package (#506) - -## 4.1.2 (2021-05-17) - -- Fix the check of the existence of the `CacheItem` class while attempting to enable the cache instrumentation (#501) - -## 4.1.1 (2021-05-10) - -- Fix the conditions to automatically enable the cache instrumentation when possible (#487) -- Fix deprecations triggered by Symfony `5.3` (#489) -- Fix fatal error when the `SERVER_PROTOCOL` header is missing (#495) - -## 4.1.0 (2021-04-19) - -- Avoid failures when the `RequestFetcher` fails to translate the `Request` (#472) -- Add support for distributed tracing of Symfony request events (#423) -- Add support for distributed tracing of Twig template rendering (#430) -- Add support for distributed tracing of SQL queries while using Doctrine DBAL (#426) -- Add support for distributed tracing when running a console command (#455) -- Add support for distributed tracing of cache pools (#477) -- Add the full CLI command string to the extra context (#352) -- Deprecate the `Sentry\SentryBundle\EventListener\ConsoleCommandListener` class in favor of its parent class `Sentry\SentryBundle\EventListener\ConsoleListener` (#429) -- Lower the required version of `symfony/psr-http-message-bridge` to allow installing it on a project that uses Symfony `3.4.x` components only (#480) - -## 4.0.3 (2021-03-03) - -- Fix regression from #454 for `null` value on DSN not disabling Sentry (#457) - -## 4.0.2 (2021-03-03) - -- Add `kernel.project_dir` to `prefixes` default value to trim paths to the project root (#434) -- Fix `null`, `false` or empty value not disabling Sentry (#454) - -## 4.0.1 (2021-01-26) - -- Add missing `capture-soft-fails` option to the XSD schema for the XML config (#417) -- Fix regression that send PII even when the `send_default_pii` option is off (#425) -- Fix capture of console errors when the `register_error_listener` option is disabled (#427) - -## 4.0.0 (2021-01-19) - -**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. - -- Enable back all error listeners from base SDK integration (#322) -- Add `options.traces_sampler` and `options.traces_sample_rate` configuration options (#385) -- [BC BREAK] Remove the `options.project_root` configuration option. Instead of setting it, use a combination of `options.in_app_include` and `options.in_app_exclude` (#385) -- [BC BREAK] Remove the `options.excluded_exceptions` configuration option. Instead of setting it, configure the `IgnoreErrorsIntegration` integration (#385) -- [BC BREAK] Refactorize the `ConsoleCommandListener`, `ErrorListener`, `RequestListener` and `SubRequestListener` event listeners (#387) -- Registered the CLI commands as lazy services (#373) -- [BC BREAK] Refactorize the configuration tree and the definitions of some container services (#401) -- Support the XML format for the bundle configuration (#401) -- PHP 8 support (#399, thanks to @Yozhef) -- Retrieve the request from the `RequestStack` when using the `RequestIntegration` integration (#361) -- Reorganize the folder structure and change CS standard (#405) -- [BC BREAK] Remove the `monolog` configuration option. Instead, register the service manually (#406) -- [BC BREAK] Remove the `listener_priorities` configuration option. Instead, use a compiler pass to change the priority of the listeners (#407) -- Prefer usage of the existing `Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface` service for the `RequestFetcher` class (#409) -- [BC BREAK] Change the priorities of the `RequestListener` and `SubRequestListener` listeners (#414) - -## 3.5.3 (2020-10-13) - -- Refactors and fixes class aliases for more robustness (#315 #359, thanks to @guilliamxavier) - -## 3.5.2 (2020-07-08) - -- Use `jean85/pretty-package-versions` `^1.5` to leverage the new `getRootPackageVersion` method (c8799ac) -- Fix support for PHP preloading (#354, thanks to @annuh) -- Fix `capture_soft_fails: false` option for the Messenger (#353) - -## 3.5.1 (2020-05-07) - -- Capture events using the `Hub` in the `MessengerListener` to avoid loosing `Scope` data (#339, thanks to @sh41) -- Capture multiple events if multiple exceptions are generated in a Messenger Worker context (#340, thanks to @emarref) - -## 3.5.0 (2020-05-04) - -- Capture and flush messages in a Messenger Worker context (#326, thanks to @emarref) -- Support Composer 2 (#335) -- Avoid issues with dependency lower bound, fix #331 (#335) - -## 3.4.4 (2020-03-16) - -- Improve `release` option default value (#325) - -## 3.4.3 (2020-02-03) - -- Change default of `in_app_include` to empty, due to getsentry/sentry-php#958 (#311) -- Improve class_alias robustness (#315) - -## 3.4.2 (2020-01-29) - -- Remove space from classname used with `class_alias` (#313) - -## 3.4.1 (2020-01-24) - -- Fix issue due to usage of `class_alias` to fix deprecations, which could break BC layers of third party packages (#309, thanks to @scheb) - -## 3.4.0 (2020-01-20) - -- Add support for `sentry/sentry` 2.3 (#298) -- Drop support for `sentry/sentry` < 2.3 (#298) -- Add support to `in_app_include` client option (#298) -- Remap `excluded_exceptions` option to use the new `IgnoreErrorsIntegration` (#298) - -## 3.3.2 (2020-01-16) - -- Fix issue with exception listener under Symfony 4.3 (#301) - -## 3.3.1 (2020-01-14) - -- Fixed Release - -## 3.3.0 (2020-01-14) - -- Add support for Symfony 5.0 (#266, thanks to @Big-Shark) -- Drop support for Symfony < 3.4 (#277) -- Add default value for the `release` option, using the detected root package version (#291 #292, thanks to @Ocramius) - -## 3.2.1 (2019-12-19) - -- Fix handling of command with no name on `ConsoleListener` (#261) -- Remove check by AuthorizationChecker in `RequestListener` (#264) -- Fixed undefined variable in `RequestListener` (#263) - -## 3.2.0 (2019-10-04) - -- Add forward compatibility with Symfony 5 (#235, thanks to @garak) -- Fix Hub initialization for `ErrorListener` (#243, thanks to @teohhanhui) -- Fix compatibility with sentry/sentry 2.2+ (#244) -- Add support for `class_serializers` option (#245) -- Add support for `max_request_body_size` option (#249) -- Add option to disable the error listener completely (#247, thanks to @HypeMC) -- Add options to register the Monolog Handler (#247, thanks to @HypeMC) - -## 3.1.0 (2019-07-02) + ```php + // 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) + ) + ); + + // 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 + ); + ``` -- Add support for Symfony 2.8 (#233, thanks to @nocive) -- Fix handling of ESI requests (#213, thanks to @franmomu) +- New default cURL HTTP client [(#1589)](https://github.com/getsentry/sentry-php/pull/1589) -## 3.0.0 (2019-05-10) + The SDK now ships with its own HTTP client based on cURL. A few new options were added. -- Add the `sentry:test` command, to test if the Sentry SDK is functioning properly. + ```yaml + // config/packages/sentry.yaml -## 3.0.0-beta2 (2019-03-22) + sentry: + options: + - 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 + ``` -- Disable Sentry's ErrorHandler, and report all errors using Symfony's events (#204) + To use a different client, you may use the `http_client` option. + 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. -## 3.0.0-beta1 (2019-03-06) +### Misc -The 3.0 major release has multiple breaking changes. The most notable one is the upgrade to the 2.0 base SDK client. -Refer to the [UPGRADE-3.0.md](https://github.com/getsentry/sentry-symfony/blob/master/UPGRADE-3.0.md) document for a -detailed explanation. +- The abandoned package `php-http/message-factory` was removed. diff --git a/Makefile b/Makefile deleted file mode 100644 index 4f116379..00000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -.PHONY: pre-commit-check - -cs: - vendor/bin/php-cs-fixer fix --verbose - -cs-dry-run: - vendor/bin/php-cs-fixer fix --verbose --dry-run - -phpstan: - vendor/bin/phpstan analyze - -psalm: - vendor/bin/psalm - -test: - vendor/bin/phpunit - -pre-commit-check: cs phpstan psalm test - -setup-git: - git config branch.autosetuprebase always diff --git a/README.md b/README.md index 780d82e0..87b0db9e 100644 --- a/README.md +++ b/README.md @@ -13,157 +13,41 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he [![Total Downloads](https://poser.pugx.org/sentry/sentry-symfony/downloads)](https://packagist.org/packages/sentry/sentry-symfony) [![Monthly Downloads](https://poser.pugx.org/sentry/sentry-symfony/d/monthly)](https://packagist.org/packages/sentry/sentry-symfony) -![CI](https://github.com/getsentry/sentry-symfony/workflows/CI/badge.svg) [![Coverage Status][Master Code Coverage Image]][Master Code Coverage] +[![CI](https://github.com/getsentry/sentry-symfony/actions/workflows/tests.yaml/badge.svg)](https://github.com/getsentry/sentry-symfony/actions/workflows/tests.yaml) +[![Coverage Status][Master Code Coverage Image]][Master Code Coverage] [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/cWnMQeA) This is the official Symfony SDK for [Sentry](https://getsentry.com/). ## Getting Started -Using this `sentry-symfony` SDK provides you with the following benefits: - - * Quickly integrate and configure Sentry for your Symfony app - * Out of the box, each event will contain the following data by default - - The currently authenticated user - - The Symfony environment - ### 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/). +Install the SDK using [Composer](https://getcomposer.org/). ```bash composer require sentry/sentry-symfony ``` -If you're using the [Symfony Flex](https://symfony.com/doc/current/setup/flex.html) Composer plugin, you might encounter a message similar to this: - -``` -The recipe for this package comes from the "contrib" repository, which is open to community contributions. -Review the recipe at https://github.com/symfony/recipes-contrib/tree/master/sentry/sentry-symfony/3.0 - -Do you want to execute this recipe? -``` - -Just type `y`, press return, and the procedure will continue. - -**Caution:** Due to a bug in the [`SensioFrameworkExtra`](https://github.com/sensiolabs/SensioFrameworkExtraBundle) bundle, affecting version 6.0 and below, you might run into a missing `Nyholm\Psr7\Factory\Psr17Factory::class` error while executing the commands mentioned above. -If you are not using the PSR-7 bridge, you can work around this issue by changing the configuration of the bundle as follows: - -```yaml -sensio_framework_extra: - psr_message: - enabled: false -``` - -For more details about the issue see https://github.com/sensiolabs/SensioFrameworkExtraBundle/pull/710. - -### Enable the Bundle - -If you installed the package using the Flex recipe, the bundle will be automatically enabled. Otherwise, enable it by adding it to the list -of registered bundles in the `Kernel.php` file of your project: - -```php -class AppKernel extends \Symfony\Component\HttpKernel\Kernel -{ - public function registerBundles(): array - { - return [ - // ... - new \Sentry\SentryBundle\SentryBundle(), - ]; - } - - // ... -} -``` - -The bundle will be enabled in all environments by default. -To enable event reporting, you'll need to add a DSN (see the next step). - ### Configure -Add the [Sentry DSN](https://docs.sentry.io/quickstart/#configure-the-dsn) of your project. -If you're using Symfony 3.4, add the DSN to your `app/config/config_prod.yml` file. -For Symfony 4 or newer, add the DSN to your `config/packages/sentry.yaml` file. - -Keep in mind that by leaving the `dsn` value empty (or undeclared), you will disable Sentry's event reporting. - -```yaml -sentry: - dsn: "https://public:secret@sentry.example.com/1" - messenger: - enabled: true # flushes Sentry messages at the end of each message handling - capture_soft_fails: true # captures exceptions marked for retry too - options: - environment: '%kernel.environment%' - release: '%env(VERSION)%' #your app version -``` - -The parameter `options` allows to fine-tune exceptions. To discover more options, please refer to -[the Unified APIs](https://docs.sentry.io/development/sdk-dev/unified-api/#options) options and -the [PHP specific](https://docs.sentry.io/platforms/php/#php-specific-options) ones. - -#### Optional: use custom HTTP factory/transport - -Since the SDK 2.0 uses HTTPlug to remain transport-agnostic, you need to install two packages that provide -[`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation) -and [`psr/http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation). - -This bundle depends on `sentry/sdk`, which is a metapackage that already solves this need, requiring our suggested HTTP -packages: the Symfony HTTP client (`symfony/http-client`) and Guzzle's message factories (`http-interop/http-factory-guzzle`). - -If you want to use a different HTTP client or message factory, you can override the `sentry/sdk` package by adding the following to your `composer.json` after the `require` section: +Add the [Sentry DSN](https://docs.sentry.io/quickstart/#configure-the-dsn) to your `.env` file. -```json - "replace": { - "sentry/sdk": "*" - } ``` - -For example when you want to use Guzzle's components: - -```bash -composer require php-http/guzzle6-adapter guzzlehttp/psr7 -``` - -If you don't have a compatible HTTP client and/or message factory implementation installed `php-http/discovery` will try to install it for you using a Composer plugin. - -## Maintained versions - - * 4.x is actively maintained and developed on the master branch, and uses Sentry SDK 3.0; - * 3.x is supported only for fixes and uses Sentry SDK 2.0; - * 2.x is no longer maintained; from this version onwards it requires Symfony 3+ and PHP 7.1+; - * 1.x is no longer maintained; you can use it for Symfony < 2.8 and PHP 5.6/7.0; - * 0.8.x is no longer maintained. - -### Upgrading to 4.0 - -The 4.0 version of the bundle uses the newest version (3.x) of the underlying Sentry SDK. If you need to migrate from previous versions, please check the `UPGRADE-4.0.md` document. - -#### Custom serializers - -The option class_serializers can be used to send customized objects serialization. -```yml -sentry: - options: - class_serializers: - YourValueObject: 'ValueObjectSerializer' +###> sentry/sentry-symfony ### +SENTRY_DSN="https://public@sentry.example.com/1" +###< sentry/sentry-symfony ### ``` -Several serializers can be added and the serializable check is done by using the **instanceof** type operator. -The serializer must implement the `__invoke` method, which needs to return an **array**, containing the information that should be send to Sentry. The class name is always sent by default. +### Usage -Serializer example: ```php -final class ValueObjectSerializer -{ - public function __invoke(YourValueObject $vo): array - { - return [ - 'value' => $vo->value() - ]; - } +use function Sentry\captureException; + +try { + $this->functionThatMayFail(); +} catch (\Throwable $exception) { + captureException($exception); } ``` @@ -171,6 +55,12 @@ final class ValueObjectSerializer Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). +### Thanks to all the people who contributed so far! + + + + + ## Getting help/support If you need help setting up or configuring the Symfony 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! @@ -190,3 +80,4 @@ Licensed under the MIT license, see [`LICENSE`](LICENSE) [Packagist link]: https://packagist.org/packages/sentry/sentry-symfony [Master Code Coverage]: https://codecov.io/gh/getsentry/sentry-symfony/branch/master [Master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-symfony/master?logo=codecov + diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md new file mode 100644 index 00000000..5737c2cc --- /dev/null +++ b/UPGRADE-5.0.md @@ -0,0 +1,54 @@ +# Upgrade 4.x to 5.0 + +This version adds support for the underlying [Sentry PHP SDK v4.0](https://github.com/getsentry/sentry-php). +Please refer to the PHP SDK [sentry-php/UPGRADE-4.0.md](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-4.0.md) guide for a complete list of breaking changes. + +- 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`. + +- You need to have `ext-curl` installed to use the SDK. + +- The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead. + Previously, both `Symfony\Component\ErrorHandler\Error\FatalError` and `Symfony\Component\Debug\Exception\FatalErrorException` were ignored by default. + To continue ignoring these exceptions, make the following changes to your `config/packages/sentry.yaml` file: + + ```yaml + // config/packages/sentry.yaml + + sentry: + options: + ignore_exceptions: + - 'Symfony\Component\ErrorHandler\Error\FatalError' + - 'Symfony\Component\Debug\Exception\FatalErrorException' + ``` + + 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. + +- Removed support for `guzzlehttp/psr7: ^1.8.4`. + +- The `RequestFetcher` now relies on `guzzlehttp/psr7: ^2.1.1`. + +- Continue traces from the W3C `traceparent` request header. +- Inject the W3C `traceparent` header on outgoing HTTP client calls. +- Added `Sentry\SentryBundle\Twig\SentryExtension::getW3CTraceMeta()`. + +- The new default value for the `sentry.options.trace_propagation_targets` option is now `null`. To not attach any headers to outgoing requests, set this option to `[]`. + +- Added the `sentry.options.enable_tracing` option. +- Added the `sentry.options.attach_metric_code_locations` option. +- Added the `sentry.options.spotlight` option. +- Added the `sentry.options.spotlight_url` option. +- Added the `sentry.options.transport` option. +- Added the `sentry.options.http_client` option. +- Added the `sentry.options.http_proxy_authentication` option. +- Added the `sentry.options.http_ssl_verify_peer` option. +- Added the `sentry.options.http_compression` option. + +- Removed the `sentry.transport_factory` option. Use `sentry.options.transport` to use a custom transport. +- Removed the `sentry.options.send_attempts` option. You may use a custom transport if you rely on this behaviour. +- Removed the `sentry.options.enable_compression` option. Use `sentry.options.http_compression` instead. + +- Removed `Sentry\SentryBundle\Transport\TransportFactory`. +- Removed `Sentry\State\HubInterface\Sentry\State\HubInterface`. diff --git a/composer.json b/composer.json index 80e74ddc..a68c320a 100644 --- a/composer.json +++ b/composer.json @@ -7,28 +7,15 @@ "license": "MIT", "authors": [ { - "name": "David Cramer", - "email": "dcramer@gmail.com" - }, - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" + "name": "Sentry", + "email": "accounts@sentry.io" } ], - "config": { - "sort-packages": true, - "allow-plugins": { - "composer/package-versions-deprecated": true, - "phpstan/extension-installer": true, - "php-http/discovery": false - } - }, "require": { "php": "^7.2||^8.0", - "guzzlehttp/psr7": "^1.7 || ^2.0", - "jean85/pretty-package-versions": "^1.5 || ^2.0", - "sentry/sdk": "^3.6", - "sentry/sentry": "^3.22.1", + "guzzlehttp/psr7": "^2.1.1", + "jean85/pretty-package-versions": "^1.5||^2.0", + "sentry/sentry": "^4.14.1", "symfony/cache-contracts": "^1.1||^2.4||^3.0", "symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0", @@ -36,9 +23,7 @@ "symfony/event-dispatcher": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/http-kernel": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/polyfill-php80": "^1.22", - "symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0", - "symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0", - "symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0" + "symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0" }, "require-dev": { "doctrine/dbal": "^2.13||^3.3||^4.0", @@ -46,10 +31,10 @@ "friendsofphp/php-cs-fixer": "^2.19||^3.40", "masterminds/html5": "^2.8", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.0", - "phpunit/phpunit": "^8.5.14||^9.3.9", + "phpstan/phpstan": "1.12.5", + "phpstan/phpstan-phpunit": "1.4.0", + "phpstan/phpstan-symfony": "1.4.10", + "phpunit/phpunit": "^8.5.40||^9.6.21", "symfony/browser-kit": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/cache": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/dom-crawler": "^4.4.20||^5.0.11||^6.0||^7.0", @@ -59,6 +44,8 @@ "symfony/monolog-bundle": "^3.4", "symfony/phpunit-bridge": "^5.2.6||^6.0||^7.0", "symfony/process": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/twig-bundle": "^4.4.20||^5.0.11||^6.0||^7.0", "symfony/yaml": "^4.4.20||^5.0.11||^6.0||^7.0", "vimeo/psalm": "^4.3||^5.16.0" @@ -83,24 +70,23 @@ } }, "scripts": { - "tests": [ - "vendor/bin/phpunit --verbose" - ], - "phpcs": [ - "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run" + "check": [ + "@cs-check", + "@phpstan", + "@psalm", + "@tests" ], - "phpstan": [ - "vendor/bin/phpstan analyse" - ], - "psalm": [ - "vendor/bin/psalm" - ] + "tests": "vendor/bin/phpunit --verbose", + "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", + "psalm": "vendor/bin/psalm" }, - "extra": { - "branch-alias": { - "releases/3.2.x": "3.2.x-dev", - "releases/2.x": "2.x-dev", - "releases/1.x": "1.x-dev" + "config": { + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true, + "phpstan/extension-installer": true } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d2ecc82a..4313a41a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,6 +15,16 @@ parameters: count: 1 path: src/DependencyInjection/SentryExtension.php + - + message: "#^Cannot access offset 'before_send_check_in' on mixed\\.$#" + count: 1 + path: src/DependencyInjection/SentryExtension.php + + - + message: "#^Cannot access offset 'before_send_metrics' on mixed\\.$#" + count: 1 + path: src/DependencyInjection/SentryExtension.php + - message: "#^Cannot access offset 'before_send…' on mixed\\.$#" count: 1 @@ -45,6 +55,11 @@ parameters: count: 1 path: src/DependencyInjection/SentryExtension.php + - + message: "#^Cannot access offset 'http_client' on mixed\\.$#" + count: 1 + path: src/DependencyInjection/SentryExtension.php + - message: "#^Cannot access offset 'in_app_exclude' on mixed\\.$#" count: 2 @@ -55,13 +70,18 @@ parameters: count: 2 path: src/DependencyInjection/SentryExtension.php + - + message: "#^Cannot access offset 'logger' on mixed\\.$#" + count: 1 + path: src/DependencyInjection/SentryExtension.php + - message: "#^Cannot access offset 'traces_sampler' on mixed\\.$#" count: 1 path: src/DependencyInjection/SentryExtension.php - - message: "#^Class Symfony\\\\Component\\\\Debug\\\\Exception\\\\FatalErrorException not found\\.$#" + message: "#^Cannot access offset 'transport' on mixed\\.$#" count: 1 path: src/DependencyInjection/SentryExtension.php @@ -77,7 +97,7 @@ parameters: - message: "#^Parameter \\#1 \\$id of class Symfony\\\\Component\\\\DependencyInjection\\\\Reference constructor expects string, mixed given\\.$#" - count: 6 + count: 10 path: src/DependencyInjection/SentryExtension.php - @@ -130,11 +150,6 @@ parameters: count: 4 path: src/DependencyInjection/SentryExtension.php - - - message: "#^Parameter \\#2 \\$registerErrorListener of method Sentry\\\\SentryBundle\\\\DependencyInjection\\\\SentryExtension\\:\\:configureErrorListenerIntegration\\(\\) expects bool, mixed given\\.$#" - count: 1 - path: src/DependencyInjection/SentryExtension.php - - message: "#^Parameter \\#2 \\$useDefaultIntegrations of method Sentry\\\\SentryBundle\\\\DependencyInjection\\\\SentryExtension\\:\\:configureRequestIntegration\\(\\) expects bool, mixed given\\.$#" count: 1 @@ -151,14 +166,24 @@ parameters: path: src/EventListener/AbstractTracingRequestListener.php - - message: "#^Class Sentry\\\\SentryBundle\\\\EventListener\\\\ConsoleCommandListener extends @final class Sentry\\\\SentryBundle\\\\EventListener\\\\ConsoleListener\\.$#" + message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#" count: 1 - path: src/EventListener/ConsoleCommandListener.php + path: src/EventListener/LoginListener.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#" + message: "#^Instanceof between Throwable and Symfony\\\\Component\\\\Messenger\\\\Exception\\\\DelayedMessageHandlingException will always evaluate to false\\.$#" count: 1 - path: src/EventListener/LoginListener.php + path: src/EventListener/MessengerListener.php + + - + message: "#^Instanceof between Throwable and Symfony\\\\Component\\\\Messenger\\\\Exception\\\\HandlerFailedException will always evaluate to false\\.$#" + count: 1 + path: src/EventListener/MessengerListener.php + + - + message: "#^Result of && is always false\\.$#" + count: 2 + path: src/EventListener/MessengerListener.php - message: "#^Call to an undefined method Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\KernelEvent\\:\\:isMasterRequest\\(\\)\\.$#" @@ -255,11 +280,6 @@ parameters: count: 1 path: tests/DependencyInjection/Fixtures/php/release_option_fallback_to_env_var.php - - - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: tests/DependencyInjection/SentryExtensionTest.php - - message: "#^Cannot access offset 'dsn' on mixed\\.$#" count: 1 @@ -275,11 +295,6 @@ parameters: count: 1 path: tests/DependencyInjection/SentryExtensionTest.php - - - message: "#^Class Symfony\\\\Component\\\\Debug\\\\Exception\\\\FatalErrorException not found\\.$#" - count: 1 - path: tests/DependencyInjection/SentryExtensionTest.php - - message: "#^Class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Client not found\\.$#" count: 1 @@ -300,11 +315,6 @@ parameters: count: 1 path: tests/EventListener/LoginListenerTest.php - - - message: "#^Parameter \\#1 \\$user of method Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\AbstractToken\\:\\:setUser\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, string\\|Stringable\\|Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface given\\.$#" - count: 1 - path: tests/EventListener/LoginListenerTest.php - - message: "#^Parameter \\#2 \\$firewallName of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects string, null given\\.$#" count: 1 @@ -320,11 +330,6 @@ parameters: count: 1 path: tests/EventListener/LoginListenerTest.php - - - message: "#^Parameter \\#5 \\$originatedFromUri of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\SwitchUserToken constructor expects string\\|null, Sentry\\\\SentryBundle\\\\Tests\\\\EventListener\\\\AuthenticatedTokenStub given\\.$#" - count: 1 - path: tests/EventListener/LoginListenerTest.php - - message: "#^Access to undefined constant Symfony\\\\Component\\\\HttpKernel\\\\HttpKernelInterface\\:\\:MASTER_REQUEST\\.$#" count: 6 diff --git a/phpstan.neon b/phpstan.neon index d5a9c0a4..3608367b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,6 +2,7 @@ includes: - phpstan-baseline.neon parameters: + reportUnmatchedIgnoredErrors: true level: 9 paths: - src diff --git a/phpunit.xml b/phpunit.xml index 72fe2378..e4dadb92 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,17 +20,11 @@ src + + src/aliases.php + - - - src - - src/aliases.php - - - - diff --git a/psalm.xml b/psalm.xml index bb410780..71321eb0 100644 --- a/psalm.xml +++ b/psalm.xml @@ -3,6 +3,8 @@ errorLevel="4" memoizeMethodCallResults="true" errorBaseline="psalm-baseline.xml" + findUnusedBaselineEntry="false" + findUnusedCode="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" diff --git a/src/Command/SentryTestCommand.php b/src/Command/SentryTestCommand.php index 94ed9454..5c28a46e 100644 --- a/src/Command/SentryTestCommand.php +++ b/src/Command/SentryTestCommand.php @@ -25,7 +25,7 @@ public function __construct(?HubInterface $hub = null) parent::__construct(); if (null === $hub) { - @trigger_error(sprintf('Not passing an instance of the "%s" interface as argument of the constructor is deprecated since version 4.12 and will not work since version 5.0.', HubInterface::class), \E_USER_DEPRECATED); + @trigger_error(\sprintf('Not passing an instance of the "%s" interface as argument of the constructor is deprecated since version 4.12 and will not work since version 5.0.', HubInterface::class), \E_USER_DEPRECATED); } $this->hub = $hub ?? SentrySdk::getCurrentHub(); diff --git a/src/DependencyInjection/Compiler/AddLoginListenerTagPass.php b/src/DependencyInjection/Compiler/AddLoginListenerTagPass.php index 24d3110c..5f935cb6 100644 --- a/src/DependencyInjection/Compiler/AddLoginListenerTagPass.php +++ b/src/DependencyInjection/Compiler/AddLoginListenerTagPass.php @@ -17,9 +17,17 @@ final class AddLoginListenerTagPass implements CompilerPassInterface */ public function process(ContainerBuilder $container): void { + if (!$container->hasDefinition(LoginListener::class)) { + return; + } $listenerDefinition = $container->getDefinition(LoginListener::class); - if (!class_exists(LoginSuccessEvent::class)) { + if (class_exists(LoginSuccessEvent::class)) { + $listenerDefinition->addTag('kernel.event_listener', [ + 'event' => LoginSuccessEvent::class, + 'method' => 'handleLoginSuccessEvent', + ]); + } elseif (class_exists(AuthenticationSuccessEvent::class)) { $listenerDefinition->addTag('kernel.event_listener', [ 'event' => AuthenticationSuccessEvent::class, 'method' => 'handleAuthenticationSuccessEvent', diff --git a/src/DependencyInjection/Compiler/DbalTracingPass.php b/src/DependencyInjection/Compiler/DbalTracingPass.php index e867a781..e4cbcdd7 100644 --- a/src/DependencyInjection/Compiler/DbalTracingPass.php +++ b/src/DependencyInjection/Compiler/DbalTracingPass.php @@ -45,8 +45,8 @@ public function process(ContainerBuilder $container): void } foreach ($connectionsToTrace as $connectionName) { - if (!\in_array(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName), $connections, true)) { - throw new \InvalidArgumentException(sprintf('The Doctrine connection "%s" does not exists and cannot be instrumented.', $connectionName)); + if (!\in_array(\sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName), $connections, true)) { + throw new \InvalidArgumentException(\sprintf('The Doctrine connection "%s" does not exists and cannot be instrumented.', $connectionName)); } if (class_exists(Result::class)) { @@ -65,7 +65,7 @@ private function configureConnectionForDoctrineDBALVersion3(ContainerBuilder $co private function configureConnectionForDoctrineDBALVersion2(ContainerBuilder $container, string $connectionName): void { - $connectionDefinition = $container->getDefinition(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName)); + $connectionDefinition = $container->getDefinition(\sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName)); $connectionDefinition->setConfigurator([new Reference(ConnectionConfigurator::class), 'configure']); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 717d8ada..ca095895 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -7,7 +7,6 @@ use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Sentry\Options; use Sentry\SentryBundle\ErrorTypesParser; -use Sentry\Transport\TransportFactoryInterface; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -43,7 +42,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() ->scalarNode('dsn') - ->info('If this value is not provided, the SDK will try to read it from the SENTRY_DSN environment variable. If that variable also does not exist, the SDK will just not send any events.') + ->info('If this value is not provided, the SDK will try to read it from the SENTRY_DSN environment variable. If that variable also does not exist, the SDK will not send any events.') ->end() ->booleanNode('register_error_listener')->defaultTrue()->end() ->booleanNode('register_error_handler')->defaultTrue()->end() @@ -51,10 +50,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('The service ID of the PSR-3 logger used to log messages coming from the SDK client. Be aware that setting the same logger of the application may create a circular loop when an event fails to be sent.') ->defaultNull() ->end() - ->scalarNode('transport_factory') - ->info('The service ID of the transport factory used by the default SDK client.') - ->defaultValue(TransportFactoryInterface::class) - ->end() ->arrayNode('options') ->addDefaultsIfNotSet() ->fixXmlConfig('integration') @@ -69,7 +64,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarPrototype()->end() ->end() ->booleanNode('default_integrations')->end() - ->integerNode('send_attempts')->min(0)->end() ->arrayNode('prefixes') ->defaultValue(array_merge(['%kernel.project_dir%'], array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: '')))) ->scalarPrototype()->end() @@ -79,33 +73,46 @@ public function getConfigTreeBuilder(): TreeBuilder ->max(1.0) ->info('The sampling factor to apply to events. A value of 0 will deny sending any event, and a value of 1 will send all events.') ->end() + ->booleanNode('enable_tracing')->end() ->floatNode('traces_sample_rate') ->min(0.0) ->max(1.0) ->info('The sampling factor to apply to transactions. A value of 0 will deny sending any transaction, and a value of 1 will send all transactions.') ->end() + ->scalarNode('traces_sampler')->end() ->floatNode('profiles_sample_rate') ->min(0.0) ->max(1.0) ->info('The sampling factor to apply to profiles. A value of 0 will deny sending any profiles, and a value of 1 will send all profiles. Profiles are sampled in relation to traces_sample_rate') ->end() - ->scalarNode('traces_sampler')->end() - ->variableNode('trace_propagation_targets')->end() ->booleanNode('attach_stacktrace')->end() + ->booleanNode('attach_metric_code_locations')->end() ->integerNode('context_lines')->min(0)->end() - ->booleanNode('enable_compression')->end() ->scalarNode('environment') ->cannotBeEmpty() ->defaultValue('%kernel.environment%') ->end() ->scalarNode('logger')->end() + ->booleanNode('spotlight')->end() + ->scalarNode('spotlight_url')->end() ->scalarNode('release') ->cannotBeEmpty() ->defaultValue('%env(default::SENTRY_RELEASE)%') ->end() ->scalarNode('server_name')->end() + ->arrayNode('ignore_exceptions') + ->scalarPrototype()->end() + ->beforeNormalization()->castToArray()->end() + ->end() + ->arrayNode('ignore_transactions') + ->scalarPrototype()->end() + ->beforeNormalization()->castToArray()->end() + ->end() ->scalarNode('before_send')->end() ->scalarNode('before_send_transaction')->end() + ->scalarNode('before_send_check_in')->end() + ->scalarNode('before_send_metrics')->end() + ->variableNode('trace_propagation_targets')->end() ->arrayNode('tags') ->useAttributeAsKey('name') ->normalizeKeys(false) @@ -132,7 +139,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->booleanNode('send_default_pii')->end() ->integerNode('max_value_length')->min(0)->end() + ->scalarNode('transport')->end() + ->scalarNode('http_client')->end() ->scalarNode('http_proxy')->end() + ->scalarNode('http_proxy_authentication')->end() ->floatNode('http_connect_timeout') ->min(0) ->info('The maximum number of seconds to wait while trying to connect to a server. It works only when using the default transport.') @@ -141,6 +151,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->min(0) ->info('The maximum execution time for the request+response as a whole. It works only when using the default transport.') ->end() + ->booleanNode('http_ssl_verify_peer')->end() + ->booleanNode('http_compression')->end() ->booleanNode('capture_silenced_errors')->end() ->enumNode('max_request_body_size') ->values([ @@ -155,14 +167,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->normalizeKeys(false) ->scalarPrototype()->end() ->end() - ->arrayNode('ignore_exceptions') - ->scalarPrototype()->end() - ->beforeNormalization()->castToArray()->end() - ->end() - ->arrayNode('ignore_transactions') - ->scalarPrototype()->end() - ->beforeNormalization()->castToArray()->end() - ->end() ->end() ->end() ->end(); diff --git a/src/DependencyInjection/SentryExtension.php b/src/DependencyInjection/SentryExtension.php index a0d502a5..a05a62e8 100644 --- a/src/DependencyInjection/SentryExtension.php +++ b/src/DependencyInjection/SentryExtension.php @@ -9,13 +9,13 @@ use Psr\Log\NullLogger; use Sentry\Client; use Sentry\ClientBuilder; -use Sentry\Integration\IgnoreErrorsIntegration; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\RequestFetcherInterface; use Sentry\Integration\RequestIntegration; use Sentry\Options; use Sentry\SentryBundle\EventListener\ConsoleListener; use Sentry\SentryBundle\EventListener\ErrorListener; +use Sentry\SentryBundle\EventListener\LoginListener; use Sentry\SentryBundle\EventListener\MessengerListener; use Sentry\SentryBundle\EventListener\TracingConsoleListener; use Sentry\SentryBundle\EventListener\TracingRequestListener; @@ -26,19 +26,16 @@ use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware; use Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension; use Sentry\Serializer\RepresentationSerializer; -use Sentry\Serializer\Serializer; -use Sentry\Transport\TransportFactoryInterface; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Config\FileLocator; -use Symfony\Component\Debug\Exception\FatalErrorException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\ErrorHandler\Error\FatalError; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; final class SentryExtension extends ConfigurableExtension { @@ -80,6 +77,10 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container $this->registerTwigTracingConfiguration($container, $mergedConfig['tracing']); $this->registerCacheTracingConfiguration($container, $mergedConfig['tracing']); $this->registerHttpClientTracingConfiguration($container, $mergedConfig['tracing']); + + if (!interface_exists(TokenStorageInterface::class)) { + $container->removeDefinition(LoginListener::class); + } } /** @@ -99,6 +100,10 @@ private function registerConfiguration(ContainerBuilder $container, array $confi }); } + if (isset($options['logger'])) { + $options['logger'] = new Reference($options['logger']); + } + if (isset($options['traces_sampler'])) { $options['traces_sampler'] = new Reference($options['traces_sampler']); } @@ -111,6 +116,14 @@ private function registerConfiguration(ContainerBuilder $container, array $confi $options['before_send_transaction'] = new Reference($options['before_send_transaction']); } + if (isset($options['before_send_check_in'])) { + $options['before_send_check_in'] = new Reference($options['before_send_check_in']); + } + + if (isset($options['before_send_metrics'])) { + $options['before_send_metrics'] = new Reference($options['before_send_metrics']); + } + if (isset($options['before_breadcrumb'])) { $options['before_breadcrumb'] = new Reference($options['before_breadcrumb']); } @@ -121,6 +134,14 @@ private function registerConfiguration(ContainerBuilder $container, array $confi }, $options['class_serializers']); } + if (isset($options['transport'])) { + $options['transport'] = new Reference($options['transport']); + } + + if (isset($options['http_client'])) { + $options['http_client'] = new Reference($options['http_client']); + } + $container->getDefinition(IntegrationConfigurator::class) ->setArgument(0, $this->configureIntegrationsOption($options['integrations'], $config)) ->setArgument(1, $config['register_error_handler']); @@ -131,10 +152,6 @@ private function registerConfiguration(ContainerBuilder $container, array $confi ->setPublic(false) ->setArgument(0, $options); - $serializer = (new Definition(Serializer::class)) - ->setPublic(false) - ->setArgument(0, new Reference('sentry.client.options')); - $representationSerializerDefinition = (new Definition(RepresentationSerializer::class)) ->setPublic(false) ->setArgument(0, new Reference('sentry.client.options')); @@ -143,15 +160,10 @@ private function registerConfiguration(ContainerBuilder $container, array $confi ? new Reference(NullLogger::class, ContainerBuilder::IGNORE_ON_INVALID_REFERENCE) : new Reference($config['logger']); - $factoryBuilderDefinition = $container->getDefinition(TransportFactoryInterface::class); - $factoryBuilderDefinition->setArgument('$logger', $loggerReference); - $clientBuilderDefinition = (new Definition(ClientBuilder::class)) ->setArgument(0, new Reference('sentry.client.options')) ->addMethodCall('setSdkIdentifier', [SentryBundle::SDK_IDENTIFIER]) ->addMethodCall('setSdkVersion', [SentryBundle::SDK_VERSION]) - ->addMethodCall('setTransportFactory', [new Reference($config['transport_factory'])]) - ->addMethodCall('setSerializer', [$serializer]) ->addMethodCall('setRepresentationSerializer', [$representationSerializerDefinition]) ->addMethodCall('setLogger', [$loggerReference]); @@ -285,36 +297,11 @@ private function configureIntegrationsOption(array $integrations, array $config) return new Reference($value); }, $integrations); - $integrations = $this->configureErrorListenerIntegration($integrations, $config['register_error_listener']); $integrations = $this->configureRequestIntegration($integrations, $config['options']['default_integrations'] ?? true); return $integrations; } - /** - * @param array $integrations - * - * @return array - */ - private function configureErrorListenerIntegration(array $integrations, bool $registerErrorListener): array - { - if ($registerErrorListener && !$this->isIntegrationEnabled(IgnoreErrorsIntegration::class, $integrations)) { - // Prepend this integration to the beginning of the array so that - // we can save some performance by skipping the rest of the integrations - // if the error must be ignored - array_unshift($integrations, new Definition(IgnoreErrorsIntegration::class, [ - [ - 'ignore_exceptions' => [ - FatalError::class, - FatalErrorException::class, - ], - ], - ])); - } - - return $integrations; - } - /** * @param array $integrations * diff --git a/src/ErrorTypesParser.php b/src/ErrorTypesParser.php index aa31d486..b717c641 100644 --- a/src/ErrorTypesParser.php +++ b/src/ErrorTypesParser.php @@ -54,14 +54,28 @@ private static function convertErrorConstants(string $value): string { $output = preg_replace_callback('/(E_[A-Z_]+)/', static function (array $matches) { if (\defined($matches[1])) { - return \constant($matches[1]); + $constant = \constant($matches[1]); + + if (\is_string($constant)) { + return $constant; + } elseif (\is_int($constant)) { + return (string) $constant; + } elseif (\is_array($constant)) { + return implode(' | ', array_map(static function ($value) { + return \is_string($value) ? $value : (string) $value; + }, $constant)); + } elseif (\is_object($constant)) { + return \get_class($constant); + } else { // Non-scalar values + return ''; + } } return $matches[0]; }, $value); if (null === $output) { - throw new \InvalidArgumentException(sprintf('The "%s" value could not be parsed.', $value)); + throw new \InvalidArgumentException(\sprintf('The "%s" value could not be parsed.', $value)); } return $output; diff --git a/src/EventListener/AbstractTracingRequestListener.php b/src/EventListener/AbstractTracingRequestListener.php index 022f2d1b..95c31543 100644 --- a/src/EventListener/AbstractTracingRequestListener.php +++ b/src/EventListener/AbstractTracingRequestListener.php @@ -67,7 +67,7 @@ protected function getRouteName(Request $request): string $route = $request->attributes->get('_controller'); if (\is_array($route) && \is_callable($route, true)) { - $route = sprintf('%s::%s', \is_object($route[0]) ? get_debug_type($route[0]) : $route[0], $route[1]); + $route = \sprintf('%s::%s', \is_object($route[0]) ? get_debug_type($route[0]) : $route[0], $route[1]); } } diff --git a/src/EventListener/ConsoleCommandListener.php b/src/EventListener/ConsoleCommandListener.php deleted file mode 100644 index 1f64ecb3..00000000 --- a/src/EventListener/ConsoleCommandListener.php +++ /dev/null @@ -1,20 +0,0 @@ -tokenStorage || !$this->isMainRequest($event)) { + if ( + null === $this->tokenStorage + || !$this->isMainRequest($event) + || $event->getRequest()->attributes->get('_stateless') + ) { return; } diff --git a/src/EventListener/TracingConsoleListener.php b/src/EventListener/TracingConsoleListener.php index 5efbaf26..57c187a6 100644 --- a/src/EventListener/TracingConsoleListener.php +++ b/src/EventListener/TracingConsoleListener.php @@ -7,6 +7,7 @@ use Sentry\State\HubInterface; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; +use Sentry\Tracing\SpanStatus; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; @@ -59,18 +60,20 @@ public function handleConsoleCommandEvent(ConsoleCommandEvent $event): void $currentSpan = $this->hub->getSpan(); if (null === $currentSpan) { - $transactionContext = new TransactionContext(); - $transactionContext->setOp('console.command'); - $transactionContext->setName($this->getSpanName($command)); - $transactionContext->setSource(TransactionSource::task()); - - $span = $this->hub->startTransaction($transactionContext); + $span = $this->hub->startTransaction( + TransactionContext::make() + ->setOp('console.command') + ->setOrigin('auto.console') + ->setName($this->getSpanName($command)) + ->setSource(TransactionSource::task()) + ); } else { - $spanContext = new SpanContext(); - $spanContext->setOp('console.command'); - $spanContext->setDescription($this->getSpanName($command)); - - $span = $currentSpan->startChild($spanContext); + $span = $currentSpan->startChild( + SpanContext::make() + ->setOp('console.command') + ->setOrigin('auto.console') + ->setDescription($this->getSpanName($command)) + ); } $this->hub->setSpan($span); @@ -91,6 +94,7 @@ public function handleConsoleTerminateEvent(ConsoleTerminateEvent $event): void $span = $this->hub->getSpan(); if (null !== $span) { + $span->setStatus(0 === $event->getExitCode() ? SpanStatus::ok() : SpanStatus::internalError()); $span->finish(); } } diff --git a/src/EventListener/TracingRequestListener.php b/src/EventListener/TracingRequestListener.php index 377269c2..f94f9dda 100644 --- a/src/EventListener/TracingRequestListener.php +++ b/src/EventListener/TracingRequestListener.php @@ -4,12 +4,16 @@ namespace Sentry\SentryBundle\EventListener; +use Sentry\Integration\RequestFetcherInterface; +use Sentry\SentryBundle\Integration\RequestFetcher; +use Sentry\State\HubInterface; use Sentry\Tracing\TransactionSource; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\TerminateEvent; use function Sentry\continueTrace; +use function Sentry\metrics; /** * This event listener acts on the master requests and starts a transaction @@ -19,6 +23,18 @@ */ final class TracingRequestListener extends AbstractTracingRequestListener { + /** + * @var RequestFetcherInterface|null + */ + private $requestFetcher; + + public function __construct(HubInterface $hub, ?RequestFetcherInterface $requestFetcher = null) + { + parent::__construct($hub); + + $this->requestFetcher = $requestFetcher; + } + /** * This method is called for each subrequest handled by the framework and * starts a new {@see Transaction}. @@ -34,21 +50,27 @@ public function handleKernelRequestEvent(RequestEvent $event): void /** @var Request $request */ $request = $event->getRequest(); + if ($this->requestFetcher instanceof RequestFetcher) { + $this->requestFetcher->setRequest($request); + } + /** @var float $requestStartTime */ $requestStartTime = $request->server->get('REQUEST_TIME_FLOAT', microtime(true)); $context = continueTrace( - $request->headers->get('sentry-trace', ''), + $request->headers->get('sentry-trace') ?? $request->headers->get('traceparent', ''), $request->headers->get('baggage', '') ); + $context->setOp('http.server'); + $context->setOrigin('auto.http.server'); $routeName = $request->attributes->get('_route'); if (null !== $routeName && \is_string($routeName)) { - $context->setName(sprintf('%s %s', $request->getMethod(), $routeName)); + $context->setName(\sprintf('%s %s', $request->getMethod(), $routeName)); $context->setSource(TransactionSource::route()); } else { - $context->setName(sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo())); + $context->setName(\sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo())); $context->setSource(TransactionSource::url()); } @@ -73,6 +95,11 @@ public function handleKernelTerminateEvent(TerminateEvent $event): void } $transaction->finish(); + metrics()->flush(); + + if ($this->requestFetcher instanceof RequestFetcher) { + $this->requestFetcher->setRequest(null); + } } /** @@ -86,7 +113,8 @@ private function getData(Request $request): array { $client = $this->hub->getClient(); $httpFlavor = $this->getHttpFlavor($request); - $tags = [ + + $data = [ 'net.host.port' => (string) $request->getPort(), 'http.request.method' => $request->getMethod(), 'http.url' => $request->getUri(), @@ -94,20 +122,20 @@ private function getData(Request $request): array ]; if (null !== $httpFlavor) { - $tags['http.flavor'] = $httpFlavor; + $data['http.flavor'] = $httpFlavor; } if (false !== filter_var($request->getHost(), \FILTER_VALIDATE_IP)) { - $tags['net.host.ip'] = $request->getHost(); + $data['net.host.ip'] = $request->getHost(); } else { - $tags['net.host.name'] = $request->getHost(); + $data['net.host.name'] = $request->getHost(); } if (null !== $request->getClientIp() && null !== $client && $client->getOptions()->shouldSendDefaultPii()) { - $tags['net.peer.ip'] = $request->getClientIp(); + $data['net.peer.ip'] = $request->getClientIp(); } - return $tags; + return $data; } /** diff --git a/src/EventListener/TracingSubRequestListener.php b/src/EventListener/TracingSubRequestListener.php index 6791d548..71198bb1 100644 --- a/src/EventListener/TracingSubRequestListener.php +++ b/src/EventListener/TracingSubRequestListener.php @@ -34,16 +34,19 @@ public function handleKernelRequestEvent(RequestEvent $event): void return; } - $spanContext = new SpanContext(); - $spanContext->setOp('http.server'); - $spanContext->setDescription(sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo())); - $spanContext->setData([ - 'http.request.method' => $request->getMethod(), - 'http.url' => $request->getUri(), - 'route' => $this->getRouteName($request), - ]); - - $this->hub->setSpan($span->startChild($spanContext)); + $this->hub->setSpan( + $span->startChild( + SpanContext::make() + ->setOp('http.server') + ->setData([ + 'http.request.method' => $request->getMethod(), + 'http.url' => $request->getUri(), + 'route' => $this->getRouteName($request), + ]) + ->setOrigin('auto.http.server') + ->setDescription(\sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo())) + ) + ); } /** diff --git a/src/Integration/RequestFetcher.php b/src/Integration/RequestFetcher.php index f5911b2c..2f2d2cd2 100644 --- a/src/Integration/RequestFetcher.php +++ b/src/Integration/RequestFetcher.php @@ -4,11 +4,12 @@ namespace Sentry\SentryBundle\Integration; -use Http\Discovery\Psr17FactoryDiscovery; +use GuzzleHttp\Psr7\HttpFactory; use Psr\Http\Message\ServerRequestInterface; use Sentry\Integration\RequestFetcherInterface; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; /** @@ -23,6 +24,11 @@ final class RequestFetcher implements RequestFetcherInterface */ private $requestStack; + /** + * @var Request|null The current request + */ + private $currentRequest; + /** * @var HttpMessageFactoryInterface The factory to convert Symfony requests to PSR-7 requests */ @@ -38,10 +44,10 @@ public function __construct(RequestStack $requestStack, ?HttpMessageFactoryInter { $this->requestStack = $requestStack; $this->httpMessageFactory = $httpMessageFactory ?? new PsrHttpFactory( - Psr17FactoryDiscovery::findServerRequestFactory(), - Psr17FactoryDiscovery::findStreamFactory(), - Psr17FactoryDiscovery::findUploadedFileFactory(), - Psr17FactoryDiscovery::findResponseFactory() + new HttpFactory(), + new HttpFactory(), + new HttpFactory(), + new HttpFactory() ); } @@ -50,7 +56,7 @@ public function __construct(RequestStack $requestStack, ?HttpMessageFactoryInter */ public function fetchRequest(): ?ServerRequestInterface { - $request = $this->requestStack->getCurrentRequest(); + $request = $this->currentRequest ?? $this->requestStack->getCurrentRequest(); if (null === $request) { return null; @@ -62,4 +68,9 @@ public function fetchRequest(): ?ServerRequestInterface return null; } } + + public function setRequest(?Request $request): void + { + $this->currentRequest = $request; + } } diff --git a/src/Resources/config/schema/sentry-1.0.xsd b/src/Resources/config/schema/sentry-1.0.xsd index 78ed5033..3bad5d57 100644 --- a/src/Resources/config/schema/sentry-1.0.xsd +++ b/src/Resources/config/schema/sentry-1.0.xsd @@ -35,28 +35,38 @@ - + + + + + + + + - + + + + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 74682cc6..ff0407e0 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -9,15 +9,6 @@ - - - - - - null - - - @@ -57,6 +48,7 @@ + @@ -91,7 +83,6 @@ - diff --git a/src/SentryBundle.php b/src/SentryBundle.php index 49264b62..49f7843d 100644 --- a/src/SentryBundle.php +++ b/src/SentryBundle.php @@ -16,7 +16,7 @@ final class SentryBundle extends Bundle { public const SDK_IDENTIFIER = 'sentry.php.symfony'; - public const SDK_VERSION = '4.14.0'; + public const SDK_VERSION = '5.3.0'; public function build(ContainerBuilder $container): void { diff --git a/src/Tracing/Cache/TraceableCacheAdapterForV2.php b/src/Tracing/Cache/TraceableCacheAdapterForV2.php index 7a5b57c9..e8ed9330 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterForV2.php +++ b/src/Tracing/Cache/TraceableCacheAdapterForV2.php @@ -40,11 +40,11 @@ public function __construct(HubInterface $hub, AdapterInterface $decoratedAdapte * * @return mixed */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) { if (!$this->decoratedAdapter instanceof CacheInterface) { - throw new \BadMethodCallException(sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + throw new \BadMethodCallException(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); } return $this->decoratedAdapter->get($key, $callback, $beta, $metadata); diff --git a/src/Tracing/Cache/TraceableCacheAdapterForV3.php b/src/Tracing/Cache/TraceableCacheAdapterForV3.php index 564acff3..ef276620 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterForV3.php +++ b/src/Tracing/Cache/TraceableCacheAdapterForV3.php @@ -38,11 +38,11 @@ public function __construct(HubInterface $hub, AdapterInterface $decoratedAdapte * * @param mixed[] $metadata */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) { if (!$this->decoratedAdapter instanceof CacheInterface) { - throw new \BadMethodCallException(sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + throw new \BadMethodCallException(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); } return $this->decoratedAdapter->get($key, $callback, $beta, $metadata); diff --git a/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php new file mode 100644 index 00000000..7b53c286 --- /dev/null +++ b/src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php @@ -0,0 +1,64 @@ + + */ + use TraceableCacheAdapterTrait; + + /** + * @param HubInterface $hub The current hub + * @param AdapterInterface $decoratedAdapter The decorated cache adapter + */ + public function __construct(HubInterface $hub, AdapterInterface $decoratedAdapter) + { + $this->hub = $hub; + $this->decoratedAdapter = $decoratedAdapter; + } + + /** + * {@inheritdoc} + * + * @param mixed[] $metadata + */ + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed + { + return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) { + if (!$this->decoratedAdapter instanceof CacheInterface) { + throw new \BadMethodCallException(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + } + + return $this->decoratedAdapter->get($key, $callback, $beta, $metadata); + }, $key); + } + + public function withSubNamespace(string $namespace): static + { + if (!$this->decoratedAdapter instanceof NamespacedPoolInterface) { + throw new \BadMethodCallException(\sprintf('The %s::withSubNamespace() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, NamespacedPoolInterface::class)); + } + + $clone = clone $this; + $clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace); + + return $clone; + } +} diff --git a/src/Tracing/Cache/TraceableCacheAdapterTrait.php b/src/Tracing/Cache/TraceableCacheAdapterTrait.php index 0bf2bc2f..b2af9a59 100644 --- a/src/Tracing/Cache/TraceableCacheAdapterTrait.php +++ b/src/Tracing/Cache/TraceableCacheAdapterTrait.php @@ -70,7 +70,7 @@ public function delete(string $key): bool { return $this->traceFunction('cache.delete_item', function () use ($key): bool { if (!$this->decoratedAdapter instanceof CacheInterface) { - throw new \BadMethodCallException(sprintf('The %s::delete() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + throw new \BadMethodCallException(\sprintf('The %s::delete() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); } return $this->decoratedAdapter->delete($key); @@ -168,13 +168,15 @@ public function reset(): void * * @phpstan-return TResult */ - private function traceFunction(string $spanOperation, \Closure $callback, string $spanDescription = null) + private function traceFunction(string $spanOperation, \Closure $callback, ?string $spanDescription = null) { $span = $this->hub->getSpan(); if (null !== $span) { - $spanContext = new SpanContext(); - $spanContext->setOp($spanOperation); + $spanContext = SpanContext::make() + ->setOp($spanOperation) + ->setOrigin('auto.cache'); + if (null !== $spanDescription) { $spanContext->setDescription(urldecode($spanDescription)); } @@ -190,4 +192,17 @@ private function traceFunction(string $spanOperation, \Closure $callback, string } } } + + /** + * @phpstan-param \Closure(CacheItem): CacheItem $callback + * @phpstan-param string $key + * + * @phpstan-return callable(): CacheItem + */ + private function setCallbackWrapper(callable $callback, string $key): callable + { + return function () use ($callback, $key): CacheItem { + return $callback($this->decoratedAdapter->getItem($key)); + }; + } } diff --git a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php index a89d911f..62477cc2 100644 --- a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php +++ b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php @@ -41,11 +41,11 @@ public function __construct(HubInterface $hub, TagAwareAdapterInterface $decorat * * @return mixed */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) { if (!$this->decoratedAdapter instanceof CacheInterface) { - throw new \BadMethodCallException(sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + throw new \BadMethodCallException(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); } return $this->decoratedAdapter->get($key, $callback, $beta, $metadata); diff --git a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3.php b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3.php index 733b4555..cc5cc7b4 100644 --- a/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3.php +++ b/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3.php @@ -39,11 +39,11 @@ public function __construct(HubInterface $hub, TagAwareAdapterInterface $decorat * * @param mixed[] $metadata */ - public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed + public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) { if (!$this->decoratedAdapter instanceof CacheInterface) { - throw new \BadMethodCallException(sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); + throw new \BadMethodCallException(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, CacheInterface::class)); } return $this->decoratedAdapter->get($key, $callback, $beta, $metadata); diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php index 2cb2513d..9a3a9035 100644 --- a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php @@ -60,13 +60,6 @@ final class TracingDriverConnectionForV2V3 implements TracingDriverConnectionInt */ private $decoratedConnection; - /** - * @var array The span tags - * - * @deprecated since version 4.10, to be removed in 5.0. Use $spanData instead. - */ - private $spanTags = []; - /** * @var array The data to attach to the span */ @@ -183,7 +176,7 @@ public function rollBack(): bool public function getNativeConnection() { if (!method_exists($this->decoratedConnection, 'getNativeConnection')) { - throw new \BadMethodCallException(sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection))); + throw new \BadMethodCallException(\sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection))); } return $this->decoratedConnection->getNativeConnection(); @@ -198,7 +191,7 @@ public function errorCode(): ?string return $this->decoratedConnection->errorCode(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } /** @@ -210,7 +203,7 @@ public function errorInfo(): array return $this->decoratedConnection->errorInfo(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } public function getWrappedConnection(): DriverConnectionInterface @@ -230,12 +223,13 @@ private function traceFunction(string $spanOperation, string $spanDescription, \ $span = $this->hub->getSpan(); if (null !== $span) { - $spanContext = new SpanContext(); - $spanContext->setOp($spanOperation); - $spanContext->setDescription($spanDescription); - $spanContext->setData($this->spanData); - - $span = $span->startChild($spanContext); + $span = $span->startChild( + SpanContext::make() + ->setOp($spanOperation) + ->setData($this->spanData) + ->setOrigin('auto.db') + ->setDescription($spanDescription) + ); } try { @@ -252,10 +246,10 @@ private function traceFunction(string $spanOperation, string $spanDescription, \ * * @param array $params The connection params * - * @return array - * * @phpstan-param ConnectionParams $params * + * @return array + * * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md */ private function getSpanData(string $databasePlatform, array $params): array diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php index 88ae34b3..ee0d2bbf 100644 --- a/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php +++ b/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php @@ -184,7 +184,7 @@ public function errorCode(): ?string return $this->decoratedConnection->errorCode(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } /** @@ -196,7 +196,7 @@ public function errorInfo(): array return $this->decoratedConnection->errorInfo(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } public function getWrappedConnection(): DriverConnectionInterface @@ -221,12 +221,13 @@ private function traceFunction(string $spanOperation, string $spanDescription, \ $span = $this->hub->getSpan(); if (null !== $span) { - $spanContext = new SpanContext(); - $spanContext->setOp($spanOperation); - $spanContext->setDescription($spanDescription); - $spanContext->setData($this->spanData); - - $span = $span->startChild($spanContext); + $span = $span->startChild( + SpanContext::make() + ->setOp($spanOperation) + ->setData($this->spanData) + ->setOrigin('auto.db') + ->setDescription($spanDescription) + ); } try { @@ -243,10 +244,10 @@ private function traceFunction(string $spanOperation, string $spanDescription, \ * * @param array $params The connection params * - * @return array - * * @phpstan-param ConnectionParams $params * + * @return array + * * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md */ private function getSpanData(string $databasePlatform, array $params): array diff --git a/src/Tracing/Doctrine/DBAL/TracingDriverMiddleware.php b/src/Tracing/Doctrine/DBAL/TracingDriverMiddleware.php index 0ef93b70..d9129441 100644 --- a/src/Tracing/Doctrine/DBAL/TracingDriverMiddleware.php +++ b/src/Tracing/Doctrine/DBAL/TracingDriverMiddleware.php @@ -31,11 +31,11 @@ public function __construct($hubOrConnectionFactory) if ($hubOrConnectionFactory instanceof TracingDriverConnectionFactoryInterface) { $this->connectionFactory = $hubOrConnectionFactory; } elseif ($hubOrConnectionFactory instanceof HubInterface) { - @trigger_error(sprintf('Not passing an instance of the "%s" interface as argument of the constructor is deprecated since version 4.2 and will not work since version 5.0.', TracingDriverConnectionFactoryInterface::class), \E_USER_DEPRECATED); + @trigger_error(\sprintf('Not passing an instance of the "%s" interface as argument of the constructor is deprecated since version 4.2 and will not work since version 5.0.', TracingDriverConnectionFactoryInterface::class), \E_USER_DEPRECATED); $this->connectionFactory = new TracingDriverConnectionFactory($hubOrConnectionFactory); } else { - throw new \InvalidArgumentException(sprintf('The constructor requires either an instance of the "%s" interface or an instance of the "%s" interface.', HubInterface::class, TracingDriverConnectionFactoryInterface::class)); + throw new \InvalidArgumentException(\sprintf('The constructor requires either an instance of the "%s" interface or an instance of the "%s" interface.', HubInterface::class, TracingDriverConnectionFactoryInterface::class)); } } diff --git a/src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php b/src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php index de1270bd..30945e97 100644 --- a/src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php +++ b/src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php @@ -109,7 +109,7 @@ public function getServerVersion(): string $wrappedConnection = $this->getWrappedConnection(); if (!$wrappedConnection instanceof ServerInfoAwareConnection) { - throw new \BadMethodCallException(sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class)); + throw new \BadMethodCallException(\sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class)); } return $wrappedConnection->getServerVersion(); @@ -123,7 +123,7 @@ public function getServerVersion(): string public function getNativeConnection() { if (!method_exists($this->decoratedConnection, 'getNativeConnection')) { - throw new \BadMethodCallException(sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection))); + throw new \BadMethodCallException(\sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection))); } return $this->decoratedConnection->getNativeConnection(); @@ -137,11 +137,11 @@ public function requiresQueryForServerVersion(): bool $wrappedConnection = $this->getWrappedConnection(); if (!$wrappedConnection instanceof ServerInfoAwareConnection) { - throw new \BadMethodCallException(sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class)); + throw new \BadMethodCallException(\sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class)); } if (!method_exists($wrappedConnection, 'requiresQueryForServerVersion')) { - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } return $wrappedConnection->requiresQueryForServerVersion(); @@ -156,7 +156,7 @@ public function errorCode(): ?string return $this->decoratedConnection->errorCode(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } /** @@ -168,7 +168,7 @@ public function errorInfo(): array return $this->decoratedConnection->errorInfo(); } - throw new \BadMethodCallException(sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__)); } public function getWrappedConnection(): Connection diff --git a/src/Tracing/Doctrine/DBAL/TracingStatementForV2.php b/src/Tracing/Doctrine/DBAL/TracingStatementForV2.php index ca9728ce..a506a17a 100644 --- a/src/Tracing/Doctrine/DBAL/TracingStatementForV2.php +++ b/src/Tracing/Doctrine/DBAL/TracingStatementForV2.php @@ -116,10 +116,11 @@ public function bindParam($param, &$variable, $type = ParameterType::STRING, $le */ public function execute($params = null): bool { - $spanContext = new SpanContext(); - $spanContext->setOp(self::SPAN_OP_STMT_EXECUTE); - $spanContext->setDescription($this->sqlQuery); - $spanContext->setData($this->spanData); + $spanContext = SpanContext::make() + ->setOp(self::SPAN_OP_STMT_EXECUTE) + ->setData($this->spanData) + ->setOrigin('auto.db') + ->setDescription($this->sqlQuery); return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute'], $params); } diff --git a/src/Tracing/Doctrine/DBAL/TracingStatementForV3.php b/src/Tracing/Doctrine/DBAL/TracingStatementForV3.php index b2e8f1a0..45487a5a 100644 --- a/src/Tracing/Doctrine/DBAL/TracingStatementForV3.php +++ b/src/Tracing/Doctrine/DBAL/TracingStatementForV3.php @@ -35,10 +35,11 @@ public function bindParam($param, &$variable, $type = ParameterType::STRING, $le */ public function execute($params = null): Result { - $spanContext = new SpanContext(); - $spanContext->setOp(self::SPAN_OP_STMT_EXECUTE); - $spanContext->setDescription($this->sqlQuery); - $spanContext->setData($this->spanData); + $spanContext = SpanContext::make() + ->setOp(self::SPAN_OP_STMT_EXECUTE) + ->setData($this->spanData) + ->setOrigin('auto.db') + ->setDescription($this->sqlQuery); return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute'], $params); } diff --git a/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php b/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php index ba958687..5537549e 100644 --- a/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php +++ b/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php @@ -27,10 +27,11 @@ public function bindValue(int|string $param, mixed $value, ParameterType $type): */ public function execute(): Result { - $spanContext = new SpanContext(); - $spanContext->setOp(self::SPAN_OP_STMT_EXECUTE); - $spanContext->setDescription($this->sqlQuery); - $spanContext->setData($this->spanData); + $spanContext = SpanContext::make() + ->setOp(self::SPAN_OP_STMT_EXECUTE) + ->setData($this->spanData) + ->setOrigin('auto.db') + ->setDescription($this->sqlQuery); return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute']); } diff --git a/src/Tracing/HttpClient/AbstractTraceableHttpClient.php b/src/Tracing/HttpClient/AbstractTraceableHttpClient.php index 4ee8f77a..5a61dfb8 100644 --- a/src/Tracing/HttpClient/AbstractTraceableHttpClient.php +++ b/src/Tracing/HttpClient/AbstractTraceableHttpClient.php @@ -72,9 +72,10 @@ public function request(string $method, string $url, array $options = []): Respo 'path' => $uri->getPath(), ]); - $context = new SpanContext(); - $context->setOp('http.client'); - $context->setDescription($method . ' ' . (string) $partialUri); + $context = SpanContext::make() + ->setOp('http.client') + ->setOrigin('auto.http.client') + ->setDescription($method . ' ' . (string) $partialUri); $contextData = [ 'http.url' => (string) $partialUri, @@ -93,6 +94,7 @@ public function request(string $method, string $url, array $options = []): Respo if (self::shouldAttachTracingHeaders($client, $uri)) { $headers['baggage'] = $childSpan->toBaggage(); $headers['sentry-trace'] = $childSpan->toTraceparent(); + $headers['traceparent'] = $childSpan->toW3CTraceparent(); } $options['headers'] = $headers; @@ -103,12 +105,12 @@ public function request(string $method, string $url, array $options = []): Respo /** * {@inheritdoc} */ - public function stream($responses, float $timeout = null): ResponseStreamInterface + public function stream($responses, ?float $timeout = null): ResponseStreamInterface { if ($responses instanceof AbstractTraceableResponse) { $responses = [$responses]; } elseif (!is_iterable($responses)) { - throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + throw new \TypeError(\sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); } return new ResponseStream(AbstractTraceableResponse::stream($this->client, $responses, $timeout)); @@ -138,12 +140,8 @@ private static function shouldAttachTracingHeaders(?ClientInterface $client, Uri // 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($uri->getHost(), $sdkOptions->getTracePropagationTargets()) - ) + null === $sdkOptions->getTracePropagationTargets() + || \in_array($uri->getHost(), $sdkOptions->getTracePropagationTargets()) ) { return true; } diff --git a/src/Tracing/HttpClient/AbstractTraceableResponse.php b/src/Tracing/HttpClient/AbstractTraceableResponse.php index f53ecd8d..aba53b8e 100644 --- a/src/Tracing/HttpClient/AbstractTraceableResponse.php +++ b/src/Tracing/HttpClient/AbstractTraceableResponse.php @@ -107,7 +107,7 @@ public static function stream(HttpClientInterface $client, iterable $responses, foreach ($responses as $response) { if (!$response instanceof self) { - throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($response))); + throw new \TypeError(\sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($response))); } $traceableMap[$response->response] = $response; diff --git a/src/Tracing/HttpClient/TraceableResponseForV5.php b/src/Tracing/HttpClient/TraceableResponseForV5.php index 0ca572a2..c079fe4f 100644 --- a/src/Tracing/HttpClient/TraceableResponseForV5.php +++ b/src/Tracing/HttpClient/TraceableResponseForV5.php @@ -16,7 +16,7 @@ final class TraceableResponseForV5 extends AbstractTraceableResponse implements * * @return mixed */ - public function getInfo(string $type = null) + public function getInfo(?string $type = null) { return $this->response->getInfo($type); } diff --git a/src/Tracing/HttpClient/TraceableResponseForV6.php b/src/Tracing/HttpClient/TraceableResponseForV6.php index 43dbbbf2..64cc7f28 100644 --- a/src/Tracing/HttpClient/TraceableResponseForV6.php +++ b/src/Tracing/HttpClient/TraceableResponseForV6.php @@ -14,7 +14,7 @@ final class TraceableResponseForV6 extends AbstractTraceableResponse implements /** * {@inheritdoc} */ - public function getInfo(string $type = null): mixed + public function getInfo(?string $type = null): mixed { return $this->response->getInfo($type); } diff --git a/src/Tracing/Twig/TwigTracingExtension.php b/src/Tracing/Twig/TwigTracingExtension.php index ec06e58c..e03996d4 100644 --- a/src/Tracing/Twig/TwigTracingExtension.php +++ b/src/Tracing/Twig/TwigTracingExtension.php @@ -46,11 +46,12 @@ public function enter(Profile $profile): void return; } - $spanContext = new SpanContext(); - $spanContext->setOp('view.render'); - $spanContext->setDescription($this->getSpanDescription($profile)); - - $this->spans[$profile] = $transaction->startChild($spanContext); + $this->spans[$profile] = $transaction->startChild( + SpanContext::make() + ->setOp('view.render') + ->setOrigin('auto.view') + ->setDescription($this->getSpanDescription($profile)) + ); } /** @@ -95,7 +96,7 @@ private function getSpanDescription(Profile $profile): string return $profile->getTemplate(); default: - return sprintf('%s::%s(%s)', $profile->getTemplate(), $profile->getType(), $profile->getName()); + return \sprintf('%s::%s(%s)', $profile->getTemplate(), $profile->getType(), $profile->getName()); } } } diff --git a/src/Transport/TransportFactory.php b/src/Transport/TransportFactory.php deleted file mode 100644 index 6ddd272b..00000000 --- a/src/Transport/TransportFactory.php +++ /dev/null @@ -1,65 +0,0 @@ -decoratedTransportFactory = new DefaultTransportFactory( - $streamFactory, - $requestFactory, - new HttpClientFactory( - $uriFactory, - $responseFactory, - $streamFactory, - $httpClient, - 'sentry.php.symfony', - SentryBundle::SDK_VERSION - ), - $logger - ); - } - - public function create(Options $options): TransportInterface - { - return $this->decoratedTransportFactory->create($options); - } -} diff --git a/src/Twig/SentryExtension.php b/src/Twig/SentryExtension.php index ba705155..3c2ecc77 100644 --- a/src/Twig/SentryExtension.php +++ b/src/Twig/SentryExtension.php @@ -16,7 +16,7 @@ final class SentryExtension extends AbstractExtension /** * @param HubInterface $hub The current hub */ - public function __construct(HubInterface $hub = null) + public function __construct(?HubInterface $hub = null) { } @@ -27,6 +27,7 @@ public function getFunctions(): array { return [ new TwigFunction('sentry_trace_meta', [$this, 'getTraceMeta'], ['is_safe' => ['html']]), + new TwigFunction('sentry_w3c_trace_meta', [$this, 'getW3CTraceMeta'], ['is_safe' => ['html']]), new TwigFunction('sentry_baggage_meta', [$this, 'getBaggageMeta'], ['is_safe' => ['html']]), ]; } @@ -36,7 +37,17 @@ public function getFunctions(): array */ public function getTraceMeta(): string { - return sprintf('', getTraceparent()); + return \sprintf('', getTraceparent()); + } + + /** + * Returns an HTML meta tag named `traceparent`. + * + * @deprecated since version 5.3. To be removed in version 6.0. + */ + public function getW3CTraceMeta(): string + { + return ''; } /** @@ -44,6 +55,6 @@ public function getTraceMeta(): string */ public function getBaggageMeta(): string { - return sprintf('', getBaggage()); + return \sprintf('', getBaggage()); } } diff --git a/src/aliases.php b/src/aliases.php index fddd2689..e0433bcb 100644 --- a/src/aliases.php +++ b/src/aliases.php @@ -9,6 +9,7 @@ use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3; +use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3WithNamespace; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2; use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3; @@ -36,13 +37,19 @@ use Sentry\SentryBundle\Tracing\HttpClient\TraceableResponseForV6; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\DoctrineProvider; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; if (interface_exists(AdapterInterface::class)) { if (!class_exists(DoctrineProvider::class, false) && version_compare(\PHP_VERSION, '8.0.0', '>=')) { if (!class_exists(TraceableCacheAdapter::class, false)) { - class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class); + if (interface_exists(NamespacedPoolInterface::class)) { + class_alias(TraceableCacheAdapterForV3WithNamespace::class, TraceableCacheAdapter::class); + } else { + class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class); + } } if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) { @@ -78,7 +85,7 @@ class_alias(TracingDriverConnectionFactoryForV2V3::class, TracingDriverConnectio } } -if (!class_exists(TraceableResponse::class) && interface_exists(HttpClientInterface::class)) { +if (!class_exists(TraceableResponse::class) && class_exists(HttpClient::class)) { if (!interface_exists(StreamableInterface::class)) { class_alias(TraceableResponseForV4::class, TraceableResponse::class); class_alias(TraceableHttpClientForV4::class, TraceableHttpClient::class); diff --git a/tests/DependencyInjection/Compiler/AddLoginListenerTagPassTest.php b/tests/DependencyInjection/Compiler/AddLoginListenerTagPassTest.php index cc91f8f7..6fb3248b 100644 --- a/tests/DependencyInjection/Compiler/AddLoginListenerTagPassTest.php +++ b/tests/DependencyInjection/Compiler/AddLoginListenerTagPassTest.php @@ -28,4 +28,20 @@ public function testProcess(): void $this->assertSame([['event' => AuthenticationSuccessEvent::class, 'method' => 'handleAuthenticationSuccessEvent']], $listenerDefinition->getTag('kernel.event_listener')); } + + public function testProcessLoginSuccess(): void + { + if (!class_exists(LoginSuccessEvent::class)) { + $this->markTestSkipped('Skipping this test because LoginSuccessEvent does not exist.'); + } + + $container = new ContainerBuilder(); + $container->register(LoginListener::class)->setPublic(true); + $container->addCompilerPass(new AddLoginListenerTagPass()); + $container->compile(); + + $listenerDefinition = $container->getDefinition(LoginListener::class); + + $this->assertSame([['event' => LoginSuccessEvent::class, 'method' => 'handleLoginSuccessEvent']], $listenerDefinition->getTag('kernel.event_listener')); + } } diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index c5a6bea9..c993f6f2 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -23,12 +23,13 @@ public function testProcessConfigurationWithDefaultConfiguration(): void 'register_error_listener' => true, 'register_error_handler' => true, 'logger' => null, - 'transport_factory' => 'Sentry\\Transport\\TransportFactoryInterface', 'options' => [ 'integrations' => [], 'prefixes' => array_merge(['%kernel.project_dir%'], array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: ''))), 'environment' => '%kernel.environment%', 'release' => '%env(default::SENTRY_RELEASE)%', + 'ignore_exceptions' => [], + 'ignore_transactions' => [], 'tags' => [], 'in_app_exclude' => [ '%kernel.cache_dir%', @@ -37,8 +38,6 @@ public function testProcessConfigurationWithDefaultConfiguration(): void ], 'in_app_include' => [], 'class_serializers' => [], - 'ignore_exceptions' => [], - 'ignore_transactions' => [], ], 'messenger' => [ 'enabled' => interface_exists(MessageBusInterface::class), diff --git a/tests/DependencyInjection/Fixtures/php/error_types.php b/tests/DependencyInjection/Fixtures/php/error_types.php index 9c8473e5..6d072037 100644 --- a/tests/DependencyInjection/Fixtures/php/error_types.php +++ b/tests/DependencyInjection/Fixtures/php/error_types.php @@ -7,6 +7,7 @@ /** @var ContainerBuilder $container */ $container->loadFromExtension('sentry', [ 'options' => [ - 'error_types' => \E_ALL & ~(\E_NOTICE | \E_STRICT | \E_DEPRECATED), + // 2048 is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices + 'error_types' => \E_ALL & ~(\E_NOTICE | 2048 | \E_DEPRECATED), ], ]); diff --git a/tests/DependencyInjection/Fixtures/php/full.php b/tests/DependencyInjection/Fixtures/php/full.php index 7c69cc19..aa11c22f 100644 --- a/tests/DependencyInjection/Fixtures/php/full.php +++ b/tests/DependencyInjection/Fixtures/php/full.php @@ -7,27 +7,32 @@ /** @var ContainerBuilder $container */ $container->loadFromExtension('sentry', [ 'dsn' => 'https://examplePublicKey@o0.ingest.sentry.io/0', - 'transport_factory' => 'App\\Sentry\\Transport\\TransportFactory', 'logger' => 'app.logger', 'options' => [ 'integrations' => ['App\\Sentry\\Integration\\FooIntegration'], 'default_integrations' => false, - 'send_attempts' => 1, 'prefixes' => ['%kernel.project_dir%'], 'sample_rate' => 1, + 'enable_tracing' => true, 'traces_sample_rate' => 1, - 'profiles_sample_rate' => 1, 'traces_sampler' => 'App\\Sentry\\Tracing\\TracesSampler', - 'trace_propagation_targets' => ['website.invalid'], + 'profiles_sample_rate' => 1, 'attach_stacktrace' => true, + 'attach_metric_code_locations' => true, 'context_lines' => 0, - 'enable_compression' => true, 'environment' => 'development', - 'logger' => 'php', + 'logger' => Sentry\Logger\DebugStdOutLogger::class, + 'spotlight' => true, + 'spotlight_url' => 'http://localhost:8969', 'release' => '4.0.x-dev', 'server_name' => 'localhost', + 'ignore_exceptions' => ['Symfony\Component\HttpKernel\Exception\BadRequestHttpException'], + 'ignore_transactions' => ['GET tracing_ignored_transaction'], 'before_send' => 'App\\Sentry\\BeforeSendCallback', 'before_send_transaction' => 'App\\Sentry\\BeforeSendTransactionCallback', + 'before_send_check_in' => 'App\\Sentry\\BeforeSendCheckInCallback', + 'before_send_metrics' => 'App\\Sentry\\BeforeSendMetricsCallback', + 'trace_propagation_targets' => ['website.invalid'], 'tags' => [ 'context' => 'development', ], @@ -38,14 +43,17 @@ 'in_app_include' => ['%kernel.project_dir%'], 'send_default_pii' => true, 'max_value_length' => 255, + 'transport' => 'App\\Sentry\\Transport', + 'http_client' => 'App\\Sentry\\HttpClient', 'http_proxy' => 'proxy.example.com:8080', - 'http_timeout' => 10, + 'http_proxy_authentication' => 'user:password', 'http_connect_timeout' => 15, + 'http_timeout' => 10, + 'http_ssl_verify_peer' => true, + 'http_compression' => true, 'capture_silenced_errors' => true, 'max_request_body_size' => 'none', 'class_serializers' => ['App\\FooClass' => 'App\\Sentry\\Serializer\\FooClassSerializer'], - 'ignore_exceptions' => ['Symfony\Component\HttpKernel\Exception\BadRequestHttpException'], - 'ignore_transactions' => ['GET tracing_ignored_transaction'], ], 'messenger' => [ 'enabled' => true, diff --git a/tests/DependencyInjection/Fixtures/xml/error_types.xml b/tests/DependencyInjection/Fixtures/xml/error_types.xml index c0c46ab9..4be73596 100644 --- a/tests/DependencyInjection/Fixtures/xml/error_types.xml +++ b/tests/DependencyInjection/Fixtures/xml/error_types.xml @@ -7,6 +7,6 @@ https://sentry.io/schema/dic/sentry-symfony https://sentry.io/schema/dic/sentry-symfony/sentry-1.0.xsd"> - + diff --git a/tests/DependencyInjection/Fixtures/xml/full.xml b/tests/DependencyInjection/Fixtures/xml/full.xml index f9d84f93..df951eb4 100644 --- a/tests/DependencyInjection/Fixtures/xml/full.xml +++ b/tests/DependencyInjection/Fixtures/xml/full.xml @@ -8,32 +8,40 @@ diff --git a/tests/DependencyInjection/Fixtures/yml/error_types.yml b/tests/DependencyInjection/Fixtures/yml/error_types.yml index 95149254..8c976cd2 100644 --- a/tests/DependencyInjection/Fixtures/yml/error_types.yml +++ b/tests/DependencyInjection/Fixtures/yml/error_types.yml @@ -1,3 +1,3 @@ sentry: options: - error_types: E_ALL & ~(E_NOTICE|E_STRICT|E_DEPRECATED) + error_types: E_ALL & ~(E_NOTICE|2048|E_DEPRECATED) diff --git a/tests/DependencyInjection/Fixtures/yml/full.yml b/tests/DependencyInjection/Fixtures/yml/full.yml index d2f13c5e..8a42e682 100644 --- a/tests/DependencyInjection/Fixtures/yml/full.yml +++ b/tests/DependencyInjection/Fixtures/yml/full.yml @@ -1,29 +1,36 @@ sentry: dsn: https://examplePublicKey@o0.ingest.sentry.io/0 - transport_factory: App\Sentry\Transport\TransportFactory logger: app.logger options: integrations: - App\Sentry\Integration\FooIntegration default_integrations: false - send_attempts: 1 prefixes: - '%kernel.project_dir%' sample_rate: 1 + enable_tracing: true traces_sample_rate: 1 - profiles_sample_rate: 1 traces_sampler: App\Sentry\Tracing\TracesSampler - trace_propagation_targets: - - 'website.invalid' + profiles_sample_rate: 1 attach_stacktrace: true + attach_metric_code_locations: true context_lines: 0 - enable_compression: true environment: development - logger: php + logger: Sentry\Logger\DebugStdOutLogger + spotlight: true + spotlight_url: http://localhost:8969 release: 4.0.x-dev server_name: localhost + ignore_exceptions: + - Symfony\Component\HttpKernel\Exception\BadRequestHttpException + ignore_transactions: + - GET tracing_ignored_transaction before_send: App\Sentry\BeforeSendCallback before_send_transaction: App\Sentry\BeforeSendTransactionCallback + before_send_check_in: App\Sentry\BeforeSendCheckInCallback + before_send_metrics: App\Sentry\BeforeSendMetricsCallback + trace_propagation_targets: + - 'website.invalid' tags: context: development error_types: !php/const E_ALL @@ -35,17 +42,18 @@ sentry: - '%kernel.project_dir%' send_default_pii: true max_value_length: 255 + transport: App\Sentry\Transport + http_client: App\Sentry\HttpClient http_proxy: proxy.example.com:8080 - http_timeout: 10 + http_proxy_authentication: user:password http_connect_timeout: 15 + http_timeout: 10 + http_ssl_verify_peer: true + http_compression: true capture_silenced_errors: true max_request_body_size: 'none' class_serializers: App\FooClass: App\Sentry\Serializer\FooClassSerializer - ignore_exceptions: - - Symfony\Component\HttpKernel\Exception\BadRequestHttpException - ignore_transactions: - - GET tracing_ignored_transaction messenger: enabled: true capture_soft_fails: false diff --git a/tests/DependencyInjection/SentryExtensionTest.php b/tests/DependencyInjection/SentryExtensionTest.php index cfb2b0b6..db949243 100644 --- a/tests/DependencyInjection/SentryExtensionTest.php +++ b/tests/DependencyInjection/SentryExtensionTest.php @@ -9,11 +9,12 @@ use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Sentry\ClientInterface; -use Sentry\Integration\IgnoreErrorsIntegration; +use Sentry\Logger\DebugStdOutLogger; use Sentry\Options; use Sentry\SentryBundle\DependencyInjection\SentryExtension; use Sentry\SentryBundle\EventListener\ConsoleListener; use Sentry\SentryBundle\EventListener\ErrorListener; +use Sentry\SentryBundle\EventListener\LoginListener; use Sentry\SentryBundle\EventListener\MessengerListener; use Sentry\SentryBundle\EventListener\RequestListener; use Sentry\SentryBundle\EventListener\SubRequestListener; @@ -26,11 +27,8 @@ use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware; use Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension; use Sentry\Serializer\RepresentationSerializer; -use Sentry\Serializer\Serializer; -use Sentry\Transport\TransportFactoryInterface; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Console\ConsoleEvents; -use Symfony\Component\Debug\Exception\FatalErrorException; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass; use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; @@ -38,7 +36,6 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\ErrorHandler\Error\FatalError; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\TraceableHttpClient; use Symfony\Component\HttpKernel\KernelEvents; @@ -203,25 +200,38 @@ public function testClientIsCreatedFromOptions(): void $container = $this->createContainerFromFixture('full'); $optionsDefinition = $container->getDefinition('sentry.client.options'); $expectedOptions = [ + 'dsn' => 'https://examplePublicKey@o0.ingest.sentry.io/0', 'integrations' => new Reference(IntegrationConfigurator::class), 'default_integrations' => false, - 'send_attempts' => 1, 'prefixes' => [$container->getParameter('kernel.project_dir')], 'sample_rate' => 1, + 'enable_tracing' => true, 'traces_sample_rate' => 1, - 'profiles_sample_rate' => 1, 'traces_sampler' => new Reference('App\\Sentry\\Tracing\\TracesSampler'), - 'trace_propagation_targets' => ['website.invalid'], + 'profiles_sample_rate' => 1, 'attach_stacktrace' => true, + 'attach_metric_code_locations' => true, 'context_lines' => 0, - 'enable_compression' => true, 'environment' => 'development', - 'logger' => 'php', + 'logger' => new Reference(DebugStdOutLogger::class), + 'spotlight' => true, + 'spotlight_url' => 'http://localhost:8969', 'release' => '4.0.x-dev', 'server_name' => 'localhost', + 'ignore_exceptions' => [ + 'Symfony\Component\HttpKernel\Exception\BadRequestHttpException', + ], + 'ignore_transactions' => [ + 'GET tracing_ignored_transaction', + ], 'before_send' => new Reference('App\\Sentry\\BeforeSendCallback'), 'before_send_transaction' => new Reference('App\\Sentry\\BeforeSendTransactionCallback'), - 'tags' => ['context' => 'development'], + 'before_send_check_in' => new Reference('App\\Sentry\\BeforeSendCheckInCallback'), + 'before_send_metrics' => new Reference('App\\Sentry\\BeforeSendMetricsCallback'), + 'trace_propagation_targets' => ['website.invalid'], + 'tags' => [ + 'context' => 'development', + ], 'error_types' => \E_ALL, 'max_breadcrumbs' => 1, 'before_breadcrumb' => new Reference('App\\Sentry\\BeforeBreadcrumbCallback'), @@ -229,21 +239,19 @@ public function testClientIsCreatedFromOptions(): void 'in_app_include' => [$container->getParameter('kernel.project_dir')], 'send_default_pii' => true, 'max_value_length' => 255, + 'transport' => new Reference('App\\Sentry\\Transport'), + 'http_client' => new Reference('App\\Sentry\\HttpClient'), 'http_proxy' => 'proxy.example.com:8080', + 'http_proxy_authentication' => 'user:password', 'http_timeout' => 10, 'http_connect_timeout' => 15, + 'http_ssl_verify_peer' => true, + 'http_compression' => true, 'capture_silenced_errors' => true, 'max_request_body_size' => 'none', 'class_serializers' => [ 'App\\FooClass' => new Reference('App\\Sentry\\Serializer\\FooClassSerializer'), ], - 'dsn' => 'https://examplePublicKey@o0.ingest.sentry.io/0', - 'ignore_exceptions' => [ - 'Symfony\Component\HttpKernel\Exception\BadRequestHttpException', - ], - 'ignore_transactions' => [ - 'GET tracing_ignored_transaction', - ], ]; $this->assertSame(Options::class, $optionsDefinition->getClass()); @@ -251,14 +259,6 @@ public function testClientIsCreatedFromOptions(): void $integrationConfiguratorDefinition = $container->getDefinition(IntegrationConfigurator::class); $expectedIntegrations = [ - new Definition(IgnoreErrorsIntegration::class, [ - [ - 'ignore_exceptions' => [ - FatalError::class, - FatalErrorException::class, - ], - ], - ]), new Reference('App\\Sentry\\Integration\\FooIntegration'), ]; @@ -274,32 +274,15 @@ public function testClientIsCreatedFromOptions(): void $methodCalls = $factory[0]->getMethodCalls(); - $this->assertCount(6, $methodCalls); + $this->assertCount(4, $methodCalls); $this->assertDefinitionMethodCallAt($methodCalls[0], 'setSdkIdentifier', [SentryBundle::SDK_IDENTIFIER]); $this->assertDefinitionMethodCallAt($methodCalls[1], 'setSdkVersion', [SentryBundle::SDK_VERSION]); - $this->assertDefinitionMethodCallAt($methodCalls[2], 'setTransportFactory', [new Reference('App\\Sentry\\Transport\\TransportFactory')]); - $this->assertDefinitionMethodCallAt($methodCalls[5], 'setLogger', [new Reference('app.logger')]); - - $this->assertSame('setSerializer', $methodCalls[3][0]); - $this->assertInstanceOf(Definition::class, $methodCalls[3][1][0]); - $this->assertSame(Serializer::class, $methodCalls[3][1][0]->getClass()); - $this->assertEquals($methodCalls[3][1][0]->getArgument(0), new Reference('sentry.client.options')); - - $this->assertSame('setRepresentationSerializer', $methodCalls[4][0]); - $this->assertInstanceOf(Definition::class, $methodCalls[4][1][0]); - $this->assertSame(RepresentationSerializer::class, $methodCalls[4][1][0]->getClass()); - $this->assertEquals($methodCalls[4][1][0]->getArgument(0), new Reference('sentry.client.options')); - } + $this->assertDefinitionMethodCallAt($methodCalls[3], 'setLogger', [new Reference('app.logger')]); - public function testLoggerIsPassedToTransportFactory(): void - { - $container = $this->createContainerFromFixture('full'); - - $transportFactoryDefinition = $container->findDefinition(TransportFactoryInterface::class); - $logger = $transportFactoryDefinition->getArgument('$logger'); - - $this->assertInstanceOf(Reference::class, $logger); - $this->assertSame('app.logger', $logger->__toString()); + $this->assertSame('setRepresentationSerializer', $methodCalls[2][0]); + $this->assertInstanceOf(Definition::class, $methodCalls[2][1][0]); + $this->assertSame(RepresentationSerializer::class, $methodCalls[2][1][0]->getClass()); + $this->assertEquals($methodCalls[2][1][0]->getArgument(0), new Reference('sentry.client.options')); } public function testErrorTypesOptionIsParsedFromStringToIntegerValue(): void @@ -307,26 +290,8 @@ public function testErrorTypesOptionIsParsedFromStringToIntegerValue(): void $container = $this->createContainerFromFixture('error_types'); $optionsDefinition = $container->getDefinition('sentry.client.options'); - $this->assertSame(\E_ALL & ~(\E_NOTICE | \E_STRICT | \E_DEPRECATED), $optionsDefinition->getArgument(0)['error_types']); - } - - public function testIgnoreErrorsIntegrationIsNotAddedTwiceIfAlreadyConfigured(): void - { - $container = $this->createContainerFromFixture('ignore_errors_integration_overridden'); - $integrations = $container->getDefinition(IntegrationConfigurator::class)->getArgument(0); - $ignoreErrorsIntegrationsCount = 0; - - foreach ($integrations as $integration) { - if ($integration instanceof Reference && IgnoreErrorsIntegration::class === (string) $integration) { - ++$ignoreErrorsIntegrationsCount; - } - - if ($integration instanceof Definition && IgnoreErrorsIntegration::class === $integration->getClass()) { - ++$ignoreErrorsIntegrationsCount; - } - } - - $this->assertSame(1, $ignoreErrorsIntegrationsCount); + // 2048 is \E_STRICT which has been deprecated in PHP 8.4 so we should not reference it directly to prevent deprecation notices + $this->assertSame(\E_ALL & ~(\E_NOTICE | 2048 | \E_DEPRECATED), $optionsDefinition->getArgument(0)['error_types']); } /** @@ -451,7 +416,13 @@ public function testLoggerOptionFallbackToNullLoggerIfNotSet(): void $methodCalls = $factory[0]->getMethodCalls(); - $this->assertDefinitionMethodCallAt($methodCalls[5], 'setLogger', [new Reference(NullLogger::class, ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)]); + $this->assertDefinitionMethodCallAt($methodCalls[3], 'setLogger', [new Reference(NullLogger::class, ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)]); + } + + public function testLoginListener(): void + { + $container = $this->createContainerFromFixture('full'); + $this->assertTrue($container->hasDefinition(LoginListener::class)); } /** diff --git a/tests/End2End/App/Controller/TracingController.php b/tests/End2End/App/Controller/TracingController.php index 37579ae4..fa86876a 100644 --- a/tests/End2End/App/Controller/TracingController.php +++ b/tests/End2End/App/Controller/TracingController.php @@ -21,7 +21,7 @@ class TracingController */ private $connection; - public function __construct(HubInterface $hub, Connection $connection = null) + public function __construct(HubInterface $hub, ?Connection $connection = null) { $this->hub = $hub; $this->connection = $connection; diff --git a/tests/End2End/App/Kernel.php b/tests/End2End/App/Kernel.php index 3f323d7e..c3467c61 100644 --- a/tests/End2End/App/Kernel.php +++ b/tests/End2End/App/Kernel.php @@ -44,6 +44,13 @@ public function registerContainerConfiguration(LoaderInterface $loader): void $loader->load(__DIR__ . '/deprecations_for_5.yml'); } + if (self::VERSION_ID >= 50400 && self::VERSION_ID <= 60000) { + // Check if class for Messenger is present (component symfony/messenger is not mandatory) + if (interface_exists(MessageBusInterface::class)) { + $loader->load(__DIR__ . '/deprecations_for_54.yml'); + } + } + if (self::VERSION_ID >= 60000) { $loader->load(__DIR__ . '/deprecations_for_6.yml'); } diff --git a/tests/End2End/App/Messenger/FooMessageHandler.php b/tests/End2End/App/Messenger/FooMessageHandler.php index 5641175d..28518d96 100644 --- a/tests/End2End/App/Messenger/FooMessageHandler.php +++ b/tests/End2End/App/Messenger/FooMessageHandler.php @@ -11,7 +11,7 @@ class FooMessageHandler public function __invoke(FooMessage $message): void { if (!$message->shouldRetry()) { - throw new class() extends \Exception implements UnrecoverableExceptionInterface { }; + throw new class extends \Exception implements UnrecoverableExceptionInterface { }; } throw new \Exception('This is an intentional failure while handling a message of class ' . \get_class($message)); diff --git a/tests/End2End/App/config.yml b/tests/End2End/App/config.yml index 52837104..87aa6cf3 100644 --- a/tests/End2End/App/config.yml +++ b/tests/End2End/App/config.yml @@ -6,8 +6,12 @@ sentry: capture_silenced_errors: false error_types: E_ALL & ~E_USER_DEPRECATED traces_sample_rate: 0 - ignore_exceptions: 'Symfony\Component\HttpKernel\Exception\BadRequestHttpException' + ignore_exceptions: + - 'Symfony\Component\HttpKernel\Exception\BadRequestHttpException' + - 'Symfony\Component\ErrorHandler\Error\FatalError' + - 'Symfony\Component\Debug\Exception\FatalErrorException' ignore_transactions: 'GET tracing_ignored_transaction' + transport: 'Sentry\SentryBundle\Tests\End2End\StubTransport' framework: router: { resource: "%routing_config_dir%/routing.yml" } @@ -22,10 +26,7 @@ services: alias: Sentry\State\HubInterface public: true - Sentry\SentryBundle\Tests\End2End\StubTransportFactory: ~ - - Sentry\Transport\TransportFactoryInterface: - alias: Sentry\SentryBundle\Tests\End2End\StubTransportFactory + Sentry\SentryBundle\Tests\End2End\StubTransport: ~ Sentry\SentryBundle\Tests\End2End\App\Controller\MainController: autowire: true diff --git a/tests/End2End/App/deprecations_for_54.yml b/tests/End2End/App/deprecations_for_54.yml new file mode 100644 index 00000000..abd0417d --- /dev/null +++ b/tests/End2End/App/deprecations_for_54.yml @@ -0,0 +1,3 @@ +framework: + messenger: + reset_on_message: true diff --git a/tests/End2End/End2EndTest.php b/tests/End2End/End2EndTest.php index 7eef4ffd..19ddfe06 100644 --- a/tests/End2End/End2EndTest.php +++ b/tests/End2End/End2EndTest.php @@ -207,10 +207,10 @@ public function testCommand(): void } $this->assertEventCount(1); - $this->assertCount(1, StubTransportFactory::$events); + $this->assertCount(1, StubTransport::$events); $this->assertSame( ['Full command' => 'main-command --option1 --option2=foo bar'], - StubTransportFactory::$events[0]->getExtra() + StubTransport::$events[0]->getExtra() ); } @@ -281,7 +281,7 @@ private function assertEventCount(int $expectedCount): void { $events = file_get_contents(self::SENT_EVENTS_LOG); $this->assertNotFalse($events, 'Cannot read sent events log'); - $listOfEvents = array_filter(explode(StubTransportFactory::SEPARATOR, trim($events))); + $listOfEvents = array_filter(explode(StubTransport::SEPARATOR, trim($events))); $this->assertCount($expectedCount, $listOfEvents, 'Wrong number of events sent: ' . \PHP_EOL . $events); } diff --git a/tests/End2End/StubTransport.php b/tests/End2End/StubTransport.php new file mode 100644 index 00000000..3f3b7644 --- /dev/null +++ b/tests/End2End/StubTransport.php @@ -0,0 +1,52 @@ +getMessage()) { + $message = $event->getMessage(); + } elseif ($event->getExceptions()) { + $message = $event->getExceptions()[0]->getValue(); + } elseif ($event->getTransaction()) { + $message = 'TRACING: ' . $event->getTransaction(); + foreach ($event->getSpans() as $i => $span) { + $message .= \PHP_EOL . $i . ') ' . $span->getDescription(); + } + } else { + $message = 'NO MESSAGE NOR EXCEPTIONS'; + } + + file_put_contents( + End2EndTest::SENT_EVENTS_LOG, + $event->getId() . ': ' . $message . \PHP_EOL . self::SEPARATOR . \PHP_EOL, + \FILE_APPEND + ); + + return new Result(ResultStatus::success(), $event); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +} diff --git a/tests/End2End/StubTransportFactory.php b/tests/End2End/StubTransportFactory.php deleted file mode 100644 index 061f068b..00000000 --- a/tests/End2End/StubTransportFactory.php +++ /dev/null @@ -1,61 +0,0 @@ -getMessage()) { - $message = $event->getMessage(); - } elseif ($event->getExceptions()) { - $message = $event->getExceptions()[0]->getValue(); - } elseif ($event->getTransaction()) { - $message = 'TRACING: ' . $event->getTransaction(); - foreach ($event->getSpans() as $i => $span) { - $message .= \PHP_EOL . $i . ') ' . $span->getDescription(); - } - } else { - $message = 'NO MESSAGE NOR EXCEPTIONS'; - } - - file_put_contents( - End2EndTest::SENT_EVENTS_LOG, - $event->getId() . ': ' . $message . \PHP_EOL . StubTransportFactory::SEPARATOR . \PHP_EOL, - \FILE_APPEND - ); - - return new FulfilledPromise(new Response(ResponseStatus::success(), $event)); - } - - public function close(?int $timeout = null): PromiseInterface - { - return new FulfilledPromise(true); - } - }; - } -} diff --git a/tests/End2End/TracingEnd2EndTest.php b/tests/End2End/TracingEnd2EndTest.php index b3204d0c..0a764b32 100644 --- a/tests/End2End/TracingEnd2EndTest.php +++ b/tests/End2End/TracingEnd2EndTest.php @@ -87,7 +87,7 @@ private function assertTracingEventCount(int $expectedCount): void { $events = file_get_contents(self::SENT_EVENTS_LOG); $this->assertNotFalse($events, 'Cannot read sent events log'); - $listOfTracingEvents = array_filter(explode(StubTransportFactory::SEPARATOR, trim($events)), static function (string $elem) { + $listOfTracingEvents = array_filter(explode(StubTransport::SEPARATOR, trim($events)), static function (string $elem) { return str_contains('TRACING', $elem); }); diff --git a/tests/ErrorTypesParserTest.php b/tests/ErrorTypesParserTest.php index e30d6c78..9e3cd532 100644 --- a/tests/ErrorTypesParserTest.php +++ b/tests/ErrorTypesParserTest.php @@ -71,5 +71,8 @@ public function parseThrowsExceptionIfArgumentContainsInvalidCharactersDataProvi yield ['(']; yield [')']; yield ['()']; + // Non scalar values (probably misstypes, but still valid PHP code) + yield ['[8, 8192]']; + yield [\stdClass::class]; } } diff --git a/tests/EventListener/ConsoleCommandListenerTest.php b/tests/EventListener/ConsoleCommandListenerTest.php deleted file mode 100644 index e217dca4..00000000 --- a/tests/EventListener/ConsoleCommandListenerTest.php +++ /dev/null @@ -1,23 +0,0 @@ -username = $username; } + /** + * @return non-empty-string + */ public function getUserIdentifier(): string { return $this->getUsername(); } + /** + * @return non-empty-string + */ public function getUsername(): string { return $this->username; diff --git a/tests/EventListener/LoginListenerTest.php b/tests/EventListener/LoginListenerTest.php index 8092cc15..da037bef 100644 --- a/tests/EventListener/LoginListenerTest.php +++ b/tests/EventListener/LoginListenerTest.php @@ -183,17 +183,33 @@ public function testHandleAuthenticationSuccessEvent(TokenInterface $token, ?Use public function authenticationTokenDataProvider(): \Generator { - yield 'If the username is already set on the User context, then it is not overridden' => [ - new AuthenticatedTokenStub(new UserWithIdentifierStub()), - new UserDataBag('bar_user'), - new UserDataBag('bar_user'), - ]; + if (version_compare(Kernel::VERSION, '5.4', '<')) { + yield 'If the username is already set on the User context, then it is not overridden' => [ + new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()), + new UserDataBag('bar_user'), + new UserDataBag('bar_user'), + ]; + } else { + yield 'If the username is already set on the User context, then it is not overridden' => [ + new AuthenticatedTokenStub(new UserWithIdentifierStub()), + new UserDataBag('bar_user'), + new UserDataBag('bar_user'), + ]; + } - yield 'If the username is not set on the User context, then it is retrieved from the token' => [ - new AuthenticatedTokenStub(new UserWithIdentifierStub()), - null, - new UserDataBag('foo_user'), - ]; + if (version_compare(Kernel::VERSION, '5.4', '<')) { + yield 'If the username is not set on the User context, then it is retrieved from the token' => [ + new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()), + null, + new UserDataBag('foo_user'), + ]; + } else { + yield 'If the username is not set on the User context, then it is retrieved from the token' => [ + new AuthenticatedTokenStub(new UserWithIdentifierStub()), + null, + new UserDataBag('foo_user'), + ]; + } yield 'If the user is being impersonated, then the username of the impersonator is set on the User context' => [ (static function (): SwitchUserToken { @@ -203,7 +219,8 @@ public function authenticationTokenDataProvider(): \Generator null, 'foo_provider', ['ROLE_USER'], - new AuthenticatedTokenStub(new UserWithIdentifierStub('bar_user')) + // @phpstan-ignore-next-line + new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub('bar_user')) ); } @@ -228,28 +245,69 @@ public function authenticationTokenForSymfonyVersionLowerThan54DataProvider(): \ return; } - yield 'If the user is a string, then the value is used as-is' => [ - new AuthenticatedTokenStub('foo_user'), - null, - new UserDataBag('foo_user'), - ]; + if (version_compare(Kernel::VERSION, '5.0', '<')) { + yield 'If the user is a string, then the value is used as-is' => [ + new LegacyAuthenticatedTokenStub('foo_user'), + null, + new UserDataBag('foo_user'), + ]; + } else { + yield 'If the user is a string, then the value is used as-is' => [ + new AuthenticatedTokenStub('foo_user'), + null, + new UserDataBag('foo_user'), + ]; + } - yield 'If the user is an instance of the UserInterface interface but the getUserIdentifier() method does not exist, then the getUsername() method is invoked' => [ - new AuthenticatedTokenStub(new UserWithoutIdentifierStub()), - null, - new UserDataBag('foo_user'), - ]; + if (version_compare(Kernel::VERSION, '5.0', '<')) { + yield 'If the user is an instance of the UserInterface interface but the getUserIdentifier() method does not exist, then the getUsername() method is invoked' => [ + new LegacyAuthenticatedTokenStub(new UserWithoutIdentifierStub()), + null, + new UserDataBag('foo_user'), + ]; + } else { + yield 'If the user is an instance of the UserInterface interface but the getUserIdentifier() method does not exist, then the getUsername() method is invoked' => [ + new AuthenticatedTokenStub(new UserWithoutIdentifierStub()), + null, + new UserDataBag('foo_user'), + ]; + } - yield 'If the user is an object implementing the Stringable interface, then the __toString() method is invoked' => [ - new AuthenticatedTokenStub(new class() implements \Stringable { - public function __toString(): string - { - return 'foo_user'; - } - }), - null, - new UserDataBag('foo_user'), - ]; + if (version_compare(Kernel::VERSION, '5.0', '<')) { + yield 'If the user is an object implementing the Stringable interface, then the __toString() method is invoked' => [ + new LegacyAuthenticatedTokenStub(new class implements \Stringable { + public function __toString(): string + { + return 'foo_user'; + } + }), + null, + new UserDataBag('foo_user'), + ]; + } else { + yield 'If the user is an object implementing the Stringable interface, then the __toString() method is invoked' => [ + new AuthenticatedTokenStub(new class implements \Stringable { + public function __toString(): string + { + return 'foo_user'; + } + }), + null, + new UserDataBag('foo_user'), + ]; + } + } + + public function testHandleKernelRequestEventDoesNothingIfRequestIsForStatelessRoute(): void + { + $this->tokenStorage->expects($this->never()) + ->method('getToken'); + + $this->listener->handleKernelRequestEvent(new RequestEvent( + $this->createMock(HttpKernelInterface::class), + new Request([], [], ['_stateless' => true]), + HttpKernelInterface::SUB_REQUEST + )); } public function testHandleKernelRequestEventDoesNothingIfRequestIsNotMain(): void @@ -312,14 +370,25 @@ public function testHandleLoginSuccessEventDoesNothingIfClientIsNotSetOnHub(): v $this->hub->expects($this->never()) ->method('configureScope'); - $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( - $this->createMock(AuthenticatorInterface::class), - new SelfValidatingPassport(new UserBadge('foo_passport_user')), - new AuthenticatedTokenStub(new UserWithIdentifierStub()), - new Request(), - null, - 'main' - )); + if (version_compare(Kernel::VERSION, '5.4', '<')) { + $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( + $this->createMock(AuthenticatorInterface::class), + new SelfValidatingPassport(new UserBadge('foo_passport_user')), + new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()), + new Request(), + null, + 'main' + )); + } else { + $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( + $this->createMock(AuthenticatorInterface::class), + new SelfValidatingPassport(new UserBadge('foo_passport_user')), + new AuthenticatedTokenStub(new UserWithIdentifierStub()), + new Request(), + null, + 'main' + )); + } } public function testHandleLoginSuccessEventDoesNothingIfSendingDefaultPiiIsDisabled(): void @@ -340,14 +409,25 @@ public function testHandleLoginSuccessEventDoesNothingIfSendingDefaultPiiIsDisab $this->hub->expects($this->never()) ->method('configureScope'); - $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( - $this->createMock(AuthenticatorInterface::class), - new SelfValidatingPassport(new UserBadge('foo_passport_user')), - new AuthenticatedTokenStub(new UserWithIdentifierStub()), - new Request(), - null, - 'main' - )); + if (version_compare(Kernel::VERSION, '5.4', '<')) { + $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( + $this->createMock(AuthenticatorInterface::class), + new SelfValidatingPassport(new UserBadge('foo_passport_user')), + new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()), + new Request(), + null, + 'main' + )); + } else { + $this->listener->handleLoginSuccessEvent(new LoginSuccessEvent( + $this->createMock(AuthenticatorInterface::class), + new SelfValidatingPassport(new UserBadge('foo_passport_user')), + new AuthenticatedTokenStub(new UserWithIdentifierStub()), + new Request(), + null, + 'main' + )); + } } public function testHandleAuthenticationSuccessEventDoesNothingIfTokenIsNotAuthenticated(): void @@ -378,7 +458,11 @@ public function testHandleAuthenticationSuccessEventDoesNothingIfClientIsNotSetO $this->hub->expects($this->never()) ->method('configureScope'); - $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new AuthenticatedTokenStub(new UserWithIdentifierStub()))); + if (version_compare(Kernel::VERSION, '5.4', '<')) { + $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()))); + } else { + $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new AuthenticatedTokenStub(new UserWithIdentifierStub()))); + } } public function testHandleAuthenticationSuccessEventDoesNothingIfSendingDefaultPiiIsDisabled(): void @@ -399,7 +483,11 @@ public function testHandleAuthenticationSuccessEventDoesNothingIfSendingDefaultP $this->hub->expects($this->never()) ->method('configureScope'); - $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new AuthenticatedTokenStub(new UserWithIdentifierStub()))); + if (version_compare(Kernel::VERSION, '5.4', '<')) { + $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new LegacyAuthenticatedTokenStub(new UserWithIdentifierStub()))); + } else { + $this->listener->handleAuthenticationSuccessEvent(new AuthenticationSuccessEvent(new AuthenticatedTokenStub(new UserWithIdentifierStub()))); + } } } @@ -416,8 +504,47 @@ public function getCredentials(): ?string } } +class LegacyAuthenticatedTokenStub extends AbstractToken +{ + /** + * @var bool + * + * @phpstan-ignore-next-line + */ + private $authenticated = false; + + /** + * @param UserInterface|\Stringable|string|null $user + */ + public function __construct($user) + { + parent::__construct(); + + if (null !== $user) { + // @phpstan-ignore-next-line + $this->setUser($user); + } + + if (version_compare(Kernel::VERSION, '5.4', '<') && method_exists($this, 'setAuthenticated')) { + $this->setAuthenticated(true); + } else { + $this->authenticated = true; + } + } + + public function getCredentials(): ?string + { + return null; + } +} + final class AuthenticatedTokenStub extends AbstractToken { + /** + * @var bool + */ + private $authenticated = false; + /** * @param UserInterface|\Stringable|string|null $user */ @@ -426,14 +553,22 @@ public function __construct($user) parent::__construct(); if (null !== $user) { + // @phpstan-ignore-next-line $this->setUser($user); } - if (method_exists($this, 'setAuthenticated')) { + if (version_compare(Kernel::VERSION, '5.4', '<') && method_exists($this, 'setAuthenticated')) { $this->setAuthenticated(true); + } else { + $this->authenticated = true; } } + public function isAuthenticated(): bool + { + return $this->authenticated; + } + public function getCredentials(): ?string { return null; diff --git a/tests/EventListener/TracingConsoleListenerTest.php b/tests/EventListener/TracingConsoleListenerTest.php index 3987ea04..b619dfe7 100644 --- a/tests/EventListener/TracingConsoleListenerTest.php +++ b/tests/EventListener/TracingConsoleListenerTest.php @@ -44,6 +44,9 @@ public function testHandleConsoleCommandEventStartsTransactionIfNoSpanIsSetOnHub $this->hub->expects($this->once()) ->method('startTransaction') ->with($this->callback(function (TransactionContext $context) use ($expectedTransactionContext): bool { + // This value is random when the metadata is constructed, thus we set it to a fixed expected value since we don't care for the value here + $context->getMetadata()->setSampleRand(0.1337); + $this->assertEquals($expectedTransactionContext, $context); return true; @@ -71,7 +74,9 @@ public function handleConsoleCommandEventStartsTransactionIfNoSpanIsSetOnHubData $transactionContext = new TransactionContext(); $transactionContext->setOp('console.command'); $transactionContext->setName(''); + $transactionContext->setOrigin('auto.console'); $transactionContext->setSource(TransactionSource::task()); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield [ new Command(), @@ -81,7 +86,9 @@ public function handleConsoleCommandEventStartsTransactionIfNoSpanIsSetOnHubData $transactionContext = new TransactionContext(); $transactionContext->setOp('console.command'); $transactionContext->setName('app:command'); + $transactionContext->setOrigin('auto.console'); $transactionContext->setSource(TransactionSource::task()); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield [ new Command('app:command'), diff --git a/tests/EventListener/TracingRequestListenerTest.php b/tests/EventListener/TracingRequestListenerTest.php index 739d1e11..6eb17581 100644 --- a/tests/EventListener/TracingRequestListenerTest.php +++ b/tests/EventListener/TracingRequestListenerTest.php @@ -65,6 +65,9 @@ public function testHandleKernelRequestEvent(Options $options, Request $request, $this->hub->expects($this->once()) ->method('startTransaction') ->with($this->callback(function (TransactionContext $context) use ($expectedTransactionContext): bool { + // This value is random when the metadata is constructed, thus we set it to a fixed expected value since we don't care for the value here + $context->getMetadata()->setSampleRand(0.1337); + $this->assertEquals($expectedTransactionContext, $context); return true; @@ -97,6 +100,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -107,6 +111,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'net.host.name' => 'www.example.com', ]); $transactionContext->getMetadata()->setDynamicSamplingContext($samplingContext); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.headers.sentry-trace EXISTS' => [ new Options(), @@ -134,6 +139,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -144,6 +150,8 @@ public function handleKernelRequestEventDataProvider(): \Generator 'net.host.name' => 'www.example.com', ]); $transactionContext->getMetadata()->setDynamicSamplingContext($samplingContext); + $transactionContext->getMetadata()->setParentSamplingRate(1.0); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.headers.sentry-trace and headers.baggage EXISTS' => [ new Options(), @@ -166,6 +174,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -175,6 +184,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => '', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); $request = Request::create('http://www.example.com'); $request->server->remove('REQUEST_TIME_FLOAT'); @@ -189,6 +199,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://127.0.0.1/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -198,6 +209,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => '', 'net.host.ip' => '127.0.0.1', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.server.HOST IS IPV4' => [ new Options(), @@ -220,6 +232,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET app_homepage'); $transactionContext->setSource(TransactionSource::route()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -229,6 +242,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => 'app_homepage', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes.route IS STRING' => [ new Options(), @@ -244,6 +258,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/path'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -253,6 +268,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => '/path', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes.route IS INSTANCEOF Symfony\Component\Routing\Route' => [ new Options(), @@ -268,6 +284,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -277,6 +294,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => 'App\\Controller::indexAction', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes._controller IS STRING' => [ new Options(), @@ -292,6 +310,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -301,6 +320,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => 'App\\Controller::indexAction', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes._controller IS CALLABLE (1)' => [ new Options(), @@ -310,12 +330,13 @@ public function handleKernelRequestEventDataProvider(): \Generator $request = Request::create('http://www.example.com/'); $request->server->set('REQUEST_TIME_FLOAT', 1613493597.010275); - $request->attributes->set('_controller', [new class() {}, 'indexAction']); + $request->attributes->set('_controller', [new class {}, 'indexAction']); $transactionContext = new TransactionContext(); $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -325,6 +346,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => 'class@anonymous::indexAction', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes._controller IS CALLABLE (2)' => [ new Options(), @@ -340,6 +362,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -349,6 +372,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => '', 'net.host.name' => 'www.example.com', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.attributes._controller IS ARRAY and NOT VALID CALLABLE' => [ new Options(), @@ -364,6 +388,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://www.example.com/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '80', @@ -374,6 +399,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'net.host.name' => 'www.example.com', 'net.peer.ip' => '127.0.0.1', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.server.REMOTE_ADDR EXISTS and client.options.send_default_pii = TRUE' => [ new Options(['send_default_pii' => true]), @@ -388,6 +414,7 @@ public function handleKernelRequestEventDataProvider(): \Generator $transactionContext->setName('GET http://:/'); $transactionContext->setSource(TransactionSource::url()); $transactionContext->setOp('http.server'); + $transactionContext->setOrigin('auto.http.server'); $transactionContext->setStartTimestamp(1613493597.010275); $transactionContext->setData([ 'net.host.port' => '', @@ -396,6 +423,7 @@ public function handleKernelRequestEventDataProvider(): \Generator 'route' => '', 'net.host.name' => '', ]); + $transactionContext->getMetadata()->setSampleRand(0.1337); yield 'request.server.SERVER_PROTOCOL NOT EXISTS' => [ new Options(), @@ -432,7 +460,6 @@ public function testHandleResponseRequestEvent(): void )); $this->assertSame(SpanStatus::ok(), $transaction->getStatus()); - $this->assertSame(['http.status_code' => '200'], $transaction->getTags()); } public function testHandleResponseRequestEventDoesNothingIfNoTransactionIsSetOnHub(): void diff --git a/tests/EventListener/TracingSubRequestListenerTest.php b/tests/EventListener/TracingSubRequestListenerTest.php index 614dfc1c..75effa05 100644 --- a/tests/EventListener/TracingSubRequestListenerTest.php +++ b/tests/EventListener/TracingSubRequestListenerTest.php @@ -104,7 +104,7 @@ public function handleKernelRequestEventDataProvider(): \Generator ]; $request = Request::create('http://www.example.com/'); - $request->attributes->set('_controller', [new class() {}, 'indexAction']); + $request->attributes->set('_controller', [new class {}, 'indexAction']); $span = new Span(); $span->setOp('http.server'); @@ -224,7 +224,6 @@ public function testHandleResponseRequestEvent(): void )); $this->assertSame(SpanStatus::ok(), $span->getStatus()); - $this->assertSame(['http.status_code' => '200'], $span->getTags()); } public function testHandleResponseRequestEventDoesNothingIfNoTransactionIsSetOnHub(): void diff --git a/tests/Integration/IntegrationConfiguratorTest.php b/tests/Integration/IntegrationConfiguratorTest.php index 44206b5c..4fd59339 100644 --- a/tests/Integration/IntegrationConfiguratorTest.php +++ b/tests/Integration/IntegrationConfiguratorTest.php @@ -50,7 +50,7 @@ public function integrationsDataProvider(): iterable $environmentIntegration = new EnvironmentIntegration(); $modulesIntegration = new ModulesIntegration(); - $userIntegration1 = new class() implements IntegrationInterface { + $userIntegration1 = new class implements IntegrationInterface { public function setupOnce(): void { } diff --git a/tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php b/tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php index 75583c8b..4b49194f 100644 --- a/tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php +++ b/tests/Tracing/Cache/AbstractTraceableCacheAdapterTest.php @@ -159,7 +159,7 @@ public function testGetThrowsExceptionIfDecoratedAdapterDoesNotImplementTheCache $adapter = $this->createCacheAdapter($this->createMock(static::getAdapterClassFqcn())); $this->expectException(\BadMethodCallException::class); - $this->expectExceptionMessage(sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "Symfony\\Contracts\\Cache\\CacheInterface" interface.', \get_class($adapter))); + $this->expectExceptionMessage(\sprintf('The %s::get() method is not supported because the decorated adapter does not implement the "Symfony\\Contracts\\Cache\\CacheInterface" interface.', \get_class($adapter))); $adapter->get('foo', static function () {}); } @@ -197,7 +197,7 @@ public function testDeleteThrowsExceptionIfDecoratedAdapterDoesNotImplementTheCa $adapter = $this->createCacheAdapter($this->createMock(static::getAdapterClassFqcn())); $this->expectException(\BadMethodCallException::class); - $this->expectExceptionMessage(sprintf('The %s::delete() method is not supported because the decorated adapter does not implement the "Symfony\\Contracts\\Cache\\CacheInterface" interface.', \get_class($adapter))); + $this->expectExceptionMessage(\sprintf('The %s::delete() method is not supported because the decorated adapter does not implement the "Symfony\\Contracts\\Cache\\CacheInterface" interface.', \get_class($adapter))); $adapter->delete('foo'); } diff --git a/tests/Tracing/Cache/TraceableCacheAdapterTest.php b/tests/Tracing/Cache/TraceableCacheAdapterTest.php index 1536adfd..5e5b9c29 100644 --- a/tests/Tracing/Cache/TraceableCacheAdapterTest.php +++ b/tests/Tracing/Cache/TraceableCacheAdapterTest.php @@ -6,6 +6,7 @@ use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapter; use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; /** * @phpstan-extends AbstractTraceableCacheAdapterTest @@ -27,4 +28,16 @@ protected static function getAdapterClassFqcn(): string { return AdapterInterface::class; } + + public function testNamespacePoolImplementation(): void + { + if (!interface_exists(NamespacedPoolInterface::class)) { + $this->markTestSkipped('NamespacedPoolInterface does not exists.'); + } + + $decoratedAdapter = $this->createMock(static::getAdapterClassFqcn()); + $adapter = $this->createCacheAdapter($decoratedAdapter); + + static::assertInstanceOf(NamespacedPoolInterface::class, $adapter); + } } diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php index d8ce3581..3ad7ede2 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3Test.php @@ -418,7 +418,7 @@ public function testGetWrappedConnection(): void public function testGetNativeConnection(): void { - $nativeConnection = new class() { + $nativeConnection = new class { }; $decoratedConnection = $this->createMock(NativeDriverConnectionInterfaceStub::class); diff --git a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php index e1daefb7..77490bb3 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4Test.php @@ -412,7 +412,7 @@ public function testGetWrappedConnection(): void public function testGetNativeConnection(): void { - $nativeConnection = new class() { + $nativeConnection = new class { }; $decoratedConnection = $this->createMock(NativeDriverConnectionInterfaceStub::class); diff --git a/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php b/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php index f8cd4537..13342222 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php +++ b/tests/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnectionTest.php @@ -264,7 +264,7 @@ public function testGetWrappedConnection(): void public function testGetNativeConnection(): void { - $nativeConnection = new class() { + $nativeConnection = new class { }; $decoratedConnection = $this->createMock(NativeDriverConnectionInterfaceStub::class); diff --git a/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php b/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php index 382fb0aa..722b1c27 100644 --- a/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php +++ b/tests/Tracing/Doctrine/DBAL/TracingStatementForV3Test.php @@ -70,7 +70,7 @@ public function testBindParam(): void public function testBindParamForwardsLengthParamOnlyWhenExplicitlySet(): void { $variable = 'bar'; - $decoratedStatement = new class() implements Statement { + $decoratedStatement = new class implements Statement { /** * @var int */ diff --git a/tests/Tracing/HttpClient/TraceableHttpClientTest.php b/tests/Tracing/HttpClient/TraceableHttpClientTest.php index af4b6bdc..0b24516b 100644 --- a/tests/Tracing/HttpClient/TraceableHttpClientTest.php +++ b/tests/Tracing/HttpClient/TraceableHttpClientTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerAwareInterface; use Psr\Log\NullLogger; -use Sentry\Client; use Sentry\ClientInterface; use Sentry\Options; use Sentry\SentryBundle\Tracing\HttpClient\AbstractTraceableResponse; @@ -23,7 +22,7 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Transport\NullTransport; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -48,7 +47,7 @@ final class TraceableHttpClientTest extends TestCase public static function setUpBeforeClass(): void { - if (!self::isHttpClientPackageInstalled()) { + if (!class_exists(HttpClient::class)) { self::markTestSkipped('This test requires the "symfony/http-client" Composer package to be installed.'); } } @@ -66,8 +65,7 @@ public function testRequest(): void 'dsn' => 'http://public:secret@example.com/sentry/1', ]); $client = $this->createMock(ClientInterface::class); - $client - ->expects($this->once()) + $client->expects($this->once()) ->method('getOptions') ->willReturn($options); @@ -94,8 +92,9 @@ public function testRequest(): void $this->assertSame(200, $response->getStatusCode()); $this->assertSame('GET', $response->getInfo('http_method')); $this->assertSame('https://username:password@www.example.com/test-page?foo=bar#baz', $response->getInfo('url')); - $this->assertSame(['sentry-trace: ' . $spans[1]->toTraceparent()], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']); - $this->assertSame(['baggage: ' . $transaction->toBaggage()], $mockResponse->getRequestOptions()['normalized_headers']['baggage']); + $this->assertSame([\sprintf('sentry-trace: %s', $spans[1]->toTraceparent())], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']); + $this->assertSame([\sprintf('traceparent: %s', $spans[1]->toW3CTraceparent())], $mockResponse->getRequestOptions()['normalized_headers']['traceparent']); + $this->assertSame([\sprintf('baggage: %s', $transaction->toBaggage())], $mockResponse->getRequestOptions()['normalized_headers']['baggage']); $this->assertNotNull($transaction->getSpanRecorder()); $spans = $transaction->getSpanRecorder()->getSpans(); @@ -124,7 +123,7 @@ public function testRequestDoesNotContainTracingHeaders(): void { $options = new Options([ 'dsn' => 'http://public:secret@example.com/sentry/1', - 'trace_propagation_targets' => null, + 'trace_propagation_targets' => [], ]); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -151,6 +150,7 @@ public function testRequestDoesNotContainTracingHeaders(): void $this->assertSame('PUT', $response->getInfo('http_method')); $this->assertSame('https://www.example.com/test-page', $response->getInfo('url')); $this->assertArrayNotHasKey('sentry-trace', $mockResponse->getRequestOptions()['normalized_headers']); + $this->assertArrayNotHasKey('traceparent', $mockResponse->getRequestOptions()['normalized_headers']); $this->assertArrayNotHasKey('baggage', $mockResponse->getRequestOptions()['normalized_headers']); $this->assertNotNull($transaction->getSpanRecorder()); @@ -169,12 +169,16 @@ public function testRequestDoesNotContainTracingHeaders(): void public function testRequestDoesContainsTracingHeadersWithoutTransaction(): void { - $client = new Client(new Options([ + $options = new Options([ 'dsn' => 'http://public:secret@example.com/sentry/1', 'release' => '1.0.0', 'environment' => 'test', 'trace_propagation_targets' => ['www.example.com'], - ]), new NullTransport()); + ]); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->exactly(4)) + ->method('getOptions') + ->willReturn($options); $propagationContext = PropagationContext::fromDefaults(); $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); @@ -195,25 +199,28 @@ public function testRequestDoesContainsTracingHeadersWithoutTransaction(): void $this->assertSame(200, $response->getStatusCode()); $this->assertSame('POST', $response->getInfo('http_method')); $this->assertSame('https://www.example.com/test-page', $response->getInfo('url')); - $this->assertSame(['sentry-trace: 566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8'], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']); - $this->assertSame(['baggage: sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-public_key=public,sentry-release=1.0.0,sentry-environment=test'], $mockResponse->getRequestOptions()['normalized_headers']['baggage']); + $this->assertSame([\sprintf('sentry-trace: %s', $propagationContext->toTraceparent())], $mockResponse->getRequestOptions()['normalized_headers']['sentry-trace']); + $this->assertSame([\sprintf('baggage: %s', $propagationContext->toBaggage())], $mockResponse->getRequestOptions()['normalized_headers']['baggage']); } public function testRequestSetsUnknownErrorAsSpanStatusIfResponseStatusCodeIsUnavailable(): void { + $options = new Options([ + 'dsn' => 'http://public:secret@example.com/sentry/1', + ]); $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) + $client->expects($this->exactly(2)) ->method('getOptions') - ->willReturn(new Options(['dsn' => 'http://public:secret@example.com/sentry/1'])); + ->willReturn($options); - $transaction = new Transaction(new TransactionContext()); + $transaction = new Transaction(new TransactionContext(), $this->hub); $transaction->initSpanRecorder(); $this->hub->expects($this->once()) ->method('getSpan') ->willReturn($transaction); - $this->hub->expects($this->once()) + $this->hub->expects($this->exactly(2)) ->method('getClient') ->willReturn($client); @@ -353,13 +360,10 @@ public function testWithOptions(): void $this->assertSame('GET', $response->getInfo('http_method')); $this->assertSame('https://www.example.org/test-page', $response->getInfo('url')); } +} - private static function isHttpClientPackageInstalled(): bool +if (interface_exists(HttpClientInterface::class)) { + interface TestableHttpClientInterface extends HttpClientInterface, LoggerAwareInterface, ResetInterface { - return interface_exists(HttpClientInterface::class); } } - -interface TestableHttpClientInterface extends HttpClientInterface, LoggerAwareInterface, ResetInterface -{ -} diff --git a/tests/Tracing/HttpClient/TraceableResponseTest.php b/tests/Tracing/HttpClient/TraceableResponseTest.php index e67616d5..5a4bb370 100644 --- a/tests/Tracing/HttpClient/TraceableResponseTest.php +++ b/tests/Tracing/HttpClient/TraceableResponseTest.php @@ -12,6 +12,7 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -28,6 +29,13 @@ final class TraceableResponseTest extends TestCase */ private $hub; + public static function setUpBeforeClass(): void + { + if (!class_exists(HttpClient::class)) { + self::markTestSkipped('This test requires the "symfony/http-client" Composer package to be installed.'); + } + } + protected function setUp(): void { $this->client = $this->createMock(HttpClientInterface::class); @@ -47,7 +55,7 @@ public function testInstanceCannotBeUnserialized(): void $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Unserializing instances of this class is forbidden.'); - unserialize(sprintf('O:%u:"%s":0:{}', \strlen(TraceableResponse::class), TraceableResponse::class)); + unserialize(\sprintf('O:%u:"%s":0:{}', \strlen(TraceableResponse::class), TraceableResponse::class)); } public function testDestructor(): void diff --git a/tests/Transport/TransportFactoryTest.php b/tests/Transport/TransportFactoryTest.php deleted file mode 100644 index 667590dc..00000000 --- a/tests/Transport/TransportFactoryTest.php +++ /dev/null @@ -1,100 +0,0 @@ -create(new Options(['dsn' => 'http://public@example.com/sentry/1'])); - - $this->assertInstanceOf(HttpTransport::class, $transport); - - try { - $transport->send(Event::createEvent())->wait(); - - $this->fail('Failed asserting that the transport returns a rejected promise on error.'); - } catch (RejectionException $exception) { - $this->assertInstanceOf(Response::class, $exception->getReason()); - } - } - - public function testCreateWithCustomFactories(): void - { - $uriFactory = $this->createMock(UriFactoryInterface::class); - $requestFactory = $this->createMock(RequestFactoryInterface::class); - $responseFactory = $this->createMock(ResponseFactoryInterface::class); - $streamFactory = $this->createMock(StreamFactoryInterface::class); - $httpClient = $this->createMock(HttpAsyncClientInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transportFactory = new TransportFactory( - $uriFactory, - $requestFactory, - $responseFactory, - $streamFactory, - $httpClient, - $logger - ); - - $requestFactory->expects($this->once()) - ->method('createRequest') - ->willReturnCallback(static function (...$arguments): RequestInterface { - return Psr17FactoryDiscovery::findRequestFactory()->createRequest(...$arguments); - }); - - $streamFactory->expects($this->atLeastOnce()) - ->method('createStream') - ->willReturnCallback(static function (...$arguments): StreamInterface { - return Psr17FactoryDiscovery::findStreamFactory()->createStream(...$arguments); - }); - - $httpClient->expects($this->once()) - ->method('sendAsyncRequest') - ->willReturnCallback(static function (RequestInterface $request): HttpPromiseInterface { - return new RejectedPromise(new NetworkException('foo', $request)); - }); - - $logger->expects($this->once()) - ->method('error') - ->withAnyParameters(); - - $event = Event::createEvent(); - $transport = $transportFactory->create(new Options(['dsn' => 'http://public@example.com/sentry/1', 'send_attempts' => 0])); - - try { - $transport->send($event)->wait(); - - $this->fail('Failed asserting that the transport returns a rejected promise on error.'); - } catch (RejectionException $exception) { - /** @var Response $response */ - $response = $exception->getReason(); - - $this->assertInstanceOf(Response::class, $response); - $this->assertSame(ResponseStatus::failed(), $response->getStatus()); - $this->assertSame($event, $response->getEvent()); - } - } -} diff --git a/tests/Twig/SentryExtensionTest.php b/tests/Twig/SentryExtensionTest.php index f5bf7c75..d0921ad1 100644 --- a/tests/Twig/SentryExtensionTest.php +++ b/tests/Twig/SentryExtensionTest.php @@ -5,7 +5,6 @@ namespace Sentry\SentryBundle\Tests\Twig; use PHPUnit\Framework\TestCase; -use Sentry\Client; use Sentry\ClientInterface; use Sentry\Options; use Sentry\SentryBundle\Twig\SentryExtension; @@ -17,7 +16,6 @@ use Sentry\Tracing\TraceId; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Transport\NullTransport; use Symfony\Bundle\TwigBundle\TwigBundle; use Twig\Environment; use Twig\Loader\ArrayLoader; @@ -68,10 +66,11 @@ public function testTraceMetaFunctionWithActiveSpan(): void $transaction = new Transaction(new TransactionContext()); $transaction->setTraceId(new TraceId('a3c01c41d7b94b90aee23edac90f4319')); $transaction->setSpanId(new SpanId('e69c2aef0ec34f2a')); + $transaction->setSampled(true); $hub->setSpan($transaction); - $this->assertSame('', $environment->render('foo.twig')); + $this->assertSame('', $environment->render('foo.twig')); } public function testBaggageMetaFunctionWithNoActiveSpan(): void @@ -95,7 +94,7 @@ public function testBaggageMetaFunctionWithNoActiveSpan(): void SentrySdk::setCurrentHub($hub); - $this->assertSame('', $environment->render('foo.twig')); + $this->assertSame(\sprintf('', $propagationContext->toBaggage()), $environment->render('foo.twig')); } public function testBaggageMetaFunctionWithActiveSpan(): void @@ -103,11 +102,14 @@ public function testBaggageMetaFunctionWithActiveSpan(): void $environment = new Environment(new ArrayLoader(['foo.twig' => '{{ sentry_baggage_meta() }}'])); $environment->addExtension(new SentryExtension()); - $client = new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'release' => '1.0.0', - 'environment' => 'development', - ]), new NullTransport()); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'release' => '1.0.0', + 'environment' => 'development', + ])); $hub = new Hub($client); @@ -118,7 +120,7 @@ public function testBaggageMetaFunctionWithActiveSpan(): void $hub->setSpan($transaction); - $this->assertSame('', $environment->render('foo.twig')); + $this->assertSame(\sprintf('', $transaction->toBaggage()), $environment->render('foo.twig')); } private static function isTwigBundlePackageInstalled(): bool