├── AUTHORS
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── UPGRADE-3.0.md
├── UPGRADE-4.0.md
├── UPGRADE-5.0.md
├── composer.json
├── psalm-baseline.xml
├── psalm.xml
├── scripts
└── bump-version.sh
└── src
├── Command
└── SentryTestCommand.php
├── DependencyInjection
├── Compiler
│ ├── AddLoginListenerTagPass.php
│ ├── CacheTracingPass.php
│ ├── DbalTracingPass.php
│ └── HttpClientTracingPass.php
├── Configuration.php
└── SentryExtension.php
├── ErrorTypesParser.php
├── EventListener
├── AbstractTracingRequestListener.php
├── ConsoleListener.php
├── ErrorListener.php
├── KernelEventForwardCompatibilityTrait.php
├── LoginListener.php
├── MessengerListener.php
├── RequestListener.php
├── SubRequestListener.php
├── TracingConsoleListener.php
├── TracingRequestListener.php
└── TracingSubRequestListener.php
├── Integration
├── IntegrationConfigurator.php
└── RequestFetcher.php
├── Resources
└── config
│ ├── schema
│ └── sentry-1.0.xsd
│ └── services.xml
├── SentryBundle.php
├── Tracing
├── Cache
│ ├── TraceableCacheAdapterForV2.php
│ ├── TraceableCacheAdapterForV3.php
│ ├── TraceableCacheAdapterTrait.php
│ ├── TraceableTagAwareCacheAdapterForV2.php
│ └── TraceableTagAwareCacheAdapterForV3.php
├── Doctrine
│ └── DBAL
│ │ ├── AbstractTracingStatement.php
│ │ ├── Compatibility
│ │ └── MiddlewareInterface.php
│ │ ├── ConnectionConfigurator.php
│ │ ├── TracingDriverConnectionFactoryForV2V3.php
│ │ ├── TracingDriverConnectionFactoryForV4.php
│ │ ├── TracingDriverConnectionFactoryInterface.php
│ │ ├── TracingDriverConnectionForV2V3.php
│ │ ├── TracingDriverConnectionForV4.php
│ │ ├── TracingDriverConnectionInterface.php
│ │ ├── TracingDriverForV2.php
│ │ ├── TracingDriverForV3.php
│ │ ├── TracingDriverForV4.php
│ │ ├── TracingDriverMiddleware.php
│ │ ├── TracingServerInfoAwareDriverConnection.php
│ │ ├── TracingStatementForV2.php
│ │ ├── TracingStatementForV3.php
│ │ └── TracingStatementForV4.php
├── HttpClient
│ ├── AbstractTraceableHttpClient.php
│ ├── AbstractTraceableResponse.php
│ ├── TraceableHttpClientForV4.php
│ ├── TraceableHttpClientForV5.php
│ ├── TraceableHttpClientForV6.php
│ ├── TraceableResponseForV4.php
│ ├── TraceableResponseForV5.php
│ └── TraceableResponseForV6.php
└── Twig
│ └── TwigTracingExtension.php
├── Twig
└── SentryExtension.php
└── aliases.php
/AUTHORS:
--------------------------------------------------------------------------------
1 | http://github.com/getsentry/sentry-symfony/contributors
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 5.2.0
4 |
5 | The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.2.0.
6 |
7 | ### Features
8 |
9 | - Allow to configure the logger via the `sentry.yaml` configuration file [(#899)](https://github.com/getsentry/sentry-symfony/pull/899)
10 |
11 | ```yaml
12 | sentry:
13 | dsn: "%env(SENTRY_DSN)%"
14 | options:
15 | logger: "sentry.logger"
16 |
17 | services:
18 | sentry.logger:
19 | class: 'Sentry\Logger\DebugFileLogger'
20 | arguments:
21 | $filePath: '../../var/log/sentry.log'
22 | ```
23 |
24 | ### Bug Fixes
25 |
26 | - Fixed updating the user context when a route is marked as stateless [(#910)](https://github.com/getsentry/sentry-symfony/pull/910)
27 |
28 | ### Misc
29 |
30 | - Remove `symfony/security-core` and `symfony/security-http` as dependencies [(#912)](https://github.com/getsentry/sentry-symfony/pull/912)
31 |
32 | ## 5.1.0
33 |
34 | The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.1.0.
35 |
36 | ### Features
37 |
38 | - The SDK was updated to support PHP 8.4 [(#893)](https://github.com/getsentry/sentry-symfony/pull/893)
39 | - Set the status for CLI command transactions based on the exit code [(#891)](https://github.com/getsentry/sentry-symfony/pull/891)
40 |
41 | ### Bug Fixes
42 |
43 | - Fix including request data on transactions [(#879)](https://github.com/getsentry/sentry-symfony/pull/879)
44 |
45 | ## 5.0.1
46 |
47 | The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v5.0.1.
48 |
49 | ### Bug Fixes
50 |
51 | - Add missing `setCallbackWrapper` method to `TraceableCacheAdapterTrait` [(#841)](https://github.com/getsentry/sentry-symfony/pull/841)
52 | - Fix detection of the `symfony/http-client` being installed [(#858)](https://github.com/getsentry/sentry-symfony/pull/858)
53 |
54 | ## 5.0.0
55 |
56 | The Sentry SDK team is thrilled to announce the immediate availability of Sentry Symfony SDK v5.0.0.
57 |
58 | ### Breaking Change
59 |
60 | 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.
61 |
62 | This version adds support for the underlying [Sentry PHP SDK v4.0](https://github.com/getsentry/sentry-php).
63 | 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.
64 |
65 | - This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry.
66 |
67 | If you are using [sentry.io](https://sentry.io), no action is needed.
68 | If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`.
69 |
70 | - You need to have `ext-curl` installed to use the SDK.
71 |
72 | - The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead.
73 | Previously, both `Symfony\Component\ErrorHandler\Error\FatalError` and `Symfony\Component\Debug\Exception\FatalErrorException` were ignored by default.
74 | To continue ignoring these exceptions, make the following changes to the config file:
75 |
76 | ```yaml
77 | // config/packages/sentry.yaml
78 |
79 | sentry:
80 | options:
81 | ignore_exceptions:
82 | - 'Symfony\Component\ErrorHandler\Error\FatalError'
83 | - 'Symfony\Component\Debug\Exception\FatalErrorException'
84 | ```
85 |
86 | 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.
87 |
88 | ### Features
89 |
90 | - Add support for Sentry Developer Metrics [(#1619)](https://github.com/getsentry/sentry-php/pull/1619)
91 |
92 | ```php
93 | use function Sentry\metrics;
94 |
95 | // Add 4 to a counter named hits
96 | metrics()->increment(key: 'hits', value: 4);
97 |
98 | // Add 25 to a distribution named response_time with unit milliseconds
99 | metrics()->distribution(key: 'response_time', value: 25, unit: MetricsUnit::millisecond());
100 |
101 | // Add 2 to gauge named parallel_requests, tagged with type: "a"
102 | metrics()->gauge(key: 'parallel_requests', value: 2, tags: ['type': 'a']);
103 |
104 | // Add a user's email to a set named users.sessions, tagged with role: "admin"
105 | metrics()->set('users.sessions', 'jane.doe@example.com', null, ['role' => User::admin()]);
106 | ```
107 |
108 | Metrics are automatically sent to Sentry at the end of a request, hooking into Symfony's `kernel.terminate` event.
109 |
110 | - Add new fluent APIs [(#1601)](https://github.com/getsentry/sentry-php/pull/1601)
111 |
112 | ```php
113 | // Before
114 | $transactionContext = new TransactionContext();
115 | $transactionContext->setName('GET /example');
116 | $transactionContext->setOp('http.server');
117 |
118 | // After
119 | $transactionContext = (new TransactionContext())
120 | ->setName('GET /example');
121 | ->setOp('http.server');
122 | ```
123 |
124 | - Simplify the breadcrumb API [(#1603)](https://github.com/getsentry/sentry-php/pull/1603)
125 |
126 | ```php
127 | // Before
128 | \Sentry\addBreadcrumb(
129 | new \Sentry\Breadcrumb(
130 | \Sentry\Breadcrumb::LEVEL_INFO,
131 | \Sentry\Breadcrumb::TYPE_DEFAULT,
132 | 'auth', // category
133 | 'User authenticated', // message (optional)
134 | ['user_id' => $userId] // data (optional)
135 | )
136 | );
137 |
138 | // After
139 | \Sentry\addBreadcrumb(
140 | category: 'auth',
141 | message: 'User authenticated', // optional
142 | metadata: ['user_id' => $userId], // optional
143 | level: Breadcrumb::LEVEL_INFO, // set by default
144 | type: Breadcrumb::TYPE_DEFAULT, // set by default
145 | );
146 | ```
147 |
148 | - New default cURL HTTP client [(#1589)](https://github.com/getsentry/sentry-php/pull/1589)
149 |
150 | The SDK now ships with its own HTTP client based on cURL. A few new options were added.
151 |
152 | ```yaml
153 | // config/packages/sentry.yaml
154 |
155 | sentry:
156 | options:
157 | - http_proxy_authentication: 'username:password' // user name and password to use for proxy authentication
158 | - http_ssl_verify_peer: false // default true, verify the peer's SSL certificate
159 | - http_compression: false // default true, http request body compression
160 | ```
161 |
162 | To use a different client, you may use the `http_client` option.
163 | To use a different transport, you may use the `transport` option. A custom transport must implement the `TransportInterface`.
164 | If you use the `transport` option, the `http_client` option has no effect.
165 |
166 | ### Misc
167 |
168 | - The abandoned package `php-http/message-factory` was removed.
169 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # Contributing to the Sentry SDK for Symfony
8 |
9 | We welcome contributions to `sentry-symfony` by the community.
10 |
11 | Please search the [issue tracker](https://github.com/getsentry/sentry-symfony/issues) before creating a new issue (a problem or an improvement request). Please also ask in our [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr) before submitting a new issue. There is a ton of great people in our Discord community ready to help you!
12 |
13 | If you feel that you can fix or implement it yourself, please read on to learn how to submit your changes.
14 |
15 | ## Submitting changes
16 |
17 | - Setup the development environment.
18 | - Clone the `sentry-symfony` repository and prepare necessary changes.
19 | - Add tests for your changes to `tests/`.
20 | - Run tests and make sure all of them pass.
21 | - Submit a pull request, referencing any issues it addresses.
22 |
23 | We will review your pull request as soon as possible.
24 | Thank you for contributing!
25 |
26 | ## Development environment
27 |
28 | ### Clone the repository
29 |
30 | ```bash
31 | git clone git@github.com:getsentry/sentry-symfony.git
32 | ```
33 |
34 | Make sure that you have PHP 7.2+ installed. Version 7.4 or higher is required to run style checkers. On macOS, we recommend using brew to install PHP. For Windows, we recommend an official PHP.net release.
35 |
36 | ### Install the dependencies
37 |
38 | Dependencies are managed through [Composer](https://getcomposer.org/):
39 |
40 | ```bash
41 | composer install
42 | ```
43 |
44 | ### Running tests
45 |
46 | Tests can then be run via [PHPUnit](https://phpunit.de/):
47 |
48 | ```bash
49 | vendor/bin/phpunit
50 | ```
51 |
52 | ## Releasing a new version
53 |
54 | (only relevant for Sentry employees)
55 |
56 | Prerequisites:
57 |
58 | - All changes that should be released must be in the `master` branch.
59 | - Every commit should follow the [Commit Message Format](https://develop.sentry.dev/commit-messages/#commit-message-format) convention.
60 |
61 | Manual Process:
62 |
63 | - Update CHANGELOG.md with the version to be released. Example commit: https://github.com/getsentry/sentry-symfony/commit/d1d2895c028676113cb191516b80b91abcde31f3.
64 | - On GitHub in the `sentry-symfony` repository go to "Actions" select the "Release" workflow.
65 | - Click on "Run workflow" on the right side and make sure the `master` branch is selected.
66 | - Set "Version to release" input field. Here you decide if it is a major, minor or patch release. (See "Versioning Policy" below)
67 | - Click "Run Workflow"
68 |
69 | This will trigger [Craft](https://github.com/getsentry/craft) to prepare everything needed for a release. (For more information see [craft prepare](https://github.com/getsentry/craft#craft-prepare-preparing-a-new-release)) At the end of this process, a release issue is created in the [Publish](https://github.com/getsentry/publish) repository. (Example release issue: https://github.com/getsentry/publish/issues/815)
70 |
71 | Now one of the persons with release privileges (most probably your engineering manager) will review this Issue and then add the `accepted` label to the issue.
72 |
73 | There are always two persons involved in a release.
74 |
75 | If you are in a hurry and the release should be out immediately there is a Slack channel called `#proj-release-approval` where you can see your release issue and where you can ping people to please have a look immediately.
76 |
77 | When the release issue is labeled `accepted` [Craft](https://github.com/getsentry/craft) is triggered again to publish the release to all the right platforms. (See [craft publish](https://github.com/getsentry/craft#craft-publish-publishing-the-release) for more information). At the end of this process, the release issue on GitHub will be closed and the release is completed! Congratulations!
78 |
79 | There is a sequence diagram visualizing all this in the [README.md](https://github.com/getsentry/publish) of the `Publish` repository.
80 |
81 | ### Versioning Policy
82 |
83 | This project follows [semver](https://semver.org/), with three additions:
84 |
85 | - Semver says that major version `0` can include breaking changes at any time. Still, it is common practice to assume that only `0.x` releases (minor versions) can contain breaking changes while `0.x.y` releases (patch versions) are used for backwards-compatible changes (bugfixes and features). This project also follows that practice.
86 |
87 | - All undocumented APIs are considered internal. They are not part of this contract.
88 |
89 | - Certain features (e.g. integrations) may be explicitly called out as "experimental" or "unstable" in the documentation. They come with their own versioning policy described in the documentation.
90 |
91 | We recommend pinning your version requirements against `1.x.*` or `1.x.y`.
92 | Either one of the following is fine:
93 |
94 | ```json
95 | "sentry/sentry": "^1.0",
96 | "sentry/sentry": "^1",
97 | ```
98 |
99 | A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bug tracker.
100 |
101 | ## Commit message format guidelines
102 |
103 | See the documentation on commit messages here:
104 |
105 | https://develop.sentry.dev/commit-messages/#commit-message-format
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Functional Software, Inc. dba Sentry
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_
8 |
9 | # Official Sentry SDK for Symfony
10 |
11 | [![Stable release][Last stable image]][Packagist link]
12 | [](https://packagist.org/packages/sentry/sentry-symfony)
13 | [](https://packagist.org/packages/sentry/sentry-symfony)
14 | [](https://packagist.org/packages/sentry/sentry-symfony)
15 |
16 | [](https://github.com/getsentry/sentry-symfony/actions/workflows/tests.yaml)
17 | [![Coverage Status][Master Code Coverage Image]][Master Code Coverage]
18 | [](https://discord.gg/cWnMQeA)
19 |
20 | This is the official Symfony SDK for [Sentry](https://getsentry.com/).
21 |
22 | ## Getting Started
23 |
24 | ### Install
25 |
26 | Install the SDK using [Composer](https://getcomposer.org/).
27 |
28 | ```bash
29 | composer require sentry/sentry-symfony
30 | ```
31 |
32 | ### Configure
33 |
34 | Add the [Sentry DSN](https://docs.sentry.io/quickstart/#configure-the-dsn) to your `.env` file.
35 |
36 | ```
37 | ###> sentry/sentry-symfony ###
38 | SENTRY_DSN="https://public@sentry.example.com/1"
39 | ###< sentry/sentry-symfony ###
40 | ```
41 |
42 | ### Usage
43 |
44 | ```php
45 | use function Sentry\captureException;
46 |
47 | try {
48 | $this->functionThatMayFail();
49 | } catch (\Throwable $exception) {
50 | captureException($exception);
51 | }
52 | ```
53 |
54 | ## Contributing to the SDK
55 |
56 | Please refer to [CONTRIBUTING.md](CONTRIBUTING.md).
57 |
58 | ### Thanks to all the people who contributed so far!
59 |
60 |
61 |
62 |
63 |
64 | ## Getting help/support
65 |
66 | 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!
67 |
68 | ## Resources
69 |
70 | - [](https://docs.sentry.io/quickstart/)
71 | - [](https://discord.gg/Ww9hbqr)
72 | - [](http://stackoverflow.com/questions/tagged/sentry)
73 | - [](https://twitter.com/intent/follow?screen_name=getsentry)
74 |
75 | ## License
76 |
77 | Licensed under the MIT license, see [`LICENSE`](LICENSE)
78 |
79 | [Last stable image]: https://poser.pugx.org/sentry/sentry-symfony/version.svg
80 | [Packagist link]: https://packagist.org/packages/sentry/sentry-symfony
81 | [Master Code Coverage]: https://codecov.io/gh/getsentry/sentry-symfony/branch/master
82 | [Master Code Coverage Image]: https://img.shields.io/codecov/c/github/getsentry/sentry-symfony/master?logo=codecov
83 |
84 |
--------------------------------------------------------------------------------
/UPGRADE-3.0.md:
--------------------------------------------------------------------------------
1 | # Upgrade to 3.0
2 | The 3.0 major release of this bundle has some major changes. This document will try to list them all, to help users
3 | during the upgrade path.
4 |
5 | ## Sentry SDK 2.0
6 | The major change is in the fact that we now require the `sentry/sdk` metapackage; this, in turn, requires the original
7 | `sentry/sentry` package, but at major version 2.
8 | This new version has been completely rewritten: if you use this bundle and you interact directly with the underlying SDK
9 | and client, you should read through the [relative upgrade document](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-2.0.md).
10 |
11 | ## Changes in the configuration
12 | There are only two BC impacting the configuration:
13 | * the `skip_capture` option has been **removed**: the same feature has been implemented in the new SDK, with the PHP `excluded_exceptions` option; you just have to move your values from `sentry.skip_capture` to `sentry.options.excluded_exceptions`
14 | * Symfony internal exceptions are no longer ignored by default: Sentry will start getting events for 404 or 403, or even 403 which are followed by a (remembered) login. Add new values to the `sentry.options.excluded_exceptions` option; notice that it works with an `instanceof` against the exception, so you could ignore multiple kind of events using a common ancestor class.
15 | * The `sentry.options` values reflect the options of the PHP SDK; many of those have been removed, and there are some new ones. You can read the [appropriate section of the upgrade document](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-2.0.md#client-options), with the exception of the `server` to `dsn` migration, which is still handled in the same way by the bundle, under `sentry.dsn`
16 | * A new notable option is `send_default_pii`: it's a new option of the new SDK with by default is turned off, for GDPR compliance. You need to set `sentry.options.send_default_pii: true` to have the user's username and IP address attached to the events, as in the 2.0 bundle.
17 |
18 | ## HTTPlug
19 | Since SDK 2.0 uses HTTPlug to remain transport-agnostic, you need to have installed two packages that provides
20 | [`php-http/async-client-implementation`](https://packagist.org/providers/php-http/async-client-implementation)
21 | and [`http-message-implementation`](https://packagist.org/providers/psr/http-message-implementation).
22 |
23 | The metapackage already solves this need, requiring the Curl client and Guzzle's message factories.
24 |
25 | If instead you want to use a different HTTP client or message factory, you'll need to require manually those additional
26 | packages:
27 |
28 | ```bash
29 | composer require sentry/sentry:^2.0 php-http/guzzle6-adapter guzzlehttp/psr7
30 | ```
31 |
32 | The `sentry/sentry` package is required directly to override `sentry/sdk`, and the other two packages are up to your choice;
33 | in the current example, we're using both Guzzle's components (client and message factory).
34 |
35 | ## Changes in the services
36 | Due to the SDK changes, and to follow newer Symfony best practices, the services exposed by the bundle are completely
37 | changed:
38 |
39 | * All services are now private; declare public aliases to access them if needed; you can still use the Sentry SDK global
40 | functions if you want just to capture messages manually without injecting Sentry services in your code
41 | * All services uses the full qualified name of their interfaces to name them
42 | * The `ExceptionListener` has been split and renamed: we now have a simpler `ErrorListener`, and three other listeners
43 | dedicated to enriching events of data (`RequestListener`, `SubRequestListener` and `ConsoleListener`)
44 | * The listeners are now `final`; append your own listeners to override their behavior
45 | * The `SentrySymfonyEvents::PRE_CAPTURE` and `SentrySymfonyEvents::SET_USER_CONTEXT` events are dropped; if you want to inject data into your events, write your own listener in a similar fashion to `RequestListener`, using the `Hub` and the `Scope` to handle how and when the new information is attached to events
46 | * The listeners are registered with a priority of `1`; they will run just before the default priority of `0`, to ease
47 | the registration of custom listener that will change `Scope` data
48 | * Configuration options of the bundle are now aligned with the new ones of the 2.0 SDK
49 |
50 | ## New services
51 | This is a brief description of the services registered by this bundle:
52 |
53 | * `Sentry\State\HubInterface`: this is the central root of the SDK; it's the `Hub` that the bundle will instantiate at
54 | startup, and the current one too if you do not change it
55 | * `Sentry\ClientInterface`: this is the proper client; compared to the 1.x SDK version it's a lot more slimmed down,
56 | since a lot of the stuff has been split in separated components, so you probably will not interact with it as much as
57 | in the past. You also have to remind you that the client is bound to the `Hub`, and has to be changed there if you want
58 | to use a different one automatically in error reporting
59 | * `Sentry\ClientBuilderInterface`: this is the factory that builds the client; you can call its methods to change all
60 | the settings and dependencies that will be injected in the latter created client. You can use this service to obtain more
61 | customized clients for your needs
62 | * `Sentry\Options`: this class holds all the configuration used in the client and other SDK components; it's populated
63 | starting from the bundle configuration
64 |
--------------------------------------------------------------------------------
/UPGRADE-4.0.md:
--------------------------------------------------------------------------------
1 | # Upgrade 3.x to 4.0
2 |
3 | - Bumped the required version of `sentry/sentry` to `^3.0`: this version relies on the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/); if you are
4 | 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. For further details read the [UPGRADE-3.0.md](https://github.com/getsentry/sentry-php/blob/master/UPGRADE-3.0.md) document of `sentry/sentry`.
5 | - Added the `$hub` argument to the constructor of the `SubRequestListener` class.
6 | - Renamed the `ConsoleListener` class to `ConsoleCommandListener`.
7 | - Renamed the `ConsoleListener::onConsoleCommand` method to `ConsoleCommandListener::handleConsoleCommandEvent`.
8 | - Renamed the `ErrorListener::onException` method to `ErrorListener::handleExceptionEvent`.
9 | - Removed the `ErrorListener::onKernelException` method.
10 | - Removed the `ErrorListener::onConsoleError` method.
11 | - Renamed the `RequestListener::onKernelRequest` method to `RequestListener::handleKernelRequestEvent`.
12 | - Renamed the `RequestListener::onKernelController` method to `RequestListener::handleKernelControllerEvent`.
13 | - Renamed the `SubRequestListener::onKernelRequest` method to `SubRequestListener::handleKernelRequestEvent`.
14 | - Renamed the `SubRequestListener::onKernelFinishRequest` method to `SubRequestListener::handleKernelFinishRequestEvent`.
15 | - Removed the `Sentry\FlushableClientInterface` service alias.
16 | - Removed the `sentry.listener_priorities` configuration option.
17 |
18 | Before:
19 |
20 | ```yaml
21 | sentry:
22 | listener_priorities:
23 | request: 10
24 | ```
25 |
26 | After:
27 |
28 | ```php
29 | use Sentry\SentryBundle\EventListener\RequestListener;
30 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
31 | use Symfony\Component\DependencyInjection\ContainerBuilder;
32 | use Symfony\Component\HttpKernel\KernelEvents;
33 |
34 | final class ChangeSentryListenerPriorityPass implements CompilerPassInterface
35 | {
36 | public function process(ContainerBuilder $container)
37 | {
38 | $definition = $container->getDefinition(RequestListener::class);
39 | $definitionTags = $definition->getTags();
40 |
41 | foreach ($definitionTags['kernel.event_listener'] as &$tags) {
42 | if (KernelEvents::REQUEST === $tags['event']) {
43 | $tags['priority'] = 10;
44 | }
45 | }
46 |
47 | $definition->setTags($definitionTags);
48 | }
49 | }
50 | ```
51 |
52 | - Removed the `sentry.options.excluded_exceptions` configuration option.
53 |
54 | Before:
55 |
56 | ```yaml
57 | sentry:
58 | options:
59 | excluded_exceptions:
60 | - RuntimeException
61 | ```
62 |
63 | After:
64 |
65 | ```yaml
66 | sentry:
67 | options:
68 | integrations:
69 | - 'Sentry\Integration\IgnoreErrorsIntegration'
70 |
71 | services:
72 | Sentry\Integration\IgnoreErrorsIntegration:
73 | arguments:
74 | $options:
75 | ignore_exceptions:
76 | - RuntimeException
77 | ```
78 |
79 | - Changed the priority of the `ConsoleCommandListener::handleConsoleErrorEvent` listener to `-64`.
80 | - Changed the priority of the `ConsoleCommandListener::::handleConsoleCommandEvent` listener to `128`.
81 | - Changed the priority of the `MessengerListener::handleWorkerMessageFailedEvent` listener to `50`.
82 | - Changed the priority of the `RequestListener::handleKernelRequestEvent` listener to `5`.
83 | - Changed the priority of the `RequestListener::handleKernelControllerEvent` listener to `10`.
84 | - Changed the priority of the `SubRequestListener::handleKernelRequestEvent` listener to `3`.
85 | - Changed the priority of the `SubRequestListener::handleKernelFinishRequestEvent` listener to `5`.
86 | - Changed the type of the `sentry.options.before_send` configuration option from `scalar` to `string`. The value must always be the name of the container service to call without the `@` prefix.
87 |
88 | Before
89 |
90 | ```yaml
91 | sentry:
92 | options:
93 | before_send: '@app.sentry.before_send'
94 | ```
95 |
96 | ```yaml
97 | sentry:
98 | options:
99 | before_send: 'App\Sentry\BeforeSend::__invoke'
100 | ```
101 |
102 | ```yaml
103 | sentry:
104 | options:
105 | before_send: ['App\Sentry\BeforeSend', '__invoke']
106 | ```
107 |
108 | After
109 |
110 | ```yaml
111 | sentry:
112 | options:
113 | before_send: 'app.sentry.before_send'
114 | ```
115 |
116 | - Changed the type of the `sentry.options.before_breadcrumb` configuration option from `scalar` to `string`. The value must always be the name of the container service to call without the `@` prefix.
117 |
118 | Before
119 |
120 | ```yaml
121 | sentry:
122 | options:
123 | before_breadcrumb: '@app.sentry.before_breadcrumb'
124 | ```
125 |
126 | ```yaml
127 | sentry:
128 | options:
129 | before_breadcrumb: 'App\Sentry\BeforeBreadcrumb::__invoke'
130 | ```
131 |
132 | ```yaml
133 | sentry:
134 | options:
135 | before_breadcrumb: ['App\Sentry\BeforeBreadcrumb', '__invoke']
136 | ```
137 |
138 | After
139 |
140 | ```yaml
141 | sentry:
142 | options:
143 | before_breadcrumb: 'app.sentry.before_breadcrumb'
144 | ```
145 |
146 | - Changed the type of the `sentry.options.class_serializers` configuration option from an array of `scalar` values to an array of `string` values. The value must always be the name of the container service to call without the `@` prefix.
147 |
148 | Before
149 |
150 | ```yaml
151 | sentry:
152 | options:
153 | class_serializers:
154 | App\FooClass: '@app.sentry.foo_class_serializer'
155 | ```
156 |
157 | ```yaml
158 | sentry:
159 | options:
160 | class_serializers:
161 | App\FooClass: 'App\Sentry\FooClassSerializer::__invoke'
162 | ```
163 |
164 | ```yaml
165 | sentry:
166 | options:
167 | class_serializers:
168 | App\FooClass: ['App\Sentry\FooClassSerializer', 'invoke']
169 | ```
170 |
171 | After
172 |
173 | ```yaml
174 | sentry:
175 | options:
176 | class_serializers:
177 | App\FooClass: 'app.sentry.foo_class_serializer'
178 | ```
179 |
180 | - Changed the type of the `sentry.options.integrations` configuration option from an array of `scalar` values to an array of `string` values. The value must always be the name of the container service to call without the `@` prefix.
181 |
182 | Before
183 |
184 | ```yaml
185 | sentry:
186 | options:
187 | integrations:
188 | - '@app.sentry.foo_integration'
189 | ```
190 |
191 | After
192 |
193 | ```yaml
194 | sentry:
195 | options:
196 | integrations:
197 | - 'app.sentry.foo_integration'
198 | ```
199 |
200 | - Removed the `ClientBuilderConfigurator` class.
201 | - Removed the `SentryBundle::getSdkVersion()` method.
202 | - Removed the `SentryBundle::getCurrentHub()` method, use `SentrySdk::getCurrentHub()` instead.
203 | - Removed the `Sentry\ClientBuilderInterface` and `Sentry\Options` services.
204 | - Refactorized the `ErrorTypesParser` class and made it `@internal`.
205 | - Removed the `sentry.monolog` configuration option.
206 |
207 | Before
208 |
209 | ```yaml
210 | sentry:
211 | monolog:
212 | level: !php/const Monolog\Logger::ERROR
213 | bubble: false
214 | error_handler:
215 | enabled: true
216 | ```
217 |
218 | After
219 |
220 | ```yaml
221 | services:
222 | Sentry\Monolog\Handler:
223 | arguments:
224 | $hub: '@Sentry\State\HubInterface'
225 | $level: !php/const Monolog\Logger::ERROR
226 | $bubble: false
227 | ```
228 |
--------------------------------------------------------------------------------
/UPGRADE-5.0.md:
--------------------------------------------------------------------------------
1 | # Upgrade 4.x to 5.0
2 |
3 | This version adds support for the underlying [Sentry PHP SDK v4.0](https://github.com/getsentry/sentry-php).
4 | 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.
5 |
6 | - This version exclusively uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/) to send event data to Sentry.
7 |
8 | If you are using [sentry.io](https://sentry.io), no action is needed.
9 | If you are using an on-premise/self-hosted installation of Sentry, the minimum requirement is now version `>= v20.6.0`.
10 |
11 | - You need to have `ext-curl` installed to use the SDK.
12 |
13 | - The `IgnoreErrorsIntegration` integration was removed. Use the `ignore_exceptions` option instead.
14 | Previously, both `Symfony\Component\ErrorHandler\Error\FatalError` and `Symfony\Component\Debug\Exception\FatalErrorException` were ignored by default.
15 | To continue ignoring these exceptions, make the following changes to your `config/packages/sentry.yaml` file:
16 |
17 | ```yaml
18 | // config/packages/sentry.yaml
19 |
20 | sentry:
21 | options:
22 | ignore_exceptions:
23 | - 'Symfony\Component\ErrorHandler\Error\FatalError'
24 | - 'Symfony\Component\Debug\Exception\FatalErrorException'
25 | ```
26 |
27 | 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.
28 |
29 | - Removed support for `guzzlehttp/psr7: ^1.8.4`.
30 |
31 | - The `RequestFetcher` now relies on `guzzlehttp/psr7: ^2.1.1`.
32 |
33 | - Continue traces from the W3C `traceparent` request header.
34 | - Inject the W3C `traceparent` header on outgoing HTTP client calls.
35 | - Added `Sentry\SentryBundle\Twig\SentryExtension::getW3CTraceMeta()`.
36 |
37 | - 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 `[]`.
38 |
39 | - Added the `sentry.options.enable_tracing` option.
40 | - Added the `sentry.options.attach_metric_code_locations` option.
41 | - Added the `sentry.options.spotlight` option.
42 | - Added the `sentry.options.spotlight_url` option.
43 | - Added the `sentry.options.transport` option.
44 | - Added the `sentry.options.http_client` option.
45 | - Added the `sentry.options.http_proxy_authentication` option.
46 | - Added the `sentry.options.http_ssl_verify_peer` option.
47 | - Added the `sentry.options.http_compression` option.
48 |
49 | - Removed the `sentry.transport_factory` option. Use `sentry.options.transport` to use a custom transport.
50 | - Removed the `sentry.options.send_attempts` option. You may use a custom transport if you rely on this behaviour.
51 | - Removed the `sentry.options.enable_compression` option. Use `sentry.options.http_compression` instead.
52 |
53 | - Removed `Sentry\SentryBundle\Transport\TransportFactory`.
54 | - Removed `Sentry\State\HubInterface\Sentry\State\HubInterface`.
55 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sentry/sentry-symfony",
3 | "type": "symfony-bundle",
4 | "description": "Symfony integration for Sentry (http://getsentry.com)",
5 | "keywords": ["logging", "errors", "symfony", "sentry"],
6 | "homepage": "http://getsentry.com",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Sentry",
11 | "email": "accounts@sentry.io"
12 | }
13 | ],
14 | "require": {
15 | "php": "^7.2||^8.0",
16 | "guzzlehttp/psr7": "^2.1.1",
17 | "jean85/pretty-package-versions": "^1.5||^2.0",
18 | "sentry/sentry": "^4.11.0",
19 | "symfony/cache-contracts": "^1.1||^2.4||^3.0",
20 | "symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0",
21 | "symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0",
22 | "symfony/dependency-injection": "^4.4.20||^5.0.11||^6.0||^7.0",
23 | "symfony/event-dispatcher": "^4.4.20||^5.0.11||^6.0||^7.0",
24 | "symfony/http-kernel": "^4.4.20||^5.0.11||^6.0||^7.0",
25 | "symfony/polyfill-php80": "^1.22",
26 | "symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0"
27 | },
28 | "require-dev": {
29 | "doctrine/dbal": "^2.13||^3.3||^4.0",
30 | "doctrine/doctrine-bundle": "^2.6",
31 | "friendsofphp/php-cs-fixer": "^2.19||^3.40",
32 | "masterminds/html5": "^2.8",
33 | "phpstan/extension-installer": "^1.0",
34 | "phpstan/phpstan": "1.12.5",
35 | "phpstan/phpstan-phpunit": "1.4.0",
36 | "phpstan/phpstan-symfony": "1.4.10",
37 | "phpunit/phpunit": "^8.5.40||^9.6.21",
38 | "symfony/browser-kit": "^4.4.20||^5.0.11||^6.0||^7.0",
39 | "symfony/cache": "^4.4.20||^5.0.11||^6.0||^7.0",
40 | "symfony/dom-crawler": "^4.4.20||^5.0.11||^6.0||^7.0",
41 | "symfony/framework-bundle": "^4.4.20||^5.0.11||^6.0||^7.0",
42 | "symfony/http-client": "^4.4.20||^5.0.11||^6.0||^7.0",
43 | "symfony/messenger": "^4.4.20||^5.0.11||^6.0||^7.0",
44 | "symfony/monolog-bundle": "^3.4",
45 | "symfony/phpunit-bridge": "^5.2.6||^6.0||^7.0",
46 | "symfony/process": "^4.4.20||^5.0.11||^6.0||^7.0",
47 | "symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0",
48 | "symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0",
49 | "symfony/twig-bundle": "^4.4.20||^5.0.11||^6.0||^7.0",
50 | "symfony/yaml": "^4.4.20||^5.0.11||^6.0||^7.0",
51 | "vimeo/psalm": "^4.3||^5.16.0"
52 | },
53 | "suggest": {
54 | "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.",
55 | "doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry.",
56 | "symfony/twig-bundle": "Allow distributed tracing of Twig template rendering using Sentry.",
57 | "symfony/cache": "Allow distributed tracing of cache pools using Sentry."
58 | },
59 | "autoload": {
60 | "files": [
61 | "src/aliases.php"
62 | ],
63 | "psr-4": {
64 | "Sentry\\SentryBundle\\": "src/"
65 | }
66 | },
67 | "autoload-dev": {
68 | "psr-4": {
69 | "Sentry\\SentryBundle\\Tests\\": "tests/"
70 | }
71 | },
72 | "scripts": {
73 | "check": [
74 | "@cs-check",
75 | "@phpstan",
76 | "@psalm",
77 | "@tests"
78 | ],
79 | "tests": "vendor/bin/phpunit --verbose",
80 | "cs-check": "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run",
81 | "cs-fix": "vendor/bin/php-cs-fixer fix --verbose --diff",
82 | "phpstan": "vendor/bin/phpstan analyse",
83 | "psalm": "vendor/bin/psalm"
84 | },
85 | "config": {
86 | "sort-packages": true,
87 | "allow-plugins": {
88 | "composer/package-versions-deprecated": true,
89 | "phpstan/extension-installer": true
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/psalm-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | getServerVersion())]]>
39 |
40 |
41 |
42 | getDatabasePlatform($versionProvider)]]>
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/scripts/bump-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eux
3 |
4 | # if [ "$(uname -s)" != "Linux" ]; then
5 | # echo "Please use the GitHub Action."
6 | # exit 1
7 | # fi
8 |
9 | SCRIPT_DIR="$( dirname "$0" )"
10 | cd $SCRIPT_DIR/..
11 |
12 | OLD_VERSION="${1}"
13 | NEW_VERSION="${2}"
14 |
15 | echo "Current version: $OLD_VERSION"
16 | echo "Bumping version: $NEW_VERSION"
17 |
18 | function replace() {
19 | ! grep "$2" $3
20 | perl -i -pe "s/$1/$2/g" $3
21 | grep "$2" $3 # verify that replacement was successful
22 | }
23 |
24 | replace "SDK_VERSION = '[0-9.]+'" "SDK_VERSION = '$NEW_VERSION'" ./src/SentryBundle.php
25 |
--------------------------------------------------------------------------------
/src/Command/SentryTestCommand.php:
--------------------------------------------------------------------------------
1 | hub = $hub ?? SentrySdk::getCurrentHub();
32 | }
33 |
34 | protected function execute(InputInterface $input, OutputInterface $output): int
35 | {
36 | $client = $this->hub->getClient();
37 |
38 | if (null === $client) {
39 | $output->writeln('No client found');
40 | $output->writeln('Your DSN is probably missing, check your configuration');
41 |
42 | return 1;
43 | }
44 |
45 | $dsn = $client->getOptions()->getDsn();
46 |
47 | if (null === $dsn) {
48 | $output->writeln('No DSN configured in the current client, please check your configuration');
49 | $output->writeln('To debug further, try bin/console debug:config sentry');
50 |
51 | return 1;
52 | }
53 |
54 | $output->writeln('DSN correctly configured in the current client');
55 | $output->writeln('Sending test message...');
56 |
57 | $eventId = $this->hub->captureMessage('This is a test message from the Sentry bundle');
58 |
59 | if (null === $eventId) {
60 | $output->writeln('Message not sent!');
61 | $output->writeln('Check your DSN or your before_send callback if used');
62 |
63 | return 1;
64 | }
65 |
66 | $output->writeln("Message sent successfully with ID $eventId");
67 |
68 | return 0;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/AddLoginListenerTagPass.php:
--------------------------------------------------------------------------------
1 | hasDefinition(LoginListener::class)) {
21 | return;
22 | }
23 | $listenerDefinition = $container->getDefinition(LoginListener::class);
24 |
25 | if (class_exists(LoginSuccessEvent::class)) {
26 | $listenerDefinition->addTag('kernel.event_listener', [
27 | 'event' => LoginSuccessEvent::class,
28 | 'method' => 'handleLoginSuccessEvent',
29 | ]);
30 | } elseif (class_exists(AuthenticationSuccessEvent::class)) {
31 | $listenerDefinition->addTag('kernel.event_listener', [
32 | 'event' => AuthenticationSuccessEvent::class,
33 | 'method' => 'handleAuthenticationSuccessEvent',
34 | ]);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/CacheTracingPass.php:
--------------------------------------------------------------------------------
1 | getParameter('sentry.tracing.cache.enabled')) {
22 | return;
23 | }
24 |
25 | foreach ($container->findTaggedServiceIds('cache.pool') as $serviceId => $tags) {
26 | $cachePoolDefinition = $container->getDefinition($serviceId);
27 |
28 | if ($cachePoolDefinition->isAbstract()) {
29 | continue;
30 | }
31 |
32 | $definitionClass = $this->resolveDefinitionClass($container, $cachePoolDefinition);
33 |
34 | if (null === $definitionClass) {
35 | continue;
36 | }
37 |
38 | if (is_subclass_of($definitionClass, TagAwareAdapterInterface::class)) {
39 | $traceableCachePoolDefinition = new ChildDefinition('sentry.tracing.traceable_tag_aware_cache_adapter');
40 | } else {
41 | $traceableCachePoolDefinition = new ChildDefinition('sentry.tracing.traceable_cache_adapter');
42 | }
43 |
44 | $traceableCachePoolDefinition->setDecoratedService($serviceId);
45 | $traceableCachePoolDefinition->replaceArgument(1, new Reference($serviceId . '.traceable.inner'));
46 |
47 | $container->setDefinition($serviceId . '.traceable', $traceableCachePoolDefinition);
48 | }
49 | }
50 |
51 | private function resolveDefinitionClass(ContainerBuilder $container, Definition $definition): ?string
52 | {
53 | $class = $definition->getClass();
54 |
55 | while (null === $class && $definition instanceof ChildDefinition) {
56 | $definition = $container->findDefinition($definition->getParent());
57 | $class = $definition->getClass();
58 | }
59 |
60 | return $class;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/DbalTracingPass.php:
--------------------------------------------------------------------------------
1 | hasParameter('doctrine.connections')
29 | || !$container->getParameter('sentry.tracing.enabled')
30 | || !$container->getParameter('sentry.tracing.dbal.enabled')
31 | ) {
32 | return;
33 | }
34 |
35 | $this->assertRequiredDbalVersion();
36 |
37 | /** @var string[] $connectionsToTrace */
38 | $connectionsToTrace = $container->getParameter('sentry.tracing.dbal.connections');
39 |
40 | /** @var array $connections */
41 | $connections = $container->getParameter('doctrine.connections');
42 |
43 | if (empty($connectionsToTrace)) {
44 | $connectionsToTrace = array_keys($connections);
45 | }
46 |
47 | foreach ($connectionsToTrace as $connectionName) {
48 | if (!\in_array(\sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName), $connections, true)) {
49 | throw new \InvalidArgumentException(\sprintf('The Doctrine connection "%s" does not exists and cannot be instrumented.', $connectionName));
50 | }
51 |
52 | if (class_exists(Result::class)) {
53 | $this->configureConnectionForDoctrineDBALVersion3($container, $connectionName);
54 | } else {
55 | $this->configureConnectionForDoctrineDBALVersion2($container, $connectionName);
56 | }
57 | }
58 | }
59 |
60 | private function configureConnectionForDoctrineDBALVersion3(ContainerBuilder $container, string $connectionName): void
61 | {
62 | $tracingMiddlewareDefinition = $container->getDefinition(TracingDriverMiddleware::class);
63 | $tracingMiddlewareDefinition->addTag('doctrine.middleware', ['connection' => $connectionName]);
64 | }
65 |
66 | private function configureConnectionForDoctrineDBALVersion2(ContainerBuilder $container, string $connectionName): void
67 | {
68 | $connectionDefinition = $container->getDefinition(\sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName));
69 | $connectionDefinition->setConfigurator([new Reference(ConnectionConfigurator::class), 'configure']);
70 | }
71 |
72 | private function assertRequiredDbalVersion(): void
73 | {
74 | if (interface_exists(Result::class)) {
75 | // DBAL ^2.13
76 | return;
77 | }
78 |
79 | if (class_exists(Result::class)) {
80 | // DBAL ^3
81 | return;
82 | }
83 |
84 | throw new \LogicException('Tracing support cannot be enabled as the Doctrine DBAL 2.13+ package is not installed. Try running "composer require doctrine/dbal:^2.13".');
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/HttpClientTracingPass.php:
--------------------------------------------------------------------------------
1 | getParameter('sentry.tracing.enabled') || !$container->getParameter('sentry.tracing.http_client.enabled')) {
32 | return;
33 | }
34 |
35 | $decoratedService = $this->getDecoratedService($container);
36 |
37 | if (null === $decoratedService) {
38 | return;
39 | }
40 |
41 | $container->register(TraceableHttpClient::class, TraceableHttpClient::class)
42 | ->setArgument(0, new Reference(TraceableHttpClient::class . '.inner'))
43 | ->setArgument(1, new Reference(HubInterface::class))
44 | ->setDecoratedService($decoratedService[0], null, $decoratedService[1]);
45 | }
46 |
47 | /**
48 | * @return array{string, int}|null
49 | */
50 | private function getDecoratedService(ContainerBuilder $container): ?array
51 | {
52 | // Starting from Symfony 6.3, the raw HTTP client that serves as adapter
53 | // for the transport is registered as a separate service, so that the
54 | // scoped clients can inject it before any decoration is applied on them.
55 | // Since we need to access the full URL of the request, and such information
56 | // is available after the `ScopingHttpClient` class did its job, we have
57 | // to decorate such service. For more details, see https://github.com/symfony/symfony/pull/49513.
58 | if ($container->hasDefinition('http_client.transport')) {
59 | return ['http_client.transport', -15];
60 | }
61 |
62 | // On versions of Symfony prior to 6.3, when the mock client is in-use,
63 | // each HTTP client is decorated by referencing explicitly the innermost
64 | // service rather than by using the standard decoration feature. Hence,
65 | // we have to look for the specific names of those services, and decorate
66 | // them instead of the raw HTTP client.
67 | foreach (self::MOCK_HTTP_CLIENT_SERVICE_IDS as $httpClientServiceId) {
68 | if ($container->hasDefinition($httpClientServiceId)) {
69 | return [$httpClientServiceId, 15];
70 | }
71 | }
72 |
73 | if ($container->hasDefinition('http_client')) {
74 | return ['http_client', 15];
75 | }
76 |
77 | return null;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode()
31 | : $treeBuilder->root('sentry');
32 |
33 | $inAppExcludes = [
34 | '%kernel.cache_dir%',
35 | '%kernel.project_dir%/vendor',
36 | ];
37 |
38 | if (Kernel::VERSION_ID >= 50200) {
39 | $inAppExcludes[] = '%kernel.build_dir%';
40 | }
41 |
42 | $rootNode
43 | ->children()
44 | ->scalarNode('dsn')
45 | ->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.')
46 | ->end()
47 | ->booleanNode('register_error_listener')->defaultTrue()->end()
48 | ->booleanNode('register_error_handler')->defaultTrue()->end()
49 | ->scalarNode('logger')
50 | ->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.')
51 | ->defaultNull()
52 | ->end()
53 | ->arrayNode('options')
54 | ->addDefaultsIfNotSet()
55 | ->fixXmlConfig('integration')
56 | ->fixXmlConfig('trace_propagation_target')
57 | ->fixXmlConfig('tag')
58 | ->fixXmlConfig('class_serializer')
59 | ->fixXmlConfig('ignore_exception')
60 | ->fixXmlConfig('ignore_transaction')
61 | ->fixXmlConfig('prefix', 'prefixes')
62 | ->children()
63 | ->arrayNode('integrations')
64 | ->scalarPrototype()->end()
65 | ->end()
66 | ->booleanNode('default_integrations')->end()
67 | ->arrayNode('prefixes')
68 | ->defaultValue(array_merge(['%kernel.project_dir%'], array_filter(explode(\PATH_SEPARATOR, get_include_path() ?: ''))))
69 | ->scalarPrototype()->end()
70 | ->end()
71 | ->floatNode('sample_rate')
72 | ->min(0.0)
73 | ->max(1.0)
74 | ->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.')
75 | ->end()
76 | ->booleanNode('enable_tracing')->end()
77 | ->floatNode('traces_sample_rate')
78 | ->min(0.0)
79 | ->max(1.0)
80 | ->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.')
81 | ->end()
82 | ->scalarNode('traces_sampler')->end()
83 | ->floatNode('profiles_sample_rate')
84 | ->min(0.0)
85 | ->max(1.0)
86 | ->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')
87 | ->end()
88 | ->booleanNode('attach_stacktrace')->end()
89 | ->booleanNode('attach_metric_code_locations')->end()
90 | ->integerNode('context_lines')->min(0)->end()
91 | ->scalarNode('environment')
92 | ->cannotBeEmpty()
93 | ->defaultValue('%kernel.environment%')
94 | ->end()
95 | ->scalarNode('logger')->end()
96 | ->booleanNode('spotlight')->end()
97 | ->scalarNode('spotlight_url')->end()
98 | ->scalarNode('release')
99 | ->cannotBeEmpty()
100 | ->defaultValue('%env(default::SENTRY_RELEASE)%')
101 | ->end()
102 | ->scalarNode('server_name')->end()
103 | ->arrayNode('ignore_exceptions')
104 | ->scalarPrototype()->end()
105 | ->beforeNormalization()->castToArray()->end()
106 | ->end()
107 | ->arrayNode('ignore_transactions')
108 | ->scalarPrototype()->end()
109 | ->beforeNormalization()->castToArray()->end()
110 | ->end()
111 | ->scalarNode('before_send')->end()
112 | ->scalarNode('before_send_transaction')->end()
113 | ->scalarNode('before_send_check_in')->end()
114 | ->scalarNode('before_send_metrics')->end()
115 | ->variableNode('trace_propagation_targets')->end()
116 | ->arrayNode('tags')
117 | ->useAttributeAsKey('name')
118 | ->normalizeKeys(false)
119 | ->scalarPrototype()->end()
120 | ->end()
121 | ->scalarNode('error_types')
122 | ->beforeNormalization()
123 | ->always(\Closure::fromCallable([ErrorTypesParser::class, 'parse']))
124 | ->end()
125 | ->end()
126 | ->integerNode('max_breadcrumbs')
127 | ->min(0)
128 | ->max(Options::DEFAULT_MAX_BREADCRUMBS)
129 | ->end()
130 | ->variableNode('before_breadcrumb')->end()
131 | ->arrayNode('in_app_exclude')
132 | ->scalarPrototype()->end()
133 | ->beforeNormalization()->castToArray()->end()
134 | ->defaultValue($inAppExcludes)
135 | ->end()
136 | ->arrayNode('in_app_include')
137 | ->scalarPrototype()->end()
138 | ->beforeNormalization()->castToArray()->end()
139 | ->end()
140 | ->booleanNode('send_default_pii')->end()
141 | ->integerNode('max_value_length')->min(0)->end()
142 | ->scalarNode('transport')->end()
143 | ->scalarNode('http_client')->end()
144 | ->scalarNode('http_proxy')->end()
145 | ->scalarNode('http_proxy_authentication')->end()
146 | ->floatNode('http_connect_timeout')
147 | ->min(0)
148 | ->info('The maximum number of seconds to wait while trying to connect to a server. It works only when using the default transport.')
149 | ->end()
150 | ->floatNode('http_timeout')
151 | ->min(0)
152 | ->info('The maximum execution time for the request+response as a whole. It works only when using the default transport.')
153 | ->end()
154 | ->booleanNode('http_ssl_verify_peer')->end()
155 | ->booleanNode('http_compression')->end()
156 | ->booleanNode('capture_silenced_errors')->end()
157 | ->enumNode('max_request_body_size')
158 | ->values([
159 | 'none',
160 | 'small',
161 | 'medium',
162 | 'always',
163 | ])
164 | ->end()
165 | ->arrayNode('class_serializers')
166 | ->useAttributeAsKey('class')
167 | ->normalizeKeys(false)
168 | ->scalarPrototype()->end()
169 | ->end()
170 | ->end()
171 | ->end()
172 | ->end();
173 |
174 | $this->addMessengerSection($rootNode);
175 | $this->addDistributedTracingSection($rootNode);
176 |
177 | return $treeBuilder;
178 | }
179 |
180 | private function addMessengerSection(ArrayNodeDefinition $rootNode): void
181 | {
182 | $rootNode
183 | ->children()
184 | ->arrayNode('messenger')
185 | ->{interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
186 | ->children()
187 | ->booleanNode('capture_soft_fails')->defaultTrue()->end()
188 | ->end()
189 | ->end()
190 | ->end();
191 | }
192 |
193 | private function addDistributedTracingSection(ArrayNodeDefinition $rootNode): void
194 | {
195 | $rootNode
196 | ->children()
197 | ->arrayNode('tracing')
198 | ->canBeDisabled()
199 | ->addDefaultsIfNotSet()
200 | ->children()
201 | ->arrayNode('dbal')
202 | ->{class_exists(DoctrineBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
203 | ->fixXmlConfig('connection')
204 | ->children()
205 | ->arrayNode('connections')
206 | ->scalarPrototype()->end()
207 | ->end()
208 | ->end()
209 | ->end()
210 | ->arrayNode('twig')
211 | ->{class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
212 | ->end()
213 | ->arrayNode('cache')
214 | ->{class_exists(CacheItem::class) ? 'canBeDisabled' : 'canBeEnabled'}()
215 | ->end()
216 | ->arrayNode('http_client')
217 | ->{class_exists(HttpClient::class) ? 'canBeDisabled' : 'canBeEnabled'}()
218 | ->end()
219 | ->arrayNode('console')
220 | ->addDefaultsIfNotSet()
221 | ->fixXmlConfig('excluded_command')
222 | ->children()
223 | ->arrayNode('excluded_commands')
224 | ->scalarPrototype()->end()
225 | ->defaultValue(['messenger:consume'])
226 | ->end()
227 | ->end()
228 | ->end()
229 | ->end()
230 | ->end()
231 | ->end();
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/ErrorTypesParser.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
30 | }
31 |
32 | /**
33 | * This method is called once a response for the current HTTP request is
34 | * created, but before it is sent off to the client. Its use is mainly for
35 | * gathering information like the HTTP status code and attaching them as
36 | * tags of the span/transaction.
37 | *
38 | * @param ResponseEvent $event The event
39 | */
40 | public function handleKernelResponseEvent(ResponseEvent $event): void
41 | {
42 | $response = $event->getResponse();
43 | $span = $this->hub->getSpan();
44 |
45 | if (null === $span) {
46 | return;
47 | }
48 |
49 | $span->setHttpStatus($response->getStatusCode());
50 | }
51 |
52 | /**
53 | * Gets the name of the route or fallback to the controller FQCN if the
54 | * route is anonymous (e.g. a subrequest).
55 | *
56 | * @param Request $request The HTTP request
57 | */
58 | protected function getRouteName(Request $request): string
59 | {
60 | $route = $request->attributes->get('_route');
61 |
62 | if ($route instanceof Route) {
63 | $route = $route->getPath();
64 | }
65 |
66 | if (null === $route) {
67 | $route = $request->attributes->get('_controller');
68 |
69 | if (\is_array($route) && \is_callable($route, true)) {
70 | $route = \sprintf('%s::%s', \is_object($route[0]) ? get_debug_type($route[0]) : $route[0], $route[1]);
71 | }
72 | }
73 |
74 | return \is_string($route) ? $route : '';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/EventListener/ConsoleListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
44 | $this->captureErrors = $captureErrors;
45 | }
46 |
47 | /**
48 | * Handles the execution of a console command by pushing a new {@see Scope}.
49 | *
50 | * @param ConsoleCommandEvent $event The event
51 | */
52 | public function handleConsoleCommandEvent(ConsoleCommandEvent $event): void
53 | {
54 | $scope = $this->hub->pushScope();
55 | $command = $event->getCommand();
56 | $input = $event->getInput();
57 |
58 | if (null !== $command && null !== $command->getName()) {
59 | $scope->setTag('console.command', $command->getName());
60 | }
61 |
62 | if ($input instanceof ArgvInput) {
63 | $scope->setExtra('Full command', (string) $input);
64 | }
65 | }
66 |
67 | /**
68 | * Handles the termination of a console command by popping the {@see Scope}.
69 | *
70 | * @param ConsoleTerminateEvent $event The event
71 | */
72 | public function handleConsoleTerminateEvent(ConsoleTerminateEvent $event): void
73 | {
74 | $this->hub->popScope();
75 | }
76 |
77 | /**
78 | * Handles an error that happened while running a console command.
79 | *
80 | * @param ConsoleErrorEvent $event The event
81 | */
82 | public function handleConsoleErrorEvent(ConsoleErrorEvent $event): void
83 | {
84 | $this->hub->configureScope(function (Scope $scope) use ($event): void {
85 | $scope->setTag('console.command.exit_code', (string) $event->getExitCode());
86 |
87 | if ($this->captureErrors) {
88 | $hint = EventHint::fromArray([
89 | 'exception' => $event->getError(),
90 | 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
91 | ]);
92 |
93 | $this->hub->captureEvent(Event::createEvent(), $hint);
94 | }
95 | });
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/EventListener/ErrorListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
31 | }
32 |
33 | /**
34 | * Handles an exception that happened while running the application.
35 | *
36 | * @param ExceptionEvent $event The event
37 | */
38 | public function handleExceptionEvent(ExceptionEvent $event): void
39 | {
40 | $hint = EventHint::fromArray([
41 | 'exception' => $event->getThrowable(),
42 | 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
43 | ]);
44 |
45 | $this->hub->captureEvent(Event::createEvent(), $hint);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/EventListener/KernelEventForwardCompatibilityTrait.php:
--------------------------------------------------------------------------------
1 | isMainRequest()
20 | : $event->isMasterRequest()
21 | ;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/EventListener/LoginListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
41 | $this->tokenStorage = $tokenStorage;
42 | }
43 |
44 | /**
45 | * This method is called for each request handled by the framework and
46 | * fills the Sentry scope with information about the current user.
47 | */
48 | public function handleKernelRequestEvent(RequestEvent $event): void
49 | {
50 | if (
51 | null === $this->tokenStorage
52 | || !$this->isMainRequest($event)
53 | || $event->getRequest()->attributes->get('_stateless')
54 | ) {
55 | return;
56 | }
57 |
58 | $token = $this->tokenStorage->getToken();
59 |
60 | if (null !== $token) {
61 | $this->updateUserContext($token);
62 | }
63 | }
64 |
65 | /**
66 | * This method is called after authentication was fully successful. It allows
67 | * to set information like the username of the currently authenticated user
68 | * and of the impersonator, if any, on the Sentry's context.
69 | */
70 | public function handleLoginSuccessEvent(LoginSuccessEvent $event): void
71 | {
72 | $this->updateUserContext($event->getAuthenticatedToken());
73 | }
74 |
75 | /**
76 | * This method is called when an authentication provider authenticates the
77 | * user. It is the event closest to {@see LoginSuccessEvent} in versions of
78 | * the framework where it doesn't exist.
79 | */
80 | public function handleAuthenticationSuccessEvent(AuthenticationSuccessEvent $event): void
81 | {
82 | $this->updateUserContext($event->getAuthenticationToken());
83 | }
84 |
85 | private function updateUserContext(TokenInterface $token): void
86 | {
87 | if (!$this->isTokenAuthenticated($token)) {
88 | return;
89 | }
90 |
91 | $client = $this->hub->getClient();
92 |
93 | if (null === $client || !$client->getOptions()->shouldSendDefaultPii()) {
94 | return;
95 | }
96 |
97 | $this->hub->configureScope(function (Scope $scope) use ($token): void {
98 | $user = $scope->getUser() ?? new UserDataBag();
99 |
100 | if (null === $user->getId()) {
101 | $user->setId($this->getUserIdentifier($token->getUser()));
102 | }
103 |
104 | $impersonatorUser = $this->getImpersonatorUser($token);
105 |
106 | if (null !== $impersonatorUser) {
107 | $user->setMetadata('impersonator_username', $impersonatorUser);
108 | }
109 |
110 | $scope->setUser($user);
111 | });
112 | }
113 |
114 | private function isTokenAuthenticated(TokenInterface $token): bool
115 | {
116 | if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(false)) {
117 | return false;
118 | }
119 |
120 | return null !== $token->getUser();
121 | }
122 |
123 | /**
124 | * @param UserInterface|\Stringable|string|null $user
125 | */
126 | private function getUserIdentifier($user): ?string
127 | {
128 | if ($user instanceof UserInterface) {
129 | if (method_exists($user, 'getUserIdentifier')) {
130 | return $user->getUserIdentifier();
131 | }
132 |
133 | if (method_exists($user, 'getUsername')) {
134 | return $user->getUsername();
135 | }
136 | }
137 |
138 | if (\is_string($user)) {
139 | return $user;
140 | }
141 |
142 | if (\is_object($user) && method_exists($user, '__toString')) {
143 | return (string) $user;
144 | }
145 |
146 | return null;
147 | }
148 |
149 | private function getImpersonatorUser(TokenInterface $token): ?string
150 | {
151 | if ($token instanceof SwitchUserToken) {
152 | return $this->getUserIdentifier($token->getOriginalToken()->getUser());
153 | }
154 |
155 | return null;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/EventListener/MessengerListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
41 | $this->captureSoftFails = $captureSoftFails;
42 | }
43 |
44 | /**
45 | * This method is called for each message that failed to be handled.
46 | *
47 | * @param WorkerMessageFailedEvent $event The event
48 | */
49 | public function handleWorkerMessageFailedEvent(WorkerMessageFailedEvent $event): void
50 | {
51 | if (!$this->captureSoftFails && $event->willRetry()) {
52 | return;
53 | }
54 |
55 | $this->hub->withScope(function (Scope $scope) use ($event): void {
56 | $envelope = $event->getEnvelope();
57 | $exception = $event->getThrowable();
58 |
59 | $scope->setTag('messenger.receiver_name', $event->getReceiverName());
60 | $scope->setTag('messenger.message_class', \get_class($envelope->getMessage()));
61 |
62 | /** @var BusNameStamp|null $messageBusStamp */
63 | $messageBusStamp = $envelope->last(BusNameStamp::class);
64 |
65 | if (null !== $messageBusStamp) {
66 | $scope->setTag('messenger.message_bus', $messageBusStamp->getBusName());
67 | }
68 |
69 | $this->captureException($exception, $event->willRetry());
70 | });
71 |
72 | $this->flushClient();
73 | }
74 |
75 | /**
76 | * This method is called for each handled message.
77 | *
78 | * @param WorkerMessageHandledEvent $event The event
79 | */
80 | public function handleWorkerMessageHandledEvent(WorkerMessageHandledEvent $event): void
81 | {
82 | // Flush normally happens at shutdown... which only happens in the worker if it is run with a lifecycle limit
83 | // such as --time=X or --limit=Y. Flush immediately in a background worker.
84 | $this->flushClient();
85 | }
86 |
87 | /**
88 | * Creates Sentry events from the given exception.
89 | *
90 | * Unpacks multiple exceptions wrapped in a HandlerFailedException and notifies
91 | * Sentry of each individual exception.
92 | *
93 | * If the message will be retried the exceptions will be marked as handled
94 | * in Sentry.
95 | */
96 | private function captureException(\Throwable $exception, bool $willRetry): void
97 | {
98 | if ($exception instanceof WrappedExceptionsInterface) {
99 | $exception = $exception->getWrappedExceptions();
100 | } elseif ($exception instanceof HandlerFailedException && method_exists($exception, 'getNestedExceptions')) {
101 | $exception = $exception->getNestedExceptions();
102 | } elseif ($exception instanceof DelayedMessageHandlingException && method_exists($exception, 'getExceptions')) {
103 | $exception = $exception->getExceptions();
104 | }
105 |
106 | if (\is_array($exception)) {
107 | foreach ($exception as $nestedException) {
108 | $this->captureException($nestedException, $willRetry);
109 | }
110 |
111 | return;
112 | }
113 |
114 | $hint = EventHint::fromArray([
115 | 'exception' => $exception,
116 | 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, $willRetry),
117 | ]);
118 |
119 | $this->hub->captureEvent(Event::createEvent(), $hint);
120 | }
121 |
122 | private function flushClient(): void
123 | {
124 | $client = $this->hub->getClient();
125 |
126 | if (null !== $client) {
127 | $client->flush();
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/EventListener/RequestListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
35 | }
36 |
37 | /**
38 | * This method is called for each request handled by the framework and
39 | * fills the Sentry scope with information about the current user.
40 | *
41 | * @param RequestEvent $event The event
42 | */
43 | public function handleKernelRequestEvent(RequestEvent $event): void
44 | {
45 | if (!$this->isMainRequest($event)) {
46 | return;
47 | }
48 |
49 | $client = $this->hub->getClient();
50 |
51 | if (null === $client || !$client->getOptions()->shouldSendDefaultPii()) {
52 | return;
53 | }
54 |
55 | $this->hub->configureScope(static function (Scope $scope) use ($event): void {
56 | $user = $scope->getUser() ?? new UserDataBag();
57 |
58 | if (null === $user->getIpAddress()) {
59 | $user->setIpAddress($event->getRequest()->getClientIp());
60 | }
61 |
62 | $scope->setUser($user);
63 | });
64 | }
65 |
66 | /**
67 | * This method is called for each request handled by the framework and
68 | * sets the route on the current Sentry scope.
69 | *
70 | * @param ControllerEvent $event The event
71 | */
72 | public function handleKernelControllerEvent(ControllerEvent $event): void
73 | {
74 | if (!$this->isMainRequest($event)) {
75 | return;
76 | }
77 |
78 | $route = $event->getRequest()->attributes->get('_route');
79 |
80 | if (!\is_string($route)) {
81 | return;
82 | }
83 |
84 | $this->hub->configureScope(static function (Scope $scope) use ($route): void {
85 | $scope->setTag('route', $route);
86 | });
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/EventListener/SubRequestListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
32 | }
33 |
34 | /**
35 | * This method is called for each subrequest handled by the framework and
36 | * pushes a new {@see \Sentry\State\Scope} onto the stack.
37 | *
38 | * @param RequestEvent $event The event
39 | */
40 | public function handleKernelRequestEvent(RequestEvent $event): void
41 | {
42 | if ($this->isMainRequest($event)) {
43 | return;
44 | }
45 |
46 | $this->hub->pushScope();
47 | }
48 |
49 | /**
50 | * This method is called for each subrequest handled by the framework and
51 | * pops a {@see \Sentry\State\Scope} from the stack.
52 | *
53 | * @param FinishRequestEvent $event The event
54 | */
55 | public function handleKernelFinishRequestEvent(FinishRequestEvent $event): void
56 | {
57 | if ($this->isMainRequest($event)) {
58 | return;
59 | }
60 |
61 | $this->hub->popScope();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/EventListener/TracingConsoleListener.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
43 | $this->excludedCommands = $excludedCommands;
44 | }
45 |
46 | /**
47 | * Handles the execution of a console command by starting a new {@see Transaction}
48 | * if it doesn't exists, or a child {@see Span} if it does.
49 | *
50 | * @param ConsoleCommandEvent $event The event
51 | */
52 | public function handleConsoleCommandEvent(ConsoleCommandEvent $event): void
53 | {
54 | $command = $event->getCommand();
55 |
56 | if ($this->isCommandExcluded($command)) {
57 | return;
58 | }
59 |
60 | $currentSpan = $this->hub->getSpan();
61 |
62 | if (null === $currentSpan) {
63 | $span = $this->hub->startTransaction(
64 | TransactionContext::make()
65 | ->setOp('console.command')
66 | ->setOrigin('auto.console')
67 | ->setName($this->getSpanName($command))
68 | ->setSource(TransactionSource::task())
69 | );
70 | } else {
71 | $span = $currentSpan->startChild(
72 | SpanContext::make()
73 | ->setOp('console.command')
74 | ->setOrigin('auto.console')
75 | ->setDescription($this->getSpanName($command))
76 | );
77 | }
78 |
79 | $this->hub->setSpan($span);
80 | }
81 |
82 | /**
83 | * Handles the termination of a console command by stopping the active {@see Span}
84 | * or {@see Transaction}.
85 | *
86 | * @param ConsoleTerminateEvent $event The event
87 | */
88 | public function handleConsoleTerminateEvent(ConsoleTerminateEvent $event): void
89 | {
90 | if ($this->isCommandExcluded($event->getCommand())) {
91 | return;
92 | }
93 |
94 | $span = $this->hub->getSpan();
95 |
96 | if (null !== $span) {
97 | $span->setStatus(0 === $event->getExitCode() ? SpanStatus::ok() : SpanStatus::internalError());
98 | $span->finish();
99 | }
100 | }
101 |
102 | private function getSpanName(?Command $command): string
103 | {
104 | if (null === $command || null === $command->getName()) {
105 | return '';
106 | }
107 |
108 | return $command->getName();
109 | }
110 |
111 | private function isCommandExcluded(?Command $command): bool
112 | {
113 | if (null === $command) {
114 | return true;
115 | }
116 |
117 | return \in_array($command->getName(), $this->excludedCommands, true);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/EventListener/TracingRequestListener.php:
--------------------------------------------------------------------------------
1 | requestFetcher = $requestFetcher;
36 | }
37 |
38 | /**
39 | * This method is called for each subrequest handled by the framework and
40 | * starts a new {@see Transaction}.
41 | *
42 | * @param RequestEvent $event The event
43 | */
44 | public function handleKernelRequestEvent(RequestEvent $event): void
45 | {
46 | if (!$this->isMainRequest($event)) {
47 | return;
48 | }
49 |
50 | /** @var Request $request */
51 | $request = $event->getRequest();
52 |
53 | if ($this->requestFetcher instanceof RequestFetcher) {
54 | $this->requestFetcher->setRequest($request);
55 | }
56 |
57 | /** @var float $requestStartTime */
58 | $requestStartTime = $request->server->get('REQUEST_TIME_FLOAT', microtime(true));
59 |
60 | $context = continueTrace(
61 | $request->headers->get('sentry-trace') ?? $request->headers->get('traceparent', ''),
62 | $request->headers->get('baggage', '')
63 | );
64 |
65 | $context->setOp('http.server');
66 | $context->setOrigin('auto.http.server');
67 |
68 | $routeName = $request->attributes->get('_route');
69 | if (null !== $routeName && \is_string($routeName)) {
70 | $context->setName(\sprintf('%s %s', $request->getMethod(), $routeName));
71 | $context->setSource(TransactionSource::route());
72 | } else {
73 | $context->setName(\sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo()));
74 | $context->setSource(TransactionSource::url());
75 | }
76 |
77 | $context->setStartTimestamp($requestStartTime);
78 | $context->setData($this->getData($request));
79 |
80 | $this->hub->setSpan($this->hub->startTransaction($context));
81 | }
82 |
83 | /**
84 | * This method is called for each request handled by the framework and
85 | * ends the tracing on terminate after the client received the response.
86 | *
87 | * @param TerminateEvent $event The event
88 | */
89 | public function handleKernelTerminateEvent(TerminateEvent $event): void
90 | {
91 | $transaction = $this->hub->getTransaction();
92 |
93 | if (null === $transaction) {
94 | return;
95 | }
96 |
97 | $transaction->finish();
98 | metrics()->flush();
99 |
100 | if ($this->requestFetcher instanceof RequestFetcher) {
101 | $this->requestFetcher->setRequest(null);
102 | }
103 | }
104 |
105 | /**
106 | * Gets the data to attach to the transaction.
107 | *
108 | * @param Request $request The HTTP request
109 | *
110 | * @return array
111 | */
112 | private function getData(Request $request): array
113 | {
114 | $client = $this->hub->getClient();
115 | $httpFlavor = $this->getHttpFlavor($request);
116 |
117 | $data = [
118 | 'net.host.port' => (string) $request->getPort(),
119 | 'http.request.method' => $request->getMethod(),
120 | 'http.url' => $request->getUri(),
121 | 'route' => $this->getRouteName($request),
122 | ];
123 |
124 | if (null !== $httpFlavor) {
125 | $data['http.flavor'] = $httpFlavor;
126 | }
127 |
128 | if (false !== filter_var($request->getHost(), \FILTER_VALIDATE_IP)) {
129 | $data['net.host.ip'] = $request->getHost();
130 | } else {
131 | $data['net.host.name'] = $request->getHost();
132 | }
133 |
134 | if (null !== $request->getClientIp() && null !== $client && $client->getOptions()->shouldSendDefaultPii()) {
135 | $data['net.peer.ip'] = $request->getClientIp();
136 | }
137 |
138 | return $data;
139 | }
140 |
141 | /**
142 | * Gets the HTTP flavor from the request.
143 | *
144 | * @param Request $request The HTTP request
145 | */
146 | private function getHttpFlavor(Request $request): ?string
147 | {
148 | $protocolVersion = $request->getProtocolVersion();
149 |
150 | if (null !== $protocolVersion && str_starts_with($protocolVersion, 'HTTP/')) {
151 | return substr($protocolVersion, \strlen('HTTP/'));
152 | }
153 |
154 | return $protocolVersion;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/EventListener/TracingSubRequestListener.php:
--------------------------------------------------------------------------------
1 | isMainRequest($event)) {
27 | return;
28 | }
29 |
30 | $request = $event->getRequest();
31 | $span = $this->hub->getSpan();
32 |
33 | if (null === $span) {
34 | return;
35 | }
36 |
37 | $this->hub->setSpan(
38 | $span->startChild(
39 | SpanContext::make()
40 | ->setOp('http.server')
41 | ->setData([
42 | 'http.request.method' => $request->getMethod(),
43 | 'http.url' => $request->getUri(),
44 | 'route' => $this->getRouteName($request),
45 | ])
46 | ->setOrigin('auto.http.server')
47 | ->setDescription(\sprintf('%s %s%s%s', $request->getMethod(), $request->getSchemeAndHttpHost(), $request->getBaseUrl(), $request->getPathInfo()))
48 | )
49 | );
50 | }
51 |
52 | /**
53 | * This method is called for each subrequest handled by the framework and
54 | * ends the tracing.
55 | *
56 | * @param FinishRequestEvent $event The event
57 | */
58 | public function handleKernelFinishRequestEvent(FinishRequestEvent $event): void
59 | {
60 | if ($this->isMainRequest($event)) {
61 | return;
62 | }
63 |
64 | $span = $this->hub->getSpan();
65 |
66 | if (null === $span) {
67 | return;
68 | }
69 |
70 | $span->finish();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Integration/IntegrationConfigurator.php:
--------------------------------------------------------------------------------
1 | true,
19 | ExceptionListenerIntegration::class => true,
20 | FatalErrorListenerIntegration::class => true,
21 | ];
22 |
23 | /**
24 | * @var IntegrationInterface[]
25 | */
26 | private $userIntegrations;
27 |
28 | /**
29 | * @var bool
30 | */
31 | private $registerErrorHandler;
32 |
33 | /**
34 | * @param IntegrationInterface[] $userIntegrations
35 | */
36 | public function __construct(array $userIntegrations, bool $registerErrorHandler)
37 | {
38 | $this->userIntegrations = $userIntegrations;
39 | $this->registerErrorHandler = $registerErrorHandler;
40 | }
41 |
42 | /**
43 | * @see IntegrationRegistry::getIntegrationsToSetup()
44 | *
45 | * @param IntegrationInterface[] $defaultIntegrations
46 | *
47 | * @return IntegrationInterface[]
48 | */
49 | public function __invoke(array $defaultIntegrations): array
50 | {
51 | $integrations = [];
52 |
53 | $userIntegrationsClasses = array_map('get_class', $this->userIntegrations);
54 | $pickedIntegrationsClasses = [];
55 |
56 | foreach ($defaultIntegrations as $defaultIntegration) {
57 | $integrationClassName = \get_class($defaultIntegration);
58 |
59 | if (!$this->registerErrorHandler && isset(self::ERROR_HANDLER_INTEGRATIONS[$integrationClassName])) {
60 | continue;
61 | }
62 |
63 | if (!\in_array($integrationClassName, $userIntegrationsClasses, true) && !isset($pickedIntegrationsClasses[$integrationClassName])) {
64 | $integrations[] = $defaultIntegration;
65 | $pickedIntegrationsClasses[$integrationClassName] = true;
66 | }
67 | }
68 |
69 | foreach ($this->userIntegrations as $userIntegration) {
70 | $integrationClassName = \get_class($userIntegration);
71 |
72 | if (!isset($pickedIntegrationsClasses[$integrationClassName])) {
73 | $integrations[] = $userIntegration;
74 | $pickedIntegrationsClasses[$integrationClassName] = true;
75 | }
76 | }
77 |
78 | return $integrations;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Integration/RequestFetcher.php:
--------------------------------------------------------------------------------
1 | requestStack = $requestStack;
46 | $this->httpMessageFactory = $httpMessageFactory ?? new PsrHttpFactory(
47 | new HttpFactory(),
48 | new HttpFactory(),
49 | new HttpFactory(),
50 | new HttpFactory()
51 | );
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function fetchRequest(): ?ServerRequestInterface
58 | {
59 | $request = $this->currentRequest ?? $this->requestStack->getCurrentRequest();
60 |
61 | if (null === $request) {
62 | return null;
63 | }
64 |
65 | try {
66 | return $this->httpMessageFactory->createRequest($request);
67 | } catch (\Throwable $exception) {
68 | return null;
69 | }
70 | }
71 |
72 | public function setRequest(?Request $request): void
73 | {
74 | $this->currentRequest = $request;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Resources/config/schema/sentry-1.0.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/src/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/src/SentryBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new DbalTracingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
26 | $container->addCompilerPass(new CacheTracingPass());
27 | $container->addCompilerPass(new HttpClientTracingPass());
28 | $container->addCompilerPass(new AddLoginListenerTagPass());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Tracing/Cache/TraceableCacheAdapterForV2.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | use TraceableCacheAdapterTrait;
25 |
26 | /**
27 | * @param HubInterface $hub The current hub
28 | * @param AdapterInterface $decoratedAdapter The decorated cache adapter
29 | */
30 | public function __construct(HubInterface $hub, AdapterInterface $decoratedAdapter)
31 | {
32 | $this->hub = $hub;
33 | $this->decoratedAdapter = $decoratedAdapter;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | *
39 | * @param mixed[] $metadata
40 | *
41 | * @return mixed
42 | */
43 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null)
44 | {
45 | return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) {
46 | if (!$this->decoratedAdapter instanceof CacheInterface) {
47 | 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));
48 | }
49 |
50 | return $this->decoratedAdapter->get($key, $callback, $beta, $metadata);
51 | }, $key);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Tracing/Cache/TraceableCacheAdapterForV3.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | use TraceableCacheAdapterTrait;
25 |
26 | /**
27 | * @param HubInterface $hub The current hub
28 | * @param AdapterInterface $decoratedAdapter The decorated cache adapter
29 | */
30 | public function __construct(HubInterface $hub, AdapterInterface $decoratedAdapter)
31 | {
32 | $this->hub = $hub;
33 | $this->decoratedAdapter = $decoratedAdapter;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | *
39 | * @param mixed[] $metadata
40 | */
41 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
42 | {
43 | return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) {
44 | if (!$this->decoratedAdapter instanceof CacheInterface) {
45 | 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));
46 | }
47 |
48 | return $this->decoratedAdapter->get($key, $callback, $beta, $metadata);
49 | }, $key);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Tracing/Cache/TraceableCacheAdapterTrait.php:
--------------------------------------------------------------------------------
1 | traceFunction('cache.get_item', function () use ($key): CacheItem {
42 | return $this->decoratedAdapter->getItem($key);
43 | }, $key);
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function getItems(array $keys = []): iterable
50 | {
51 | return $this->traceFunction('cache.get_items', function () use ($keys): iterable {
52 | return $this->decoratedAdapter->getItems($keys);
53 | });
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function clear(string $prefix = ''): bool
60 | {
61 | return $this->traceFunction('cache.clear', function () use ($prefix): bool {
62 | return $this->decoratedAdapter->clear($prefix);
63 | }, $prefix);
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function delete(string $key): bool
70 | {
71 | return $this->traceFunction('cache.delete_item', function () use ($key): bool {
72 | if (!$this->decoratedAdapter instanceof CacheInterface) {
73 | 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));
74 | }
75 |
76 | return $this->decoratedAdapter->delete($key);
77 | }, $key);
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function hasItem($key): bool
84 | {
85 | return $this->traceFunction('cache.has_item', function () use ($key): bool {
86 | return $this->decoratedAdapter->hasItem($key);
87 | }, $key);
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function deleteItem($key): bool
94 | {
95 | return $this->traceFunction('cache.delete_item', function () use ($key): bool {
96 | return $this->decoratedAdapter->deleteItem($key);
97 | }, $key);
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | public function deleteItems(array $keys): bool
104 | {
105 | return $this->traceFunction('cache.delete_items', function () use ($keys): bool {
106 | return $this->decoratedAdapter->deleteItems($keys);
107 | });
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function save(CacheItemInterface $item): bool
114 | {
115 | return $this->traceFunction('cache.save', function () use ($item): bool {
116 | return $this->decoratedAdapter->save($item);
117 | });
118 | }
119 |
120 | /**
121 | * {@inheritdoc}
122 | */
123 | public function saveDeferred(CacheItemInterface $item): bool
124 | {
125 | return $this->traceFunction('cache.save_deferred', function () use ($item): bool {
126 | return $this->decoratedAdapter->saveDeferred($item);
127 | });
128 | }
129 |
130 | /**
131 | * {@inheritdoc}
132 | */
133 | public function commit(): bool
134 | {
135 | return $this->traceFunction('cache.commit', function (): bool {
136 | return $this->decoratedAdapter->commit();
137 | });
138 | }
139 |
140 | /**
141 | * {@inheritdoc}
142 | */
143 | public function prune(): bool
144 | {
145 | return $this->traceFunction('cache.prune', function (): bool {
146 | if (!$this->decoratedAdapter instanceof PruneableInterface) {
147 | return false;
148 | }
149 |
150 | return $this->decoratedAdapter->prune();
151 | });
152 | }
153 |
154 | /**
155 | * {@inheritdoc}
156 | */
157 | public function reset(): void
158 | {
159 | if ($this->decoratedAdapter instanceof ResettableInterface) {
160 | $this->decoratedAdapter->reset();
161 | }
162 | }
163 |
164 | /**
165 | * @phpstan-template TResult
166 | *
167 | * @phpstan-param \Closure(): TResult $callback
168 | *
169 | * @phpstan-return TResult
170 | */
171 | private function traceFunction(string $spanOperation, \Closure $callback, ?string $spanDescription = null)
172 | {
173 | $span = $this->hub->getSpan();
174 |
175 | if (null !== $span) {
176 | $spanContext = SpanContext::make()
177 | ->setOp($spanOperation)
178 | ->setOrigin('auto.cache');
179 |
180 | if (null !== $spanDescription) {
181 | $spanContext->setDescription(urldecode($spanDescription));
182 | }
183 |
184 | $span = $span->startChild($spanContext);
185 | }
186 |
187 | try {
188 | return $callback();
189 | } finally {
190 | if (null !== $span) {
191 | $span->finish();
192 | }
193 | }
194 | }
195 |
196 | /**
197 | * @phpstan-param \Closure(CacheItem): CacheItem $callback
198 | * @phpstan-param string $key
199 | *
200 | * @phpstan-return callable(): CacheItem
201 | */
202 | private function setCallbackWrapper(callable $callback, string $key): callable
203 | {
204 | return function () use ($callback, $key): CacheItem {
205 | return $callback($this->decoratedAdapter->getItem($key));
206 | };
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV2.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | use TraceableCacheAdapterTrait;
26 |
27 | /**
28 | * @param HubInterface $hub The current hub
29 | * @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter
30 | */
31 | public function __construct(HubInterface $hub, TagAwareAdapterInterface $decoratedAdapter)
32 | {
33 | $this->hub = $hub;
34 | $this->decoratedAdapter = $decoratedAdapter;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | *
40 | * @param mixed[] $metadata
41 | *
42 | * @return mixed
43 | */
44 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null)
45 | {
46 | return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) {
47 | if (!$this->decoratedAdapter instanceof CacheInterface) {
48 | 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));
49 | }
50 |
51 | return $this->decoratedAdapter->get($key, $callback, $beta, $metadata);
52 | }, $key);
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function invalidateTags(array $tags): bool
59 | {
60 | return $this->traceFunction('cache.invalidate_tags', function () use ($tags): bool {
61 | return $this->decoratedAdapter->invalidateTags($tags);
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Tracing/Cache/TraceableTagAwareCacheAdapterForV3.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | use TraceableCacheAdapterTrait;
26 |
27 | /**
28 | * @param HubInterface $hub The current hub
29 | * @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter
30 | */
31 | public function __construct(HubInterface $hub, TagAwareAdapterInterface $decoratedAdapter)
32 | {
33 | $this->hub = $hub;
34 | $this->decoratedAdapter = $decoratedAdapter;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | *
40 | * @param mixed[] $metadata
41 | */
42 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
43 | {
44 | return $this->traceFunction('cache.get_item', function () use ($key, $callback, $beta, &$metadata) {
45 | if (!$this->decoratedAdapter instanceof CacheInterface) {
46 | 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));
47 | }
48 |
49 | return $this->decoratedAdapter->get($key, $callback, $beta, $metadata);
50 | }, $key);
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function invalidateTags(array $tags): bool
57 | {
58 | return $this->traceFunction('cache.invalidate_tags', function () use ($tags): bool {
59 | return $this->decoratedAdapter->invalidateTags($tags);
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/AbstractTracingStatement.php:
--------------------------------------------------------------------------------
1 | The span data
36 | */
37 | protected $spanData;
38 |
39 | /**
40 | * Constructor.
41 | *
42 | * @param HubInterface $hub The current hub
43 | * @param Statement $decoratedStatement The decorated statement
44 | * @param string $sqlQuery The SQL query executed by the decorated statement
45 | * @param array $spanData The span data
46 | */
47 | public function __construct(HubInterface $hub, Statement $decoratedStatement, string $sqlQuery, array $spanData)
48 | {
49 | $this->hub = $hub;
50 | $this->decoratedStatement = $decoratedStatement;
51 | $this->sqlQuery = $sqlQuery;
52 | $this->spanData = $spanData;
53 | }
54 |
55 | /**
56 | * Calls the given callback by passing to it the specified arguments and
57 | * wrapping its execution into a child {@see Span} of the current one.
58 | *
59 | * @param callable $callback The function to call
60 | * @param mixed ...$args The arguments to pass to the callback
61 | *
62 | * @phpstan-template T
63 | *
64 | * @phpstan-param callable(mixed...): T $callback
65 | *
66 | * @phpstan-return T
67 | */
68 | protected function traceFunction(SpanContext $spanContext, callable $callback, ...$args)
69 | {
70 | $span = $this->hub->getSpan();
71 |
72 | if (null !== $span) {
73 | $span = $span->startChild($spanContext);
74 | }
75 |
76 | try {
77 | return $callback(...$args);
78 | } finally {
79 | if (null !== $span) {
80 | $span->finish();
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/Compatibility/MiddlewareInterface.php:
--------------------------------------------------------------------------------
1 | tracingDriverMiddleware = $tracingDriverMiddleware;
27 | }
28 |
29 | /**
30 | * Configures the given connection by wrapping its driver into an instance
31 | * of the {@see TracingDriver} class. This is done using the reflection,
32 | * and as such should be limited only to the versions of Doctrine DBAL that
33 | * are lower than 3.0. Since 3.0 onwards, the concept of driver middlewares
34 | * has been introduced which allows the same thing we're doing here, but in
35 | * a more appropriate and "legal" way.
36 | *
37 | * @param Connection $connection The connection to configure
38 | */
39 | public function configure(Connection $connection): void
40 | {
41 | $reflectionProperty = new \ReflectionProperty($connection, '_driver');
42 | $reflectionProperty->setAccessible(true);
43 | $reflectionProperty->setValue($connection, $this->tracingDriverMiddleware->wrap($reflectionProperty->getValue($connection)));
44 | $reflectionProperty->setAccessible(false);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV2V3.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function create(Connection $connection, AbstractPlatform $databasePlatform, array $params): TracingDriverConnectionInterface
42 | {
43 | $tracingDriverConnection = new TracingDriverConnection(
44 | $this->hub,
45 | $connection,
46 | $this->getDatabasePlatform($databasePlatform),
47 | $params
48 | );
49 |
50 | if ($connection instanceof ServerInfoAwareConnection) {
51 | $tracingDriverConnection = new TracingServerInfoAwareDriverConnection($tracingDriverConnection);
52 | }
53 |
54 | return $tracingDriverConnection;
55 | }
56 |
57 | private function getDatabasePlatform(AbstractPlatform $databasePlatform): string
58 | {
59 | // https://github.com/open-telemetry/opentelemetry-specification/blob/33113489fb5a1b6da563abb4ffa541447b87f515/specification/trace/semantic_conventions/database.md#connection-level-attributes
60 | switch (true) {
61 | case $databasePlatform instanceof AbstractMySQLPlatform:
62 | return 'mysql';
63 |
64 | case $databasePlatform instanceof DB2Platform:
65 | return 'db2';
66 |
67 | case $databasePlatform instanceof OraclePlatform:
68 | return 'oracle';
69 |
70 | case $databasePlatform instanceof PostgreSQLPlatform:
71 | return 'postgresql';
72 |
73 | case $databasePlatform instanceof SqlitePlatform:
74 | return 'sqlite';
75 |
76 | case $databasePlatform instanceof SQLServerPlatform:
77 | return 'mssql';
78 |
79 | default:
80 | return 'other_sql';
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryForV4.php:
--------------------------------------------------------------------------------
1 | hub = $hub;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function create(Connection $connection, AbstractPlatform $databasePlatform, array $params): TracingDriverConnectionInterface
41 | {
42 | $tracingDriverConnection = new TracingDriverConnection(
43 | $this->hub,
44 | $connection,
45 | $this->getDatabasePlatform($databasePlatform),
46 | $params
47 | );
48 |
49 | return $tracingDriverConnection;
50 | }
51 |
52 | private function getDatabasePlatform(AbstractPlatform $databasePlatform): string
53 | {
54 | // https://github.com/open-telemetry/opentelemetry-specification/blob/33113489fb5a1b6da563abb4ffa541447b87f515/specification/trace/semantic_conventions/database.md#connection-level-attributes
55 | switch (true) {
56 | case $databasePlatform instanceof AbstractMySQLPlatform:
57 | return 'mysql';
58 |
59 | case $databasePlatform instanceof DB2Platform:
60 | return 'db2';
61 |
62 | case $databasePlatform instanceof OraclePlatform:
63 | return 'oracle';
64 |
65 | case $databasePlatform instanceof PostgreSQLPlatform:
66 | return 'postgresql';
67 |
68 | case $databasePlatform instanceof SQLitePlatform:
69 | return 'sqlite';
70 |
71 | case $databasePlatform instanceof SQLServerPlatform:
72 | return 'mssql';
73 |
74 | default:
75 | return 'other_sql';
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionFactoryInterface.php:
--------------------------------------------------------------------------------
1 | $params The params of the connection
26 | *
27 | * @phpstan-param ConnectionParams $params
28 | */
29 | public function create(Connection $connection, AbstractPlatform $databasePlatform, array $params): TracingDriverConnectionInterface;
30 | }
31 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV2V3.php:
--------------------------------------------------------------------------------
1 | = 2.10 and >= 3.3.
18 | *
19 | * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams
20 | */
21 | final class TracingDriverConnectionForV2V3 implements TracingDriverConnectionInterface
22 | {
23 | /**
24 | * @internal
25 | */
26 | public const SPAN_OP_CONN_PREPARE = 'db.sql.prepare';
27 |
28 | /**
29 | * @internal
30 | */
31 | public const SPAN_OP_CONN_QUERY = 'db.sql.query';
32 |
33 | /**
34 | * @internal
35 | */
36 | public const SPAN_OP_CONN_EXEC = 'db.sql.exec';
37 |
38 | /**
39 | * @internal
40 | */
41 | public const SPAN_OP_CONN_BEGIN_TRANSACTION = 'db.sql.transaction.begin';
42 |
43 | /**
44 | * @internal
45 | */
46 | public const SPAN_OP_TRANSACTION_COMMIT = 'db.sql.transaction.commit';
47 |
48 | /**
49 | * @internal
50 | */
51 | public const SPAN_OP_TRANSACTION_ROLLBACK = 'db.sql.transaction.rollback';
52 |
53 | /**
54 | * @var HubInterface The current hub
55 | */
56 | private $hub;
57 |
58 | /**
59 | * @var DriverConnectionInterface The decorated connection
60 | */
61 | private $decoratedConnection;
62 |
63 | /**
64 | * @var array The data to attach to the span
65 | */
66 | private $spanData;
67 |
68 | /**
69 | * Constructor.
70 | *
71 | * @param HubInterface $hub The current hub
72 | * @param DriverConnectionInterface $decoratedConnection The connection to decorate
73 | * @param string $databasePlatform The name of the database platform
74 | * @param array $params The connection params
75 | *
76 | * @phpstan-param ConnectionParams $params
77 | */
78 | public function __construct(
79 | HubInterface $hub,
80 | DriverConnectionInterface $decoratedConnection,
81 | string $databasePlatform,
82 | array $params
83 | ) {
84 | $this->hub = $hub;
85 | $this->decoratedConnection = $decoratedConnection;
86 | $this->spanData = $this->getSpanData($databasePlatform, $params);
87 | }
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public function prepare($sql): Statement
93 | {
94 | $statement = $this->traceFunction(self::SPAN_OP_CONN_PREPARE, $sql, function () use ($sql): Statement {
95 | return $this->decoratedConnection->prepare($sql);
96 | });
97 |
98 | return new TracingStatement($this->hub, $statement, $sql, $this->spanData);
99 | }
100 |
101 | /**
102 | * {@inheritdoc}
103 | */
104 | public function query(?string $sql = null, ...$args): Result
105 | {
106 | return $this->traceFunction(self::SPAN_OP_CONN_QUERY, $sql, function () use ($sql, $args): Result {
107 | return $this->decoratedConnection->query($sql, ...$args);
108 | });
109 | }
110 |
111 | /**
112 | * {@inheritdoc}
113 | *
114 | * @return mixed
115 | */
116 | public function quote($value, $type = ParameterType::STRING)
117 | {
118 | return $this->decoratedConnection->quote($value, $type);
119 | }
120 |
121 | /**
122 | * {@inheritdoc}
123 | */
124 | public function exec($sql): int
125 | {
126 | return $this->traceFunction(self::SPAN_OP_CONN_EXEC, $sql, function () use ($sql): int {
127 | return $this->decoratedConnection->exec($sql);
128 | });
129 | }
130 |
131 | /**
132 | * {@inheritdoc}
133 | *
134 | * @return string|int|false
135 | */
136 | public function lastInsertId($name = null)
137 | {
138 | return $this->decoratedConnection->lastInsertId($name);
139 | }
140 |
141 | /**
142 | * {@inheritdoc}
143 | */
144 | public function beginTransaction(): bool
145 | {
146 | return $this->traceFunction(self::SPAN_OP_CONN_BEGIN_TRANSACTION, 'BEGIN TRANSACTION', function (): bool {
147 | return $this->decoratedConnection->beginTransaction();
148 | });
149 | }
150 |
151 | /**
152 | * {@inheritdoc}
153 | */
154 | public function commit(): bool
155 | {
156 | return $this->traceFunction(self::SPAN_OP_TRANSACTION_COMMIT, 'COMMIT', function (): bool {
157 | return $this->decoratedConnection->commit();
158 | });
159 | }
160 |
161 | /**
162 | * {@inheritdoc}
163 | */
164 | public function rollBack(): bool
165 | {
166 | return $this->traceFunction(self::SPAN_OP_TRANSACTION_ROLLBACK, 'ROLLBACK', function (): bool {
167 | return $this->decoratedConnection->rollBack();
168 | });
169 | }
170 |
171 | /**
172 | * {@inheritdoc}
173 | *
174 | * @return resource|object
175 | */
176 | public function getNativeConnection()
177 | {
178 | if (!method_exists($this->decoratedConnection, 'getNativeConnection')) {
179 | throw new \BadMethodCallException(\sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection)));
180 | }
181 |
182 | return $this->decoratedConnection->getNativeConnection();
183 | }
184 |
185 | /**
186 | * {@inheritdoc}
187 | */
188 | public function errorCode(): ?string
189 | {
190 | if (method_exists($this->decoratedConnection, 'errorCode')) {
191 | return $this->decoratedConnection->errorCode();
192 | }
193 |
194 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function errorInfo(): array
201 | {
202 | if (method_exists($this->decoratedConnection, 'errorInfo')) {
203 | return $this->decoratedConnection->errorInfo();
204 | }
205 |
206 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
207 | }
208 |
209 | public function getWrappedConnection(): DriverConnectionInterface
210 | {
211 | return $this->decoratedConnection;
212 | }
213 |
214 | /**
215 | * @phpstan-template T
216 | *
217 | * @phpstan-param \Closure(): T $callback
218 | *
219 | * @phpstan-return T
220 | */
221 | private function traceFunction(string $spanOperation, string $spanDescription, \Closure $callback)
222 | {
223 | $span = $this->hub->getSpan();
224 |
225 | if (null !== $span) {
226 | $span = $span->startChild(
227 | SpanContext::make()
228 | ->setOp($spanOperation)
229 | ->setData($this->spanData)
230 | ->setOrigin('auto.db')
231 | ->setDescription($spanDescription)
232 | );
233 | }
234 |
235 | try {
236 | return $callback();
237 | } finally {
238 | if (null !== $span) {
239 | $span->finish();
240 | }
241 | }
242 | }
243 |
244 | /**
245 | * Gets a map of key-value pairs that will be set as the span data.
246 | *
247 | * @param array $params The connection params
248 | *
249 | * @return array
250 | *
251 | * @phpstan-param ConnectionParams $params
252 | *
253 | * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
254 | */
255 | private function getSpanData(string $databasePlatform, array $params): array
256 | {
257 | $data = ['db.system' => $databasePlatform];
258 |
259 | if (isset($params['user'])) {
260 | $data['db.user'] = $params['user'];
261 | }
262 |
263 | if (isset($params['dbname'])) {
264 | $data['db.name'] = $params['dbname'];
265 | }
266 |
267 | if (isset($params['host']) && !empty($params['host']) && !isset($params['memory'])) {
268 | if (false === filter_var($params['host'], \FILTER_VALIDATE_IP)) {
269 | $data['server.address'] = $params['host'];
270 | } else {
271 | $data['server.address'] = $params['host'];
272 | }
273 | }
274 |
275 | if (isset($params['port'])) {
276 | $data['server.port'] = (string) $params['port'];
277 | }
278 |
279 | if (isset($params['unix_socket'])) {
280 | $data['server.socket.address'] = 'Unix';
281 | } elseif (isset($params['memory'])) {
282 | $data['server.socket.address'] = 'inproc';
283 | }
284 |
285 | return $data;
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionForV4.php:
--------------------------------------------------------------------------------
1 | = 4.0.
17 | *
18 | * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams
19 | */
20 | final class TracingDriverConnectionForV4 implements TracingDriverConnectionInterface
21 | {
22 | /**
23 | * @internal
24 | */
25 | public const SPAN_OP_CONN_PREPARE = 'db.sql.prepare';
26 |
27 | /**
28 | * @internal
29 | */
30 | public const SPAN_OP_CONN_QUERY = 'db.sql.query';
31 |
32 | /**
33 | * @internal
34 | */
35 | public const SPAN_OP_CONN_EXEC = 'db.sql.exec';
36 |
37 | /**
38 | * @internal
39 | */
40 | public const SPAN_OP_CONN_BEGIN_TRANSACTION = 'db.sql.transaction.begin';
41 |
42 | /**
43 | * @internal
44 | */
45 | public const SPAN_OP_TRANSACTION_COMMIT = 'db.sql.transaction.commit';
46 |
47 | /**
48 | * @internal
49 | */
50 | public const SPAN_OP_TRANSACTION_ROLLBACK = 'db.sql.transaction.rollback';
51 |
52 | /**
53 | * @var HubInterface The current hub
54 | */
55 | private $hub;
56 |
57 | /**
58 | * @var DriverConnectionInterface The decorated connection
59 | */
60 | private $decoratedConnection;
61 |
62 | /**
63 | * @var array The data to attach to the span
64 | */
65 | private $spanData;
66 |
67 | /**
68 | * Constructor.
69 | *
70 | * @param HubInterface $hub The current hub
71 | * @param DriverConnectionInterface $decoratedConnection The connection to decorate
72 | * @param string $databasePlatform The name of the database platform
73 | * @param array $params The connection params
74 | *
75 | * @phpstan-param ConnectionParams $params
76 | */
77 | public function __construct(
78 | HubInterface $hub,
79 | DriverConnectionInterface $decoratedConnection,
80 | string $databasePlatform,
81 | array $params
82 | ) {
83 | $this->hub = $hub;
84 | $this->decoratedConnection = $decoratedConnection;
85 | $this->spanData = $this->getSpanData($databasePlatform, $params);
86 | }
87 |
88 | /**
89 | * {@inheritdoc}
90 | */
91 | public function prepare($sql): Statement
92 | {
93 | $statement = $this->traceFunction(self::SPAN_OP_CONN_PREPARE, $sql, function () use ($sql): Statement {
94 | return $this->decoratedConnection->prepare($sql);
95 | });
96 |
97 | return new TracingStatement($this->hub, $statement, $sql, $this->spanData);
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | public function query(?string $sql = null, ...$args): Result
104 | {
105 | return $this->traceFunction(self::SPAN_OP_CONN_QUERY, $sql, function () use ($sql, $args): Result {
106 | return $this->decoratedConnection->query($sql, ...$args);
107 | });
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function quote(string $value): string
114 | {
115 | return $this->decoratedConnection->quote($value);
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | */
121 | public function exec(string $sql): int|string
122 | {
123 | return $this->traceFunction(self::SPAN_OP_CONN_EXEC, $sql, function () use ($sql): int|string {
124 | return $this->decoratedConnection->exec($sql);
125 | });
126 | }
127 |
128 | /**
129 | * {@inheritdoc}
130 | *
131 | * @return string|int
132 | */
133 | public function lastInsertId(): string|int
134 | {
135 | return $this->decoratedConnection->lastInsertId();
136 | }
137 |
138 | /**
139 | * {@inheritdoc}
140 | */
141 | public function beginTransaction(): void
142 | {
143 | $this->traceFunction(self::SPAN_OP_CONN_BEGIN_TRANSACTION, 'BEGIN TRANSACTION', function (): void {
144 | $this->decoratedConnection->beginTransaction();
145 | });
146 | }
147 |
148 | /**
149 | * {@inheritdoc}
150 | */
151 | public function commit(): void
152 | {
153 | $this->traceFunction(self::SPAN_OP_TRANSACTION_COMMIT, 'COMMIT', function (): void {
154 | $this->decoratedConnection->commit();
155 | });
156 | }
157 |
158 | /**
159 | * {@inheritdoc}
160 | */
161 | public function rollBack(): void
162 | {
163 | $this->traceFunction(self::SPAN_OP_TRANSACTION_ROLLBACK, 'ROLLBACK', function (): void {
164 | $this->decoratedConnection->rollBack();
165 | });
166 | }
167 |
168 | /**
169 | * {@inheritdoc}
170 | *
171 | * @return resource|object
172 | */
173 | public function getNativeConnection()
174 | {
175 | return $this->decoratedConnection->getNativeConnection();
176 | }
177 |
178 | /**
179 | * {@inheritdoc}
180 | */
181 | public function errorCode(): ?string
182 | {
183 | if (method_exists($this->decoratedConnection, 'errorCode')) {
184 | return $this->decoratedConnection->errorCode();
185 | }
186 |
187 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
188 | }
189 |
190 | /**
191 | * {@inheritdoc}
192 | */
193 | public function errorInfo(): array
194 | {
195 | if (method_exists($this->decoratedConnection, 'errorInfo')) {
196 | return $this->decoratedConnection->errorInfo();
197 | }
198 |
199 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
200 | }
201 |
202 | public function getWrappedConnection(): DriverConnectionInterface
203 | {
204 | return $this->decoratedConnection;
205 | }
206 |
207 | public function getServerVersion(): string
208 | {
209 | return $this->decoratedConnection->getServerVersion();
210 | }
211 |
212 | /**
213 | * @phpstan-template T
214 | *
215 | * @phpstan-param \Closure(): T $callback
216 | *
217 | * @phpstan-return T
218 | */
219 | private function traceFunction(string $spanOperation, string $spanDescription, \Closure $callback)
220 | {
221 | $span = $this->hub->getSpan();
222 |
223 | if (null !== $span) {
224 | $span = $span->startChild(
225 | SpanContext::make()
226 | ->setOp($spanOperation)
227 | ->setData($this->spanData)
228 | ->setOrigin('auto.db')
229 | ->setDescription($spanDescription)
230 | );
231 | }
232 |
233 | try {
234 | return $callback();
235 | } finally {
236 | if (null !== $span) {
237 | $span->finish();
238 | }
239 | }
240 | }
241 |
242 | /**
243 | * Gets a map of key-value pairs that will be set as the span data.
244 | *
245 | * @param array $params The connection params
246 | *
247 | * @return array
248 | *
249 | * @phpstan-param ConnectionParams $params
250 | *
251 | * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
252 | */
253 | private function getSpanData(string $databasePlatform, array $params): array
254 | {
255 | $data = ['db.system' => $databasePlatform];
256 |
257 | if (isset($params['user'])) {
258 | $data['db.user'] = $params['user'];
259 | }
260 |
261 | if (isset($params['dbname'])) {
262 | $data['db.name'] = $params['dbname'];
263 | }
264 |
265 | if (isset($params['host']) && !empty($params['host']) && !isset($params['memory'])) {
266 | if (false === filter_var($params['host'], \FILTER_VALIDATE_IP)) {
267 | $data['server.address'] = $params['host'];
268 | } else {
269 | $data['server.address'] = $params['host'];
270 | }
271 | }
272 |
273 | if (isset($params['port'])) {
274 | $data['server.port'] = (string) $params['port'];
275 | }
276 |
277 | if (isset($params['unix_socket'])) {
278 | $data['server.socket.address'] = 'Unix';
279 | } elseif (isset($params['memory'])) {
280 | $data['server.socket.address'] = 'inproc';
281 | }
282 |
283 | return $data;
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverConnectionInterface.php:
--------------------------------------------------------------------------------
1 | decoratedDriver = $decoratedDriver;
42 | $this->connectionFactory = $connectionFactory;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function connect(array $params, $username = null, $password = null, array $driverOptions = []): TracingDriverConnectionInterface
49 | {
50 | return $this->connectionFactory->create(
51 | $this->decoratedDriver->connect($params, $username, $password, $driverOptions),
52 | $this->decoratedDriver->getDatabasePlatform(),
53 | $params
54 | );
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function getDatabasePlatform(): AbstractPlatform
61 | {
62 | return $this->decoratedDriver->getDatabasePlatform();
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function getSchemaManager(Connection $conn, ?AbstractPlatform $platform = null): AbstractSchemaManager
69 | {
70 | return $this->decoratedDriver->getSchemaManager($conn, $platform);
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | public function getName(): string
77 | {
78 | return $this->decoratedDriver->getName();
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public function getDatabase(Connection $conn): ?string
85 | {
86 | return $this->decoratedDriver->getDatabase($conn);
87 | }
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public function createDatabasePlatformForVersion($version): AbstractPlatform
93 | {
94 | if ($this->decoratedDriver instanceof VersionAwarePlatformDriver) {
95 | return $this->decoratedDriver->createDatabasePlatformForVersion($version);
96 | }
97 |
98 | return $this->getDatabasePlatform();
99 | }
100 |
101 | /**
102 | * {@inheritdoc}
103 | */
104 | public function convertException($message, DriverException $exception): DBALDriverException
105 | {
106 | if ($this->decoratedDriver instanceof ExceptionConverterDriver) {
107 | return $this->decoratedDriver->convertException($message, $exception);
108 | }
109 |
110 | return new DBALDriverException($message, $exception);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverForV3.php:
--------------------------------------------------------------------------------
1 | = 3.0.
14 | *
15 | * @internal
16 | *
17 | * @phpstan-import-type Params from \Doctrine\DBAL\DriverManager as ConnectionParams
18 | */
19 | final class TracingDriverForV3 extends AbstractDriverMiddleware
20 | {
21 | /**
22 | * @var TracingDriverConnectionFactoryInterface The connection factory
23 | */
24 | private $connectionFactory;
25 |
26 | /**
27 | * Constructor.
28 | *
29 | * @param TracingDriverConnectionFactoryInterface $connectionFactory The connection factory
30 | * @param Driver $decoratedDriver The instance of the driver to decorate
31 | */
32 | public function __construct(TracingDriverConnectionFactoryInterface $connectionFactory, Driver $decoratedDriver)
33 | {
34 | parent::__construct($decoratedDriver);
35 |
36 | $this->connectionFactory = $connectionFactory;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | *
42 | * @phpstan-param ConnectionParams $params
43 | */
44 | public function connect(array $params): TracingDriverConnectionInterface
45 | {
46 | return $this->connectionFactory->create(
47 | parent::connect($params),
48 | $this->getDatabasePlatform(),
49 | $params
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverForV4.php:
--------------------------------------------------------------------------------
1 | = 4.0.
15 | *
16 | * @internal
17 | *
18 | * @psalm-import-type Params from \Doctrine\DBAL\DriverManager
19 | */
20 | final class TracingDriverForV4 extends AbstractDriverMiddleware
21 | {
22 | /**
23 | * @var TracingDriverConnectionFactoryInterface The connection factory
24 | */
25 | private $connectionFactory;
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param TracingDriverConnectionFactoryInterface $connectionFactory The connection factory
31 | * @param Driver $decoratedDriver The instance of the driver to decorate
32 | */
33 | public function __construct(TracingDriverConnectionFactoryInterface $connectionFactory, Driver $decoratedDriver)
34 | {
35 | parent::__construct($decoratedDriver);
36 |
37 | $this->connectionFactory = $connectionFactory;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | *
43 | * @psalm-param Params $params All connection parameters.
44 | */
45 | public function connect(array $params): TracingDriverConnectionInterface
46 | {
47 | $connection = parent::connect($params);
48 | $versionProvider = new StaticServerVersionProvider($connection->getServerVersion());
49 |
50 | return $this->connectionFactory->create(
51 | $connection,
52 | $this->getDatabasePlatform($versionProvider),
53 | $params
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingDriverMiddleware.php:
--------------------------------------------------------------------------------
1 | connectionFactory = $hubOrConnectionFactory;
33 | } elseif ($hubOrConnectionFactory instanceof HubInterface) {
34 | @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);
35 |
36 | $this->connectionFactory = new TracingDriverConnectionFactory($hubOrConnectionFactory);
37 | } else {
38 | 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));
39 | }
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function wrap(Driver $driver): Driver
46 | {
47 | return new TracingDriver($this->connectionFactory, $driver);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingServerInfoAwareDriverConnection.php:
--------------------------------------------------------------------------------
1 | decoratedConnection = $decoratedConnection;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function prepare($sql): Statement
40 | {
41 | return $this->decoratedConnection->prepare($sql);
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function query(?string $sql = null, ...$args): Result
48 | {
49 | return $this->decoratedConnection->query($sql, ...$args);
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | *
55 | * @return mixed
56 | */
57 | public function quote($value, $type = ParameterType::STRING)
58 | {
59 | return $this->decoratedConnection->quote($value, $type);
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function exec($sql): int
66 | {
67 | return $this->decoratedConnection->exec($sql);
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | *
73 | * @return string|int|false
74 | */
75 | public function lastInsertId($name = null)
76 | {
77 | return $this->decoratedConnection->lastInsertId($name);
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function beginTransaction(): bool
84 | {
85 | return $this->decoratedConnection->beginTransaction();
86 | }
87 |
88 | /**
89 | * {@inheritdoc}
90 | */
91 | public function commit(): bool
92 | {
93 | return $this->decoratedConnection->commit();
94 | }
95 |
96 | /**
97 | * {@inheritdoc}
98 | */
99 | public function rollBack(): bool
100 | {
101 | return $this->decoratedConnection->rollBack();
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | public function getServerVersion(): string
108 | {
109 | $wrappedConnection = $this->getWrappedConnection();
110 |
111 | if (!$wrappedConnection instanceof ServerInfoAwareConnection) {
112 | throw new \BadMethodCallException(\sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class));
113 | }
114 |
115 | return $wrappedConnection->getServerVersion();
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | *
121 | * @return resource|object
122 | */
123 | public function getNativeConnection()
124 | {
125 | if (!method_exists($this->decoratedConnection, 'getNativeConnection')) {
126 | throw new \BadMethodCallException(\sprintf('The connection "%s" does not support accessing the native connection.', \get_class($this->decoratedConnection)));
127 | }
128 |
129 | return $this->decoratedConnection->getNativeConnection();
130 | }
131 |
132 | /**
133 | * {@inheritdoc}
134 | */
135 | public function requiresQueryForServerVersion(): bool
136 | {
137 | $wrappedConnection = $this->getWrappedConnection();
138 |
139 | if (!$wrappedConnection instanceof ServerInfoAwareConnection) {
140 | throw new \BadMethodCallException(\sprintf('The wrapped connection must be an instance of the "%s" interface.', ServerInfoAwareConnection::class));
141 | }
142 |
143 | if (!method_exists($wrappedConnection, 'requiresQueryForServerVersion')) {
144 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
145 | }
146 |
147 | return $wrappedConnection->requiresQueryForServerVersion();
148 | }
149 |
150 | /**
151 | * {@inheritdoc}
152 | */
153 | public function errorCode(): ?string
154 | {
155 | if (method_exists($this->decoratedConnection, 'errorCode')) {
156 | return $this->decoratedConnection->errorCode();
157 | }
158 |
159 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
160 | }
161 |
162 | /**
163 | * {@inheritdoc}
164 | */
165 | public function errorInfo(): array
166 | {
167 | if (method_exists($this->decoratedConnection, 'errorInfo')) {
168 | return $this->decoratedConnection->errorInfo();
169 | }
170 |
171 | throw new \BadMethodCallException(\sprintf('The %s() method is not supported on Doctrine DBAL 3.0.', __METHOD__));
172 | }
173 |
174 | public function getWrappedConnection(): Connection
175 | {
176 | return $this->decoratedConnection->getWrappedConnection();
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingStatementForV2.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | final class TracingStatementForV2 extends AbstractTracingStatement implements \IteratorAggregate, Statement
17 | {
18 | /**
19 | * {@inheritdoc}
20 | */
21 | public function getIterator(): \Traversable
22 | {
23 | return $this->decoratedStatement;
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function closeCursor(): bool
30 | {
31 | return $this->decoratedStatement->closeCursor();
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function columnCount(): int
38 | {
39 | return $this->decoratedStatement->columnCount();
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool
46 | {
47 | return $this->decoratedStatement->setFetchMode($fetchMode, $arg2, $arg3);
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
54 | {
55 | return $this->decoratedStatement->fetch($fetchMode, $cursorOrientation, $cursorOffset);
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
62 | {
63 | return $this->decoratedStatement->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function fetchColumn($columnIndex = 0)
70 | {
71 | return $this->decoratedStatement->fetchColumn($columnIndex);
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function errorCode()
78 | {
79 | return $this->decoratedStatement->errorCode();
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function errorInfo(): array
86 | {
87 | return $this->decoratedStatement->errorInfo();
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function rowCount(): int
94 | {
95 | return $this->decoratedStatement->rowCount();
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | */
101 | public function bindValue($param, $value, $type = ParameterType::STRING): bool
102 | {
103 | return $this->decoratedStatement->bindValue($param, $value, $type);
104 | }
105 |
106 | /**
107 | * {@inheritdoc}
108 | */
109 | public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
110 | {
111 | return $this->decoratedStatement->bindParam($param, $variable, $type, $length ?? 0, ...\array_slice(\func_get_args(), 4));
112 | }
113 |
114 | /**
115 | * {@inheritdoc}
116 | */
117 | public function execute($params = null): bool
118 | {
119 | $spanContext = SpanContext::make()
120 | ->setOp(self::SPAN_OP_STMT_EXECUTE)
121 | ->setData($this->spanData)
122 | ->setOrigin('auto.db')
123 | ->setDescription($this->sqlQuery);
124 |
125 | return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute'], $params);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingStatementForV3.php:
--------------------------------------------------------------------------------
1 | decoratedStatement->bindValue($param, $value, $type);
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
29 | {
30 | return $this->decoratedStatement->bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3));
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function execute($params = null): Result
37 | {
38 | $spanContext = SpanContext::make()
39 | ->setOp(self::SPAN_OP_STMT_EXECUTE)
40 | ->setData($this->spanData)
41 | ->setOrigin('auto.db')
42 | ->setDescription($this->sqlQuery);
43 |
44 | return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute'], $params);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Tracing/Doctrine/DBAL/TracingStatementForV4.php:
--------------------------------------------------------------------------------
1 | decoratedStatement->bindValue($param, $value, $type);
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function execute(): Result
29 | {
30 | $spanContext = SpanContext::make()
31 | ->setOp(self::SPAN_OP_STMT_EXECUTE)
32 | ->setData($this->spanData)
33 | ->setOrigin('auto.db')
34 | ->setDescription($this->sqlQuery);
35 |
36 | return $this->traceFunction($spanContext, [$this->decoratedStatement, 'execute']);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/AbstractTraceableHttpClient.php:
--------------------------------------------------------------------------------
1 | client = $client;
44 | $this->hub = $hub;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function request(string $method, string $url, array $options = []): ResponseInterface
51 | {
52 | $uri = new Uri($url);
53 | $headers = $options['headers'] ?? [];
54 |
55 | $span = $this->hub->getSpan();
56 | $client = $this->hub->getClient();
57 |
58 | if (null === $span) {
59 | if (self::shouldAttachTracingHeaders($client, $uri)) {
60 | $headers['baggage'] = getBaggage();
61 | $headers['sentry-trace'] = getTraceparent();
62 | $headers['traceparent'] = getW3CTraceparent();
63 | }
64 |
65 | $options['headers'] = $headers;
66 |
67 | return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $span);
68 | }
69 |
70 | $partialUri = Uri::fromParts([
71 | 'scheme' => $uri->getScheme(),
72 | 'host' => $uri->getHost(),
73 | 'port' => $uri->getPort(),
74 | 'path' => $uri->getPath(),
75 | ]);
76 |
77 | $context = SpanContext::make()
78 | ->setOp('http.client')
79 | ->setOrigin('auto.http.client')
80 | ->setDescription($method . ' ' . (string) $partialUri);
81 |
82 | $contextData = [
83 | 'http.url' => (string) $partialUri,
84 | 'http.request.method' => $method,
85 | ];
86 | if ('' !== $uri->getQuery()) {
87 | $contextData['http.query'] = $uri->getQuery();
88 | }
89 | if ('' !== $uri->getFragment()) {
90 | $contextData['http.fragment'] = $uri->getFragment();
91 | }
92 | $context->setData($contextData);
93 |
94 | $childSpan = $span->startChild($context);
95 |
96 | if (self::shouldAttachTracingHeaders($client, $uri)) {
97 | $headers['baggage'] = $childSpan->toBaggage();
98 | $headers['sentry-trace'] = $childSpan->toTraceparent();
99 | $headers['traceparent'] = $childSpan->toW3CTraceparent();
100 | }
101 |
102 | $options['headers'] = $headers;
103 |
104 | return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $childSpan);
105 | }
106 |
107 | /**
108 | * {@inheritdoc}
109 | */
110 | public function stream($responses, ?float $timeout = null): ResponseStreamInterface
111 | {
112 | if ($responses instanceof AbstractTraceableResponse) {
113 | $responses = [$responses];
114 | } elseif (!is_iterable($responses)) {
115 | throw new \TypeError(\sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
116 | }
117 |
118 | return new ResponseStream(AbstractTraceableResponse::stream($this->client, $responses, $timeout));
119 | }
120 |
121 | public function reset(): void
122 | {
123 | if ($this->client instanceof ResetInterface) {
124 | $this->client->reset();
125 | }
126 | }
127 |
128 | /**
129 | * {@inheritdoc}
130 | */
131 | public function setLogger(LoggerInterface $logger): void
132 | {
133 | if ($this->client instanceof LoggerAwareInterface) {
134 | $this->client->setLogger($logger);
135 | }
136 | }
137 |
138 | private static function shouldAttachTracingHeaders(?ClientInterface $client, Uri $uri): bool
139 | {
140 | if (null !== $client) {
141 | $sdkOptions = $client->getOptions();
142 |
143 | // Check if the request destination is allow listed in the trace_propagation_targets option.
144 | if (
145 | null === $sdkOptions->getTracePropagationTargets()
146 | || \in_array($uri->getHost(), $sdkOptions->getTracePropagationTargets())
147 | ) {
148 | return true;
149 | }
150 | }
151 |
152 | return false;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/AbstractTraceableResponse.php:
--------------------------------------------------------------------------------
1 | client = $client;
36 | $this->response = $response;
37 | $this->span = $span;
38 | }
39 |
40 | public function __destruct()
41 | {
42 | try {
43 | if (method_exists($this->response, '__destruct')) {
44 | $this->response->__destruct();
45 | }
46 | } finally {
47 | $this->finishSpan();
48 | }
49 | }
50 |
51 | public function __sleep(): array
52 | {
53 | throw new \BadMethodCallException('Serializing instances of this class is forbidden.');
54 | }
55 |
56 | public function __wakeup()
57 | {
58 | throw new \BadMethodCallException('Unserializing instances of this class is forbidden.');
59 | }
60 |
61 | public function getStatusCode(): int
62 | {
63 | return $this->response->getStatusCode();
64 | }
65 |
66 | public function getHeaders(bool $throw = true): array
67 | {
68 | return $this->response->getHeaders($throw);
69 | }
70 |
71 | public function getContent(bool $throw = true): string
72 | {
73 | try {
74 | return $this->response->getContent($throw);
75 | } finally {
76 | $this->finishSpan();
77 | }
78 | }
79 |
80 | public function toArray(bool $throw = true): array
81 | {
82 | try {
83 | return $this->response->toArray($throw);
84 | } finally {
85 | $this->finishSpan();
86 | }
87 | }
88 |
89 | public function cancel(): void
90 | {
91 | $this->response->cancel();
92 | $this->finishSpan();
93 | }
94 |
95 | /**
96 | * @internal
97 | *
98 | * @param iterable $responses
99 | *
100 | * @return \Generator
101 | */
102 | public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator
103 | {
104 | /** @var \SplObjectStorage $traceableMap */
105 | $traceableMap = new \SplObjectStorage();
106 | $wrappedResponses = [];
107 |
108 | foreach ($responses as $response) {
109 | if (!$response instanceof self) {
110 | throw new \TypeError(\sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($response)));
111 | }
112 |
113 | $traceableMap[$response->response] = $response;
114 | $wrappedResponses[] = $response->response;
115 | }
116 |
117 | foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
118 | $traceableResponse = $traceableMap[$response];
119 | $traceableResponse->finishSpan();
120 |
121 | yield $traceableResponse => $chunk;
122 | }
123 | }
124 |
125 | private function finishSpan(): void
126 | {
127 | if (null === $this->span) {
128 | return;
129 | }
130 |
131 | // We finish the span (which means setting the span end timestamp) first
132 | // to ensure the measured time is as close as possible to the duration of
133 | // the HTTP request
134 | $this->span->finish();
135 |
136 | /** @var int $statusCode */
137 | $statusCode = $this->response->getInfo('http_code');
138 |
139 | // If the returned status code is 0, it means that this info isn't available
140 | // yet (e.g. an error happened before the request was sent), hence we cannot
141 | // determine what happened.
142 | if (0 === $statusCode) {
143 | $this->span->setStatus(SpanStatus::unknownError());
144 | } else {
145 | $this->span->setStatus(SpanStatus::createFromHttpStatusCode($statusCode));
146 | }
147 |
148 | $this->span = null;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/TraceableHttpClientForV4.php:
--------------------------------------------------------------------------------
1 | client = $this->client->withOptions($options);
19 |
20 | return $clone;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/TraceableHttpClientForV6.php:
--------------------------------------------------------------------------------
1 | client = $this->client->withOptions($options);
19 |
20 | return $clone;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/TraceableResponseForV4.php:
--------------------------------------------------------------------------------
1 | response->getInfo($type);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/TraceableResponseForV5.php:
--------------------------------------------------------------------------------
1 | response->getInfo($type);
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function toStream(bool $throw = true)
28 | {
29 | return $this->response->toStream($throw);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Tracing/HttpClient/TraceableResponseForV6.php:
--------------------------------------------------------------------------------
1 | response->getInfo($type);
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function toStream(bool $throw = true)
26 | {
27 | return $this->response->toStream($throw);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tracing/Twig/TwigTracingExtension.php:
--------------------------------------------------------------------------------
1 | The currently active spans
23 | */
24 | private $spans;
25 |
26 | /**
27 | * @param HubInterface $hub The current hub
28 | */
29 | public function __construct(HubInterface $hub)
30 | {
31 | $this->hub = $hub;
32 | $this->spans = new \SplObjectStorage();
33 | }
34 |
35 | /**
36 | * This method is called before the execution of a block, a macro or a
37 | * template.
38 | *
39 | * @param Profile $profile The profiling data
40 | */
41 | public function enter(Profile $profile): void
42 | {
43 | $transaction = $this->hub->getTransaction();
44 |
45 | if (null === $transaction) {
46 | return;
47 | }
48 |
49 | $this->spans[$profile] = $transaction->startChild(
50 | SpanContext::make()
51 | ->setOp('view.render')
52 | ->setOrigin('auto.view')
53 | ->setDescription($this->getSpanDescription($profile))
54 | );
55 | }
56 |
57 | /**
58 | * This method is called when the execution of a block, a macro or a
59 | * template is finished.
60 | *
61 | * @param Profile $profile The profiling data
62 | */
63 | public function leave(Profile $profile): void
64 | {
65 | if (!isset($this->spans[$profile])) {
66 | return;
67 | }
68 |
69 | $this->spans[$profile]->finish();
70 |
71 | unset($this->spans[$profile]);
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function getNodeVisitors(): array
78 | {
79 | return [
80 | new ProfilerNodeVisitor(self::class),
81 | ];
82 | }
83 |
84 | /**
85 | * Gets a short description for the span.
86 | *
87 | * @param Profile $profile The profiling data
88 | */
89 | private function getSpanDescription(Profile $profile): string
90 | {
91 | switch (true) {
92 | case $profile->isRoot():
93 | return $profile->getName();
94 |
95 | case $profile->isTemplate():
96 | return $profile->getTemplate();
97 |
98 | default:
99 | return \sprintf('%s::%s(%s)', $profile->getTemplate(), $profile->getType(), $profile->getName());
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Twig/SentryExtension.php:
--------------------------------------------------------------------------------
1 | ['html']]),
31 | new TwigFunction('sentry_w3c_trace_meta', [$this, 'getW3CTraceMeta'], ['is_safe' => ['html']]),
32 | new TwigFunction('sentry_baggage_meta', [$this, 'getBaggageMeta'], ['is_safe' => ['html']]),
33 | ];
34 | }
35 |
36 | /**
37 | * Returns an HTML meta tag named `sentry-trace`.
38 | */
39 | public function getTraceMeta(): string
40 | {
41 | return \sprintf('', getTraceparent());
42 | }
43 |
44 | /**
45 | * Returns an HTML meta tag named `traceparent`.
46 | */
47 | public function getW3CTraceMeta(): string
48 | {
49 | return \sprintf('', getW3CTraceparent());
50 | }
51 |
52 | /**
53 | * Returns an HTML meta tag named `baggage`.
54 | */
55 | public function getBaggageMeta(): string
56 | {
57 | return \sprintf('', getBaggage());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/aliases.php:
--------------------------------------------------------------------------------
1 | =')) {
45 | if (!class_exists(TraceableCacheAdapter::class, false)) {
46 | class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class);
47 | }
48 |
49 | if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) {
50 | class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class);
51 | }
52 | } else {
53 | if (!class_exists(TraceableCacheAdapter::class, false)) {
54 | class_alias(TraceableCacheAdapterForV2::class, TraceableCacheAdapter::class);
55 | }
56 |
57 | if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) {
58 | class_alias(TraceableTagAwareCacheAdapterForV2::class, TraceableTagAwareCacheAdapter::class);
59 | }
60 | }
61 | }
62 |
63 | if (!class_exists(TracingStatement::class)) {
64 | if (class_exists(Result::class) && !interface_exists(VersionAwarePlatformDriver::class)) {
65 | class_alias(TracingStatementForV4::class, TracingStatement::class);
66 | class_alias(TracingDriverForV4::class, TracingDriver::class);
67 | class_alias(TracingDriverConnectionForV4::class, TracingDriverConnection::class);
68 | class_alias(TracingDriverConnectionFactoryForV4::class, TracingDriverConnectionFactory::class);
69 | } elseif (class_exists(Result::class)) {
70 | class_alias(TracingStatementForV3::class, TracingStatement::class);
71 | class_alias(TracingDriverForV3::class, TracingDriver::class);
72 | class_alias(TracingDriverConnectionForV2V3::class, TracingDriverConnection::class);
73 | class_alias(TracingDriverConnectionFactoryForV2V3::class, TracingDriverConnectionFactory::class);
74 | } elseif (interface_exists(Result::class)) {
75 | class_alias(TracingStatementForV2::class, TracingStatement::class);
76 | class_alias(TracingDriverForV2::class, TracingDriver::class);
77 | class_alias(TracingDriverConnectionForV2V3::class, TracingDriverConnection::class);
78 | class_alias(TracingDriverConnectionFactoryForV2V3::class, TracingDriverConnectionFactory::class);
79 | }
80 | }
81 |
82 | if (!class_exists(TraceableResponse::class) && class_exists(HttpClient::class)) {
83 | if (!interface_exists(StreamableInterface::class)) {
84 | class_alias(TraceableResponseForV4::class, TraceableResponse::class);
85 | class_alias(TraceableHttpClientForV4::class, TraceableHttpClient::class);
86 | } elseif (method_exists(HttpClientInterface::class, 'withOptions')) {
87 | class_alias(TraceableResponseForV6::class, TraceableResponse::class);
88 | class_alias(TraceableHttpClientForV6::class, TraceableHttpClient::class);
89 | } else {
90 | class_alias(TraceableResponseForV5::class, TraceableResponse::class);
91 | class_alias(TraceableHttpClientForV5::class, TraceableHttpClient::class);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------