Skip to content

Commit

Permalink
Implementation of optional sentry tracing with twig
Browse files Browse the repository at this point in the history
  • Loading branch information
rjd22 committed Feb 21, 2021
1 parent d526d9d commit 6d29db8
Show file tree
Hide file tree
Showing 17 changed files with 271 additions and 2 deletions.
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 Twig template rendering (#430)
- 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
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@
"symfony/monolog-bundle": "^3.4",
"symfony/phpunit-bridge": "^5.0",
"symfony/polyfill-php80": "^1.22",
"symfony/twig-bundle": "^3.4.44||^4.4.12||^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.",
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry."
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry.",
"symfony/twig-bundle": "Allow distributed tracing of twig template rendering using Sentry."
},
"autoload": {
"files": [
Expand Down
11 changes: 10 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ parameters:
count: 1
path: src/Tracing/Doctrine/DBAL/TracingDriverConnection.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Twig\\\\TwigTracingExtension\\:\\:enter\\(\\) has parameter \\$profile with no value type specified in iterable type Twig\\\\Profiler\\\\Profile\\.$#"
count: 1
path: src/Tracing/Twig/TwigTracingExtension.php

-
message: "#^Method Sentry\\\\SentryBundle\\\\Tracing\\\\Twig\\\\TwigTracingExtension\\:\\:leave\\(\\) has parameter \\$profile with no value type specified in iterable type Twig\\\\Profiler\\\\Profile\\.$#"
count: 1
path: src/Tracing/Twig/TwigTracingExtension.php

-
message: "#^Class Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\GetResponseForExceptionEvent not found\\.$#"
count: 1
Expand Down Expand Up @@ -219,4 +229,3 @@ parameters:
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

3 changes: 3 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ private function addDistributedTracingSection(ArrayNodeDefinition $rootNode): vo
->end()
->end()
->end()
->arrayNode('twig')
->canBeEnabled()
->end()
->end()
->end()
->end();
Expand Down
14 changes: 14 additions & 0 deletions src/DependencyInjection/SentryExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Sentry\SentryBundle\SentryBundle;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
use Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension;
use Sentry\Serializer\RepresentationSerializer;
use Sentry\Serializer\Serializer;
use Sentry\Transport\TransportFactoryInterface;
Expand Down Expand Up @@ -62,6 +63,7 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container
$this->registerErrorListenerConfiguration($container, $mergedConfig);
$this->registerMessengerListenerConfiguration($container, $mergedConfig['messenger']);
$this->registerTracingConfiguration($container, $mergedConfig['tracing']);
$this->registerTracingTwigExtensionConfiguration($container, $mergedConfig['tracing']);
}

/**
Expand Down Expand Up @@ -173,6 +175,18 @@ private function registerTracingConfiguration(ContainerBuilder $container, array
}
}

/**
* @param array<string, mixed> $config
*/
private function registerTracingTwigExtensionConfiguration(ContainerBuilder $container, array $config): void
{
$isConfigEnabled = $this->isConfigEnabled($container, $config['twig']);

if (!$isConfigEnabled) {
$container->removeDefinition(TwigTracingExtension::class);
}
}

/**
* @param string[] $integrations
* @param array<string, mixed> $config
Expand Down
4 changes: 4 additions & 0 deletions src/Resources/config/schema/sentry-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<xsd:complexType name="tracing">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="dbal" type="tracing-dbal" minOccurs="0" maxOccurs="1" />
<xsd:element name="twig" type="tracing-twig" minOccurs="0" maxOccurs="1" />
</xsd:choice>
</xsd:complexType>

Expand All @@ -94,4 +95,7 @@

<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="tracing-twig">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
</xsd:schema>
5 changes: 5 additions & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
<argument type="service" id="Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware" />
</service>

<service id="Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension" class="Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension">
<argument type="service" id="Sentry\State\HubInterface" />
<tag name="twig.extension" />
</service>

<service id="Sentry\Integration\RequestFetcherInterface" class="Sentry\SentryBundle\Integration\RequestFetcher">
<argument type="service" id="Symfony\Component\HttpFoundation\RequestStack" />
<argument type="service" id="Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface" on-invalid="null" />
Expand Down
64 changes: 64 additions & 0 deletions src/Tracing/Twig/TwigTracingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Tracing\Twig;

use Sentry\State\HubInterface;
use Sentry\Tracing\SpanContext;
use SplObjectStorage;
use Twig\Extension\AbstractExtension;
use Twig\Profiler\NodeVisitor\ProfilerNodeVisitor;
use Twig\Profiler\Profile;

final class TwigTracingExtension extends AbstractExtension
{
/**
* @var HubInterface The current hub
*/
private $hub;

/**
* @var SplObjectStorage<object, \Sentry\Tracing\Span>
*/
private $events;

/**
* @param HubInterface $hub The current hub
*/
public function __construct(HubInterface $hub)
{
$this->hub = $hub;
$this->events = new SplObjectStorage();
}

public function enter(Profile $profile): void
{
$transaction = $this->hub->getTransaction();

if (null === $transaction || !$profile->isTemplate()) {
return;
}

$spanContext = new SpanContext();
$spanContext->setOp('twig.template');
$spanContext->setDescription($profile->getName());

$this->events[$profile] = $transaction->startChild($spanContext);
}

public function leave(Profile $profile): void
{
if (empty($this->events[$profile]) || !$profile->isTemplate()) {
return;
}

$this->events[$profile]->finish();
unset($this->events[$profile]);
}

public function getNodeVisitors(): array
{
return [new ProfilerNodeVisitor(static::class)];
}
}
3 changes: 3 additions & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
'enabled' => false,
'connections' => class_exists(DoctrineBundle::class) ? ['%doctrine.default_connection%'] : [],
],
'twig' => [
'enabled' => false,
],
],
];

