Skip to content

Commit a269147

Browse files
committed
Initial implementation of sentry tracing with twig and doctrine support
1 parent ed64c02 commit a269147

File tree

5 files changed

+310
-0
lines changed

5 files changed

+310
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\EventListener\Tracing;
6+
7+
use Doctrine\DBAL\Logging\SQLLogger;
8+
use Sentry\State\HubInterface;
9+
use Sentry\Tracing\Span;
10+
use Sentry\Tracing\SpanContext;
11+
12+
final class DbalListener implements SQLLogger
13+
{
14+
/**
15+
* @var HubInterface The current hub
16+
*/
17+
private $hub;
18+
19+
/**
20+
* @var Span
21+
*/
22+
private $querySpan;
23+
24+
/**
25+
* @param HubInterface $hub The current hub
26+
*/
27+
public function __construct(HubInterface $hub)
28+
{
29+
$this->hub = $hub;
30+
}
31+
32+
/**
33+
* @param string $sql
34+
* @param array|null $params
35+
* @param array|null $types
36+
*/
37+
public function startQuery($sql, ?array $params = null, ?array $types = null)
38+
{
39+
$transaction = $this->hub->getTransaction();
40+
41+
if (!$transaction) {
42+
return;
43+
}
44+
45+
$spanContext = new SpanContext();
46+
$spanContext->setOp('doctrine.query');
47+
$spanContext->setDescription($sql);
48+
49+
$this->querySpan = $transaction->startChild($spanContext);
50+
}
51+
52+
public function stopQuery()
53+
{
54+
if ($this->querySpan) {
55+
$this->querySpan->finish();
56+
}
57+
}
58+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\EventListener\Tracing;
6+
7+
use Sentry\SentryBundle\EventListener\RequestListenerControllerEvent;
8+
use Sentry\SentryBundle\EventListener\RequestListenerRequestEvent;
9+
use Sentry\SentryBundle\EventListener\RequestListenerResponseEvent;
10+
use Sentry\SentryBundle\EventListener\RequestListenerTerminateEvent;
11+
use Sentry\State\HubInterface;
12+
use Sentry\Tracing\Span;
13+
use Sentry\Tracing\SpanContext;
14+
use Sentry\Tracing\Transaction;
15+
use Sentry\Tracing\TransactionContext;
16+
use Symfony\Component\HttpFoundation\Request;
17+
18+
final class RequestListener
19+
{
20+
/**
21+
* @var HubInterface The current hub
22+
*/
23+
private $hub;
24+
25+
/**
26+
* @var Transaction
27+
*/
28+
private $transaction;
29+
30+
/**
31+
* @var Span
32+
*/
33+
private $requestSpan;
34+
35+
/**
36+
* @var Span
37+
*/
38+
private $controllerSpan;
39+
40+
/**
41+
* @var Span
42+
*/
43+
private $responseSpan;
44+
45+
/**
46+
* Constructor.
47+
*
48+
* @param HubInterface $hub The current hub
49+
*/
50+
public function __construct(HubInterface $hub)
51+
{
52+
$this->hub = $hub;
53+
}
54+
55+
/**
56+
* @param RequestListenerRequestEvent $event The event
57+
*/
58+
public function handleKernelRequestEvent(RequestListenerRequestEvent $event): void
59+
{
60+
if (!$event->isMasterRequest()) {
61+
return;
62+
}
63+
64+
/** @var Request $request */
65+
$request = $event->getRequest();
66+
$requestStartTime = $request->server->get('REQUEST_TIME_FLOAT', microtime(true));
67+
68+
$context = new TransactionContext();
69+
$context->setOp('http.server');
70+
$context->setName($request->getUri());
71+
$context->setData([
72+
'url' => $request->getUri(),
73+
'method' => strtoupper($request->getMethod()),
74+
]);
75+
$context->setStartTimestamp($requestStartTime);
76+
77+
$transaction = $this->hub->startTransaction($context);
78+
79+
// Setting the Transaction on the Hub
80+
$this->hub->setSpan($transaction);
81+
82+
$spanContext = new SpanContext();
83+
$spanContext->setOp('kernel.request');
84+
$spanContext->setStartTimestamp($requestStartTime);
85+
86+
$this->requestSpan = $transaction->startChild($spanContext);
87+
$this->transaction = $transaction;
88+
}
89+
90+
/**
91+
* @param RequestListenerControllerEvent $event The event
92+
*/
93+
public function handleKernelControllerEvent(RequestListenerControllerEvent $event): void
94+
{
95+
if (!$event->isMasterRequest() || !$this->transaction) {
96+
return;
97+
}
98+
99+
if ($this->requestSpan) {
100+
$this->requestSpan->finish();
101+
}
102+
103+
$spanContext = new SpanContext();
104+
$spanContext->setOp('controller');
105+
106+
$this->controllerSpan = $this->transaction->startChild($spanContext);
107+
}
108+
109+
/**
110+
* @param RequestListenerResponseEvent $event The event
111+
*/
112+
public function handleKernelResponseEvent(RequestListenerResponseEvent $event): void
113+
{
114+
if (!$event->isMasterRequest() || !$this->transaction) {
115+
return;
116+
}
117+
118+
$this->transaction->setHttpStatus($event->getResponse()->getStatusCode());
119+
120+
if ($this->controllerSpan) {
121+
$this->controllerSpan->finish();
122+
}
123+
124+
$spanContext = new SpanContext();
125+
$spanContext->setOp('kernel.response');
126+
127+
$this->responseSpan = $this->transaction->startChild($spanContext);
128+
}
129+
130+
/**
131+
* @param RequestListenerTerminateEvent $event The event
132+
*/
133+
public function handleKernelTerminateEvent(RequestListenerTerminateEvent $event): void
134+
{
135+
if (!$event->isMasterRequest() || !$this->transaction) {
136+
return;
137+
}
138+
139+
if ($this->responseSpan) {
140+
$this->responseSpan->finish();
141+
}
142+
143+
$this->transaction->finish();
144+
}
145+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\SentryBundle\EventListener\Tracing;
6+
7+
use Sentry\State\HubInterface;
8+
use Sentry\Tracing\SpanContext;
9+
use SplObjectStorage;
10+
use Twig\Extension\ProfilerExtension;
11+
use Twig\Profiler\Profile;
12+
13+
final class TwigListener extends ProfilerExtension
14+
{
15+
/**
16+
* @var HubInterface The current hub
17+
*/
18+
private $hub;
19+
20+
/**
21+
* @var SplObjectStorage
22+
*/
23+
private $events;
24+
25+
/**
26+
* @param HubInterface $hub The current hub
27+
* @param Profile $profile
28+
*/
29+
public function __construct(HubInterface $hub, Profile $profile)
30+
{
31+
parent::__construct($profile);
32+
33+
$this->hub = $hub;
34+
$this->events = new SplObjectStorage();
35+
}
36+
37+
/**
38+
* @param Profile $profile
39+
*/
40+
public function enter(Profile $profile): void
41+
{
42+
$transaction = $this->hub->getTransaction();
43+
44+
if ($transaction && $profile->isTemplate()) {
45+
$spanContext = new SpanContext();
46+
$spanContext->setOp('twig.template');
47+
$spanContext->setDescription($profile->getName());
48+
49+
$this->events[$profile] = $transaction->startChild($spanContext);
50+
}
51+
52+
parent::enter($profile);
53+
}
54+
55+
/**
56+
* @param Profile $profile
57+
*/
58+
public function leave(Profile $profile): void
59+
{
60+
parent::leave($profile);
61+
62+
if ($profile->isTemplate() && !empty($this->events[$profile])) {
63+
$this->events[$profile]->finish();
64+
unset($this->events[$profile]);
65+
}
66+
}
67+
}

src/Resources/config/services.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,32 @@
5959
<tag name="kernel.event_listener" event="Symfony\Component\Messenger\Event\WorkerMessageHandledEvent" method="handleWorkerMessageHandledEvent" priority="50" />
6060
</service>
6161

62+
<service id="Sentry\SentryBundle\EventListener\Tracing\RequestListener" class="Sentry\SentryBundle\EventListener\Tracing\RequestListener">
63+
<argument type="service" id="Sentry\State\HubInterface" />
64+
65+
<tag name="kernel.event_listener" event="kernel.request" method="handleKernelRequestEvent" priority="4" />
66+
<tag name="kernel.event_listener" event="kernel.controller" method="handleKernelControllerEvent" priority="11" />
67+
<tag name="kernel.event_listener" event="kernel.response" method="handleKernelResponseEvent" priority="15" />
68+
<tag name="kernel.event_listener" event="kernel.terminate" method="handleKernelTerminateEvent" priority="20" />
69+
</service>
70+
71+
<service id="Sentry\SentryBundle\EventListener\Tracing\DbalListener" class="Sentry\SentryBundle\EventListener\Tracing\DbalListener">
72+
<argument type="service" id="Sentry\State\HubInterface" />
73+
</service>
74+
75+
<service id="doctrine.dbal.logger.chain" class="%doctrine.dbal.logger.chain.class%" public="false" abstract="true">
76+
<call method="addLogger">
77+
<argument type="service" id="Sentry\SentryBundle\EventListener\Tracing\DbalListener" />
78+
</call>
79+
</service>
80+
81+
<service id="Sentry\SentryBundle\EventListener\Tracing\TwigListener" class="Sentry\SentryBundle\EventListener\Tracing\TwigListener">
82+
<argument type="service" id="Sentry\State\HubInterface" />
83+
<argument type="service" id="twig.profile" />
84+
85+
<tag name="twig.extension" />
86+
</service>
87+
6288
<service id="Sentry\SentryBundle\Command\SentryTestCommand" class="Sentry\SentryBundle\Command\SentryTestCommand">
6389
<tag name="console.command" />
6490
</service>

src/aliases.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
use Sentry\SentryBundle\EventListener\ErrorListenerExceptionEvent;
66
use Sentry\SentryBundle\EventListener\RequestListenerControllerEvent;
77
use Sentry\SentryBundle\EventListener\RequestListenerRequestEvent;
8+
use Sentry\SentryBundle\EventListener\RequestListenerResponseEvent;
9+
use Sentry\SentryBundle\EventListener\RequestListenerTerminateEvent;
810
use Sentry\SentryBundle\EventListener\SubRequestListenerRequestEvent;
911
use Symfony\Component\HttpKernel\Event\ControllerEvent;
1012
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
1113
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
1214
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
1315
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
1416
use Symfony\Component\HttpKernel\Event\RequestEvent;
17+
use Symfony\Component\HttpKernel\Event\ResponseEvent;
18+
use Symfony\Component\HttpKernel\Event\TerminateEvent;
1519
use Symfony\Component\HttpKernel\Kernel;
1620

1721
if (version_compare(Kernel::VERSION, '4.3.0', '>=')) {
@@ -30,6 +34,16 @@ class_alias(RequestEvent::class, RequestListenerRequestEvent::class);
3034
class_alias(ControllerEvent::class, RequestListenerControllerEvent::class);
3135
}
3236

37+
if (!class_exists(RequestListenerResponseEvent::class, false)) {
38+
/** @psalm-suppress UndefinedClass */
39+
class_alias(ResponseEvent::class, RequestListenerResponseEvent::class);
40+
}
41+
42+
if (!class_exists(RequestListenerTerminateEvent::class, false)) {
43+
/** @psalm-suppress UndefinedClass */
44+
class_alias(TerminateEvent::class, RequestListenerTerminateEvent::class);
45+
}
46+
3347
if (!class_exists(SubRequestListenerRequestEvent::class, false)) {
3448
/** @psalm-suppress UndefinedClass */
3549
class_alias(RequestEvent::class, SubRequestListenerRequestEvent::class);

0 commit comments

Comments
 (0)