Skip to content

Commit

Permalink
Add doctrine dbal tracing support that is optional to enable (#426)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefano Arlandini <[email protected]>
  • Loading branch information
rjd22 and ste93cry committed Feb 17, 2021
1 parent 06dffde commit d526d9d
Show file tree
Hide file tree
Showing 34 changed files with 1,875 additions and 17 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/static-analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ jobs:

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'

- name: Install dependencies
run: composer update --no-progress --no-interaction --prefer-dist
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,59 @@ jobs:
with:
file: './coverage.xml'
fail_ci_if_error: true

missing-optional-packages-tests:
name: Tests without optional packages
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php:
- '7.2'
- '8.0'
dependencies:
- lowest
- highest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug

- name: Setup Problem Matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Determine Composer cache directory
id: composer-cache
run: echo "::set-output name=directory::$(composer config cache-dir)"

- name: Cache Composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.directory }}
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-${{ matrix.dependencies }}-

- name: Remove optional packages
run: composer remove doctrine/dbal doctrine/doctrine-bundle symfony/messenger --dev --no-update

- name: Install highest dependencies
run: composer update --no-progress --no-interaction --prefer-dist
if: ${{ matrix.dependencies == 'highest' }}

- name: Install lowest dependencies
run: composer update --no-progress --no-interaction --prefer-dist --prefer-lowest
if: ${{ matrix.dependencies == 'lowest' }}

- name: Run tests
run: vendor/bin/phpunit --coverage-clover=build/coverage-report.xml

- name: Upload code coverage
uses: codecov/codecov-action@v1
with:
file: build/coverage-report.xml
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

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