Expand Down
3 changes: 3 additions & 0 deletions tests/DependencyInjection/Fixtures/php/full.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
'enabled' => false,
'connections' => ['default'],
],
'twig' => [
'enabled' => false,
],
],
]);
14 changes: 14 additions & 0 deletions tests/DependencyInjection/Fixtures/php/twig_tracing_enabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\ContainerBuilder;

/** @var ContainerBuilder $container */
$container->loadFromExtension('sentry', [
'tracing' => [
'twig' => [
'enabled' => true,
],
],
]);
1 change: 1 addition & 0 deletions tests/DependencyInjection/Fixtures/xml/full.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<sentry:dbal enabled="false">
<sentry:connection>default</sentry:connection>
</sentry:dbal>
<sentry:twig enabled="false" />
</sentry:tracing>
</sentry:config>
</container>
14 changes: 14 additions & 0 deletions tests/DependencyInjection/Fixtures/xml/twig_tracing_enabled.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sentry="https://sentry.io/schema/dic/sentry-symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
https://sentry.io/schema/dic/sentry-symfony https://sentry.io/schema/dic/sentry-symfony/sentry-1.0.xsd">

<sentry:config>
<sentry:tracing>
<sentry:twig enabled="true" />
</sentry:tracing>
</sentry:config>
</container>
2 changes: 2 additions & 0 deletions tests/DependencyInjection/Fixtures/yml/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ sentry:
enabled: false
connections:
- enabled
twig:
enabled: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sentry:
tracing:
twig:
enabled: true
15 changes: 15 additions & 0 deletions tests/DependencyInjection/SentryExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Sentry\SentryBundle\SentryBundle;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
use Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension;
use Sentry\Serializer\RepresentationSerializer;
use Sentry\Serializer\Serializer;
use Sentry\Transport\TransportFactoryInterface;
Expand Down Expand Up @@ -286,6 +287,20 @@ public function testTracingDriverMiddlewareIsRemovedWhenDbalTracingIsDisabled():
$this->assertEmpty($container->getParameter('sentry.tracing.dbal.connections'));
}

