Skip to content

Commit d526d9d

Browse files
rjd22ste93cry
andauthored
Add doctrine dbal tracing support that is optional to enable (#426)
Co-authored-by: Stefano Arlandini <[email protected]>
1 parent 06dffde commit d526d9d

34 files changed

+1875
-17
lines changed

.github/workflows/static-analysis.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ jobs:
1717

1818
- name: Setup PHP
1919
uses: shivammathur/setup-php@v2
20-
with:
21-
php-version: '7.4'
2220

2321
- name: Install dependencies
2422
run: composer update --no-progress --no-interaction --prefer-dist

.github/workflows/tests.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,59 @@ jobs:
6565
with:
6666
file: './coverage.xml'
6767
fail_ci_if_error: true
68+
69+
missing-optional-packages-tests:
70+
name: Tests without optional packages
71+
runs-on: ubuntu-latest
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
php:
76+
- '7.2'
77+
- '8.0'
78+
dependencies:
79+
- lowest
80+
- highest
81+
82+
steps:
83+
- name: Checkout
84+
uses: actions/checkout@v2
85+
86+
- name: Setup PHP
87+
uses: shivammathur/setup-php@v2
88+
with:
89+
php-version: ${{ matrix.php }}
90+
coverage: xdebug
91+
92+
- name: Setup Problem Matchers for PHPUnit
93+
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
94+
95+
- name: Determine Composer cache directory
96+
id: composer-cache
97+
run: echo "::set-output name=directory::$(composer config cache-dir)"
98+
99+
- name: Cache Composer dependencies
100+
uses: actions/cache@v2
101+
with:
102+
path: ${{ steps.composer-cache.outputs.directory }}
103+
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}
104+
restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-
105+
106+
- name: Remove optional packages
107+
run: composer remove doctrine/dbal doctrine/doctrine-bundle symfony/messenger --dev --no-update
108+
109+
- name: Install highest dependencies
110+
run: composer update --no-progress --no-interaction --prefer-dist
111+
if: ${{ matrix.dependencies == 'highest' }}
112+
113+
- name: Install lowest dependencies
114+
run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest
115+
if: ${{ matrix.dependencies == 'lowest' }}
116+
117+
- name: Run tests
118+
run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml
119+
120+
- name: Upload code coverage
121+
uses: codecov/codecov-action@v1
122+
with:
123+
file: build/coverage-report.xml

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Add support for distributed tracing of SQL queries while using Doctrine DBAL (#426)
56
- Added missing `capture-soft-fails` config schema option (#417)
67
- Deprecate the `Sentry\SentryBundle\EventListener\ConsoleCommandListener` class in favor of its parent class `Sentry\SentryBundle\EventListener\ConsoleListener` (#429)
78

composer.json

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@
2323
"jean85/pretty-package-versions": "^1.5 || ^2.0",
2424
"php-http/discovery": "^1.11",
2525
"sentry/sdk": "^3.1",
26-
"symfony/config": "^3.4.43||^4.4.11||^5.0.11",
27-
"symfony/console": "^3.4.43||^4.4.11||^5.0.11",
28-
"symfony/dependency-injection": "^3.4.43||^4.4.11||^5.0.11",
29-
"symfony/event-dispatcher": "^3.4.43||^4.4.11||^5.0.11",
30-
"symfony/http-kernel": "^3.4.43||^4.4.11||^5.0.11",
26+
"symfony/config": "^3.4.44||^4.4.11||^5.0.11",
27+
"symfony/console": "^3.4.44||^4.4.11||^5.0.11",
28+
"symfony/dependency-injection": "^3.4.44||^4.4.11||^5.0.11",
29+
"symfony/event-dispatcher": "^3.4.44||^4.4.11||^5.0.11",
30+
"symfony/http-kernel": "^3.4.44||^4.4.11||^5.0.11",
3131
"symfony/psr-http-message-bridge": "^2.0",
32-
"symfony/security-core": "^3.4.43||^4.4.11||^5.0.11"
32+
"symfony/security-core": "^3.4.44||^4.4.11||^5.0.11"
3333
},
3434
"require-dev": {
35-
"friendsofphp/php-cs-fixer": "^2.17",
35+
"doctrine/dbal": "^2.10||^3.0",
36+
"doctrine/doctrine-bundle": "^1.12||^2.0",
37+
"friendsofphp/php-cs-fixer": "^2.18",
3638
"jangregor/phpstan-prophecy": "^0.8",
3739
"monolog/monolog": "^1.3||^2.0",
3840
"phpspec/prophecy": "!=1.11.0",
@@ -41,17 +43,19 @@
4143
"phpstan/phpstan": "^0.12",
4244
"phpstan/phpstan-phpunit": "^0.12",
4345
"phpunit/phpunit": "^8.5||^9.0",
44-
"symfony/browser-kit": "^3.4.43||^4.4.11||^5.0.11",
45-
"symfony/framework-bundle": "^3.4.43||^4.4.11||^5.0.11",
46+
"symfony/browser-kit": "^3.4.44||^4.4.12||^5.0.11",
47+
"symfony/dom-crawler": "^3.4.44||^4.4.12||^5.0.11",
48+
"symfony/framework-bundle": "^3.4.44||^4.4.12||^5.0.11",
4649
"symfony/messenger": "^4.4.11||^5.0.11",
4750
"symfony/monolog-bundle": "^3.4",
4851
"symfony/phpunit-bridge": "^5.0",
4952
"symfony/polyfill-php80": "^1.22",
50-
"symfony/yaml": "^3.4.43||^4.4.11||^5.0.11",
53+
"symfony/yaml": "^3.4.44||^4.4.11||^5.0.11",
5154
"vimeo/psalm": "^4.3"
5255
},
5356
"suggest": {
54-
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler."
57+
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.",
58+
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry."
5559
},
5660
"autoload": {
5761
"files": [

phpstan-baseline.neon

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,91 @@ parameters:
1010
count: 1
1111
path: src/DependencyInjection/Configuration.php
1212

13+
-
14+
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:end\\(\\)\\.$#"
15+
count: 1
16+
path: src/DependencyInjection/Configuration.php
17+
1318
-
1419
message: "#^Else branch is unreachable because previous condition is always true\\.$#"
1520
count: 1
1621
path: src/EventListener/ErrorListener.php
1722

23+
-
24+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$driverOptions with no value type specified in iterable type array\\.$#"
25+
count: 1
26+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
27+
28+
-
29+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$password with no typehint specified\\.$#"
30+
count: 1
31+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
32+
33+
-
34+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$username with no typehint specified\\.$#"
35+
count: 1
36+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
37+
38+
-
39+
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:connect\\(\\)\\.$#"
40+
count: 1
41+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
42+
43+
-
44+
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:getDatabasePlatform\\(\\)\\.$#"
45+
count: 2
46+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
47+
48+
-
49+
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:getSchemaManager\\(\\)\\.$#"
50+
count: 1
51+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
52+
53+
-
54+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:convertException\\(\\) has parameter \\$message with no typehint specified\\.$#"
55+
count: 1
56+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
57+
58+
-
59+
message: "#^Call to an undefined method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:convertException\\(\\)\\.$#"
60+
count: 1
61+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
62+
63+
-
64+
message: "#^Parameter \\#2 \\$query of class Doctrine\\\\DBAL\\\\Exception\\\\DriverException constructor expects Doctrine\\\\DBAL\\\\Query\\|null, Doctrine\\\\DBAL\\\\Driver\\\\Exception given\\.$#"
65+
count: 1
66+
path: src/Tracing/Doctrine/DBAL/TracingDriver.php
67+
68+
-
69+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:prepare\\(\\) has parameter \\$sql with no typehint specified\\.$#"
70+
count: 1
71+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
72+
73+
-
74+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:query\\(\\) has parameter \\$args with no typehint specified\\.$#"
75+
count: 1
76+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
77+
78+
-
79+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:exec\\(\\) has parameter \\$sql with no typehint specified\\.$#"
80+
count: 1
81+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
82+
83+
-
84+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:errorCode\\(\\) has no return typehint specified\\.$#"
85+
count: 1
86+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
87+
88+
-
89+
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\\\Connection\\:\\:errorCode\\(\\)\\.$#"
90+
count: 1
91+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
92+
93+
-
94+
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:errorInfo\\(\\) has no return typehint specified\\.$#"
95+
count: 1
96+
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php
97+
1898
-
1999
message: "#^Class Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\GetResponseForExceptionEvent not found\\.$#"
20100
count: 1
@@ -115,3 +195,28 @@ parameters:
115195
count: 2
116196
path: tests/EventListener/SubRequestListenerTest.php
117197

198+
-
199+
message: "#^Trying to mock an undefined method getName\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\.$#"
200+
count: 1
201+
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php
202+
203+
-
204+
message: "#^Trying to mock an undefined method getDatabase\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\.$#"
205+
count: 1
206+
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php
207+
208+
-
209+
message: "#^Parameter \\#1 \\$driverException of class Doctrine\\\\DBAL\\\\Exception\\\\DriverException constructor expects Doctrine\\\\DBAL\\\\Driver\\\\Exception, string given\\.$#"
210+
count: 1
211+
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php
212+
213+
-
214+
message: "#^Parameter \\#2 \\$query of class Doctrine\\\\DBAL\\\\Exception\\\\DriverException constructor expects Doctrine\\\\DBAL\\\\Query\\|null, Doctrine\\\\DBAL\\\\Driver\\\\Exception&PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#"
215+
count: 1
216+
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php
217+
218+
-
219+
message: "#^Trying to mock an undefined method convertException\\(\\) on class Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Doctrine\\\\DBAL\\\\StubExceptionConverterDriverInterface\\.$#"
220+
count: 1
221+
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php
222+

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ parameters:
1010
- tests/End2End/App
1111
dynamicConstantNames:
1212
- Symfony\Component\HttpKernel\Kernel::VERSION
13+
- Doctrine\DBAL\Version::VERSION

psalm-baseline.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="4.4.1@9fd7a7d885b3a216cff8dec9d8c21a132f275224">
2+
<files psalm-version="4.3.0@6f916553a8de3c5c2483162b6cc01b21b24e4d1d">
33
<file src="src/EventListener/ConsoleCommandListener.php">
44
<InvalidExtendClass occurrences="1">
55
<code>ConsoleListener</code>
@@ -8,4 +8,12 @@
88
<code>public function __construct(HubInterface $hub)</code>
99
</MethodSignatureMismatch>
1010
</file>
11+
<file src="src/Tracing/Doctrine/DBAL/TracingDriver.php">
12+
<TooManyArguments occurrences="1">
13+
<code>getSchemaManager</code>
14+
</TooManyArguments>
15+
<UndefinedClass occurrences="1">
16+
<code>ExceptionConverter</code>
17+
</UndefinedClass>
18+
</file>
1119
</files>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\DependencyInjection\Compiler;
6+
7+
use Doctrine\DBAL\Driver\ResultStatement;
8+
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
9+
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
10+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\Definition;
13+
use Symfony\Component\DependencyInjection\Reference;
14+
15+
final class DbalTracingPass implements CompilerPassInterface
16+
{
17+
/**
18+
* This is the format used by the DoctrineBundle bundle to register the
19+
* services for each connection.
20+
*/
21+
private const CONNECTION_SERVICE_NAME_FORMAT = 'doctrine.dbal.%s_connection';
22+
23+
/**
24+
* This is the format used by the DoctrineBundle bundle to register the
25+
* services for each connection's configuration.
26+
*/
27+
private const CONNECTION_CONFIG_SERVICE_NAME_FORMAT = 'doctrine.dbal.%s_connection.configuration';
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function process(ContainerBuilder $container): void
33+
{
34+
if (!$container->hasParameter('doctrine.connections')) {
35+
return;
36+
}
37+
38+
/** @var string[] $connections */
39+
$connections = $container->getParameter('doctrine.connections');
40+
41+
/** @var string[] $connectionsToTrace */
42+
$connectionsToTrace = $container->getParameter('sentry.tracing.dbal.connections');
43+
44+
foreach ($connectionsToTrace as $connectionName) {
45+
if (!\in_array(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName), $connections, true)) {
46+
continue;
47+
}
48+
49+
if (!interface_exists(ResultStatement::class)) {
50+
$this->configureConnectionForDoctrineDBALVersion3($container, $connectionName);
51+
} else {
52+
$this->configureConnectionForDoctrineDBALVersion2($container, $connectionName);
53+
}
54+
}
55+
}
56+
57+
private function configureConnectionForDoctrineDBALVersion3(ContainerBuilder $container, string $connectionName): void
58+
{
59+
$configurationDefinition = $container->getDefinition(sprintf(self::CONNECTION_CONFIG_SERVICE_NAME_FORMAT, $connectionName));
60+
$setMiddlewaresMethodCallArguments = $this->getSetMiddlewaresMethodCallArguments($configurationDefinition);
61+
$setMiddlewaresMethodCallArguments[0] = array_merge($setMiddlewaresMethodCallArguments[0] ?? [], [new Reference(TracingDriverMiddleware::class)]);
62+
63+
$configurationDefinition
64+
->removeMethodCall('setMiddlewares')
65+
->addMethodCall('setMiddlewares', $setMiddlewaresMethodCallArguments);
66+
}
67+
68+
private function configureConnectionForDoctrineDBALVersion2(ContainerBuilder $container, string $connectionName): void
69+
{
70+
$connectionDefinition = $container->getDefinition(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName));
71+
$connectionDefinition->setConfigurator([new Reference(ConnectionConfigurator::class), 'configure']);
72+
}
73+
74+
/**
75+
* @return mixed[]
76+
*/
77+
private function getSetMiddlewaresMethodCallArguments(Definition $definition): array
78+
{
79+
foreach ($definition->getMethodCalls() as $methodCall) {
80+
if ('setMiddlewares' === $methodCall[0]) {
81+
return $methodCall[1];
82+
}
83+
}
84+
85+
return [];
86+
}
87+
}

0 commit comments

Comments
 (0)