Expand Down
26 changes: 15 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@
"jean85/pretty-package-versions": "^1.5 || ^2.0",
"php-http/discovery": "^1.11",
"sentry/sdk": "^3.1",
"symfony/config": "^3.4.43||^4.4.11||^5.0.11",
"symfony/console": "^3.4.43||^4.4.11||^5.0.11",
"symfony/dependency-injection": "^3.4.43||^4.4.11||^5.0.11",
"symfony/event-dispatcher": "^3.4.43||^4.4.11||^5.0.11",
"symfony/http-kernel": "^3.4.43||^4.4.11||^5.0.11",
"symfony/config": "^3.4.44||^4.4.11||^5.0.11",
"symfony/console": "^3.4.44||^4.4.11||^5.0.11",
"symfony/dependency-injection": "^3.4.44||^4.4.11||^5.0.11",
"symfony/event-dispatcher": "^3.4.44||^4.4.11||^5.0.11",
"symfony/http-kernel": "^3.4.44||^4.4.11||^5.0.11",
"symfony/psr-http-message-bridge": "^2.0",
"symfony/security-core": "^3.4.43||^4.4.11||^5.0.11"
"symfony/security-core": "^3.4.44||^4.4.11||^5.0.11"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.17",
"doctrine/dbal": "^2.10||^3.0",
"doctrine/doctrine-bundle": "^1.12||^2.0",
"friendsofphp/php-cs-fixer": "^2.18",
"jangregor/phpstan-prophecy": "^0.8",
"monolog/monolog": "^1.3||^2.0",
"phpspec/prophecy": "!=1.11.0",
Expand All @@ -41,17 +43,19 @@
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^8.5||^9.0",
"symfony/browser-kit": "^3.4.43||^4.4.11||^5.0.11",
"symfony/framework-bundle": "^3.4.43||^4.4.11||^5.0.11",
"symfony/browser-kit": "^3.4.44||^4.4.12||^5.0.11",
"symfony/dom-crawler": "^3.4.44||^4.4.12||^5.0.11",
"symfony/framework-bundle": "^3.4.44||^4.4.12||^5.0.11",
"symfony/messenger": "^4.4.11||^5.0.11",
"symfony/monolog-bundle": "^3.4",
"symfony/phpunit-bridge": "^5.0",
"symfony/polyfill-php80": "^1.22",
"symfony/yaml": "^3.4.43||^4.4.11||^5.0.11",
"symfony/yaml": "^3.4.44||^4.4.11||^5.0.11",
"vimeo/psalm": "^4.3"
},
"suggest": {
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler."
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.",
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry."
},
"autoload": {
"files": [
Expand Down
105 changes: 105 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,91 @@ parameters:
count: 1
path: src/DependencyInjection/Configuration.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\:\\:end\\(\\)\\.$#"
count: 1
path: src/DependencyInjection/Configuration.php

-
message: "#^Else branch is unreachable because previous condition is always true\\.$#"
count: 1
path: src/EventListener/ErrorListener.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$driverOptions with no value type specified in iterable type array\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$password with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:connect\\(\\) has parameter \\$username with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:connect\\(\\)\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:getDatabasePlatform\\(\\)\\.$#"
count: 2
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\|Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:getSchemaManager\\(\\)\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriver\\:\\:convertException\\(\\) has parameter \\$message with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Call to an undefined method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\Compatibility\\\\ExceptionConverterDriverInterface\\:\\:convertException\\(\\)\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Parameter \\#2 \\$query of class Doctrine\\\\DBAL\\\\Exception\\\\DriverException constructor expects Doctrine\\\\DBAL\\\\Query\\|null, Doctrine\\\\DBAL\\\\Driver\\\\Exception given\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriver.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:prepare\\(\\) has parameter \\$sql with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:query\\(\\) has parameter \\$args with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:exec\\(\\) has parameter \\$sql with no typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:errorCode\\(\\) has no return typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Call to an undefined method Doctrine\\\\DBAL\\\\Driver\\\\Connection\\:\\:errorCode\\(\\)\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Doctrine\\\\DBAL\\\\TracingDriverConnection\\:\\:errorInfo\\(\\) has no return typehint specified\\.$#"
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Class Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\GetResponseForExceptionEvent not found\\.$#"
count: 1
Expand Down Expand Up @@ -115,3 +195,28 @@ parameters:
count: 2
path: tests/EventListener/SubRequestListenerTest.php

-
message: "#^Trying to mock an undefined method getName\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\.$#"
count: 1
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php

-
message: "#^Trying to mock an undefined method getDatabase\\(\\) on class Doctrine\\\\DBAL\\\\Driver\\.$#"
count: 1
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php

-
message: "#^Parameter \\#1 \\$driverException of class Doctrine\\\\DBAL\\\\Exception\\\\DriverException constructor expects Doctrine\\\\DBAL\\\\Driver\\\\Exception, string given\\.$#"
count: 1
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php

-
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\\.$#"
count: 1
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php

-
message: "#^Trying to mock an undefined method convertException\\(\\) on class Sentry\\\\SentryBundle\\\\Tests\\\\Tracing\\\\Doctrine\\\\DBAL\\\\StubExceptionConverterDriverInterface\\.$#"
count: 1
path: tests/Tracing/Doctrine/DBAL/TracingDriverTest.php

1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ parameters:
- tests/End2End/App
dynamicConstantNames:
- Symfony\Component\HttpKernel\Kernel::VERSION
- Doctrine\DBAL\Version::VERSION
10 changes: 9 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.4.1@9fd7a7d885b3a216cff8dec9d8c21a132f275224">
<files psalm-version="4.3.0@6f916553a8de3c5c2483162b6cc01b21b24e4d1d">
<file src="src/EventListener/ConsoleCommandListener.php">
<InvalidExtendClass occurrences="1">
<code>ConsoleListener</code>
Expand All @@ -8,4 +8,12 @@
<code>public function __construct(HubInterface $hub)</code>
</MethodSignatureMismatch>
</file>
<file src="src/Tracing/Doctrine/DBAL/TracingDriver.php">
<TooManyArguments occurrences="1">
<code>getSchemaManager</code>
</TooManyArguments>
<UndefinedClass occurrences="1">
<code>ExceptionConverter</code>
</UndefinedClass>
</file>
</files>
87 changes: 87 additions & 0 deletions src/DependencyInjection/Compiler/DbalTracingPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\DependencyInjection\Compiler;

use Doctrine\DBAL\Driver\ResultStatement;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class DbalTracingPass implements CompilerPassInterface
{
/**
* This is the format used by the DoctrineBundle bundle to register the
* services for each connection.
*/
private const CONNECTION_SERVICE_NAME_FORMAT = 'doctrine.dbal.%s_connection';

/**
* This is the format used by the DoctrineBundle bundle to register the
* services for each connection's configuration.
*/
private const CONNECTION_CONFIG_SERVICE_NAME_FORMAT = 'doctrine.dbal.%s_connection.configuration';

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container): void
{
if (!$container->hasParameter('doctrine.connections')) {
return;
}

/** @var string[] $connections */
$connections = $container->getParameter('doctrine.connections');

/** @var string[] $connectionsToTrace */
$connectionsToTrace = $container->getParameter('sentry.tracing.dbal.connections');

foreach ($connectionsToTrace as $connectionName) {
if (!\in_array(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName), $connections, true)) {
continue;
}

if (!interface_exists(ResultStatement::class)) {
$this->configureConnectionForDoctrineDBALVersion3($container, $connectionName);
} else {
$this->configureConnectionForDoctrineDBALVersion2($container, $connectionName);
}
}
}

private function configureConnectionForDoctrineDBALVersion3(ContainerBuilder $container, string $connectionName): void
{
$configurationDefinition = $container->getDefinition(sprintf(self::CONNECTION_CONFIG_SERVICE_NAME_FORMAT, $connectionName));
$setMiddlewaresMethodCallArguments = $this->getSetMiddlewaresMethodCallArguments($configurationDefinition);
$setMiddlewaresMethodCallArguments[0] = array_merge($setMiddlewaresMethodCallArguments[0] ?? [], [new Reference(TracingDriverMiddleware::class)]);

$configurationDefinition
->removeMethodCall('setMiddlewares')
->addMethodCall('setMiddlewares', $setMiddlewaresMethodCallArguments);
}

private function configureConnectionForDoctrineDBALVersion2(ContainerBuilder $container, string $connectionName): void
{
$connectionDefinition = $container->getDefinition(sprintf(self::CONNECTION_SERVICE_NAME_FORMAT, $connectionName));
$connectionDefinition->setConfigurator([new Reference(ConnectionConfigurator::class), 'configure']);
}

/**
* @return mixed[]
*/
private function getSetMiddlewaresMethodCallArguments(Definition $definition): array
{
foreach ($definition->getMethodCalls() as $methodCall) {
if ('setMiddlewares' === $methodCall[0]) {
return $methodCall[1];
}
}

return [];
}
}
Loading

0 comments on commit d526d9d

Please sign in to comment.