public function testTwigTracingExtensionIsConfiguredWhenTwigTracingIsEnabled(): void
{
$container = $this->createContainerFromFixture('twig_tracing_enabled');

$this->assertTrue($container->hasDefinition(TwigTracingExtension::class));
}

public function testTwigTracingExtensionIsRemovedWhenTwigTracingIsDisabled(): void
{
$container = $this->createContainerFromFixture('full');

$this->assertFalse($container->hasDefinition(TwigTracingExtension::class));
}

private function createContainerFromFixture(string $fixtureFile): ContainerBuilder
{
$container = new ContainerBuilder(new EnvPlaceholderParameterBag([
Expand Down
111 changes: 111 additions & 0 deletions tests/Tracing/Twig/TwigTracingExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Tests\Tracing\Twig;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sentry\SentryBundle\Tracing\Twig\TwigTracingExtension;
use Sentry\State\HubInterface;
use Sentry\Tracing\Transaction;
use Sentry\Tracing\TransactionContext;
use Twig\Profiler\Profile;

final class TwigTracingExtensionTest extends TestCase
{
/**
* @var MockObject&HubInterface
*/
private $hub;

/**
* @var TwigTracingExtension
*/
private $listener;

protected function setUp(): void
{
$this->hub = $this->createMock(HubInterface::class);
$this->listener = new TwigTracingExtension($this->hub);
}

public function testThatTwigEnterProfileIgnoresTracingWhenTransactionIsNotStarted(): void
{
$this->hub->expects($this->once())
->method('getTransaction')
->willReturn(null);

$this->listener->enter(new Profile('main', Profile::TEMPLATE));
}

public function testThatTwigEnterProfileIgnoresTracingWhenNotATemplate(): void
{
$this->hub->expects($this->once())
->method('getTransaction')
->willReturn(new Transaction(new TransactionContext()));

$this->listener->enter(new Profile('main', Profile::ROOT));
}

public function testThatTwigLeaveProfileIgnoresTracingWhenTransactionIsNotStarted(): void
{
$this->hub->expects($this->once())
->method('getTransaction')
->willReturn(null);

$profile = new Profile('main', Profile::TEMPLATE);

$this->listener->enter($profile);
$this->listener->leave($profile);
}

public function testThatTwigLeaveProfileIgnoresTracingWhenNotATemplate(): void
{
$this->hub->expects($this->once())
->method('getTransaction')
->willReturn(new Transaction(new TransactionContext()));

$profile = new Profile('main', Profile::ROOT);

$this->listener->enter($profile);
$this->listener->leave($profile);
}

public function testThatTwigEnterProfileAttachesAChildSpanWhenTransactionStarted(): void
{
$transaction = new Transaction(new TransactionContext());
$transaction->initSpanRecorder();

$this->hub->expects($this->once())
->method('getTransaction')
->willReturn($transaction);

$this->listener->enter(new Profile('main', Profile::TEMPLATE));

$spans = $transaction->getSpanRecorder()->getSpans();

$this->assertCount(2, $spans);
$this->assertNull($spans[1]->getEndTimestamp());
}

public function testThatTwigLeaveProfileFinishesTheChildSpanWhenChildSpanStarted(): void
{
$transaction = new Transaction(new TransactionContext());
$transaction->initSpanRecorder();

$this->hub->expects($this->once())
->method('getTransaction')
->willReturn($transaction);

$profile = new Profile('main', Profile::TEMPLATE);

$this->listener->enter($profile);
$this->listener->leave($profile);

$spans = $transaction->getSpanRecorder()->getSpans();

$this->assertCount(2, $spans);
$this->assertNotNull($spans[1]->getEndTimestamp());
}
}

0 comments on commit 6d29db8

Please sign in to comment.