Skip to content

Commit e763b2e

Browse files
committed
Add sql.origin to SQL query spans
1 parent d139b2b commit e763b2e

File tree

3 files changed

+149
-3
lines changed

3 files changed

+149
-3
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Tracing;
4+
5+
use Sentry\Frame;
6+
use Sentry\Options;
7+
use Sentry\FrameBuilder;
8+
use Illuminate\Support\Str;
9+
use Sentry\Serializer\RepresentationSerializerInterface;
10+
11+
class BacktraceHelper
12+
{
13+
/**
14+
* @var Options The SDK client options
15+
*/
16+
private $options;
17+
18+
/**
19+
* @var FrameBuilder An instance of the builder of {@see Frame} objects
20+
*/
21+
private $frameBuilder;
22+
23+
/**
24+
* Constructor.
25+
*
26+
* @param Options $options The SDK client options
27+
* @param RepresentationSerializerInterface $representationSerializer The representation serializer
28+
*/
29+
public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer)
30+
{
31+
$this->options = $options;
32+
$this->frameBuilder = new FrameBuilder($options, $representationSerializer);
33+
}
34+
35+
/**
36+
* Find the first in app frame for a given backtrace.
37+
*
38+
* @param array<int, array<string, mixed>> $backtrace The backtrace
39+
*
40+
* @phpstan-param list<array{
41+
* line?: integer,
42+
* file?: string,
43+
* }> $backtrace
44+
*/
45+
public function findFirstInAppFrameForBacktrace(array $backtrace): ?Frame
46+
{
47+
$file = Frame::INTERNAL_FRAME_FILENAME;
48+
$line = 0;
49+
50+
foreach ($backtrace as $backtraceFrame) {
51+
$frame = $this->frameBuilder->buildFromBacktraceFrame($file, $line, $backtraceFrame);
52+
53+
if ($frame->isInApp()) {
54+
return $frame;
55+
}
56+
57+
$file = $backtraceFrame['file'] ?? Frame::INTERNAL_FRAME_FILENAME;
58+
$line = $backtraceFrame['line'] ?? 0;
59+
}
60+
61+
return null;
62+
}
63+
64+
/**
65+
* Takes a frame and if it's a compiled view path returns the original view path.
66+
*
67+
* @param \Sentry\Frame $frame
68+
*
69+
* @return string|null
70+
*/
71+
public function getOriginalViewPathForFrameOfCompiledViewPath(Frame $frame): ?string
72+
{
73+
// Check if we are dealing with a frame for a cached view path
74+
if (!Str::startsWith($frame->getFile(), '/storage/framework/views/')) {
75+
return null;
76+
}
77+
78+
// If for some reason the file does not exists, skip resolving
79+
if (!file_exists($frame->getAbsoluteFilePath())) {
80+
return null;
81+
}
82+
83+
$viewFileContents = file_get_contents($frame->getAbsoluteFilePath());
84+
85+
preg_match('/PATH (?<originalPath>.*?) ENDPATH/', $viewFileContents, $matches);
86+
87+
// No path comment found in the file, must be a very old Laravel version
88+
if (empty($matches['originalPath'])) {
89+
return null;
90+
}
91+
92+
return $this->stripPrefixFromFilePath($matches['originalPath']);
93+
}
94+
95+
/**
96+
* Removes from the given file path the specified prefixes.
97+
*
98+
* @param string $filePath The path to the file
99+
*/
100+
private function stripPrefixFromFilePath(string $filePath): string
101+
{
102+
foreach ($this->options->getPrefixes() as $prefix) {
103+
if (Str::startsWith($filePath, $prefix)) {
104+
return mb_substr($filePath, mb_strlen($prefix));
105+
}
106+
}
107+
108+
return $filePath;
109+
}
110+
}

src/Sentry/Laravel/Tracing/EventHandler.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,22 @@ class EventHandler
3535
*/
3636
private $events;
3737

38+
/**
39+
* The backtrace helper.
40+
*
41+
* @var \Sentry\Laravel\Tracing\BacktraceHelper
42+
*/
43+
private $backtraceHelper;
44+
3845
/**
3946
* EventHandler constructor.
4047
*
4148
* @param \Illuminate\Contracts\Events\Dispatcher $events
4249
*/
43-
public function __construct(Dispatcher $events)
50+
public function __construct(Dispatcher $events, BacktraceHelper $backtraceHelper)
4451
{
45-
$this->events = $events;
52+
$this->events = $events;
53+
$this->backtraceHelper = $backtraceHelper;
4654
}
4755

4856
/**
@@ -152,6 +160,21 @@ private function recordQuerySpan($query, $time): void
152160
$context->setStartTimestamp(microtime(true) - $time / 1000);
153161
$context->setEndTimestamp($context->getStartTimestamp() + $time / 1000);
154162

163+
$this->resolveQuerySourceFromBacktrace($context);
164+
155165
$parentSpan->startChild($context);
156166
}
167+
168+
private function resolveQuerySourceFromBacktrace(SpanContext $context): void
169+
{
170+
$firstAppFrame = $this->backtraceHelper->findFirstInAppFrameForBacktrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
171+
172+
if ($firstAppFrame !== null) {
173+
$filePath = $this->backtraceHelper->getOriginalViewPathForFrameOfCompiledViewPath($firstAppFrame) ?? $firstAppFrame->getFile();
174+
175+
$context->setData([
176+
'sql.origin' => "{$filePath}:{$firstAppFrame->getLine()}",
177+
]);
178+
}
179+
}
157180
}

src/Sentry/Laravel/Tracing/ServiceProvider.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\View\Engines\EngineResolver;
99
use Illuminate\View\Factory as ViewFactory;
1010
use Sentry\Laravel\BaseServiceProvider;
11+
use Sentry\Serializer\RepresentationSerializer;
1112

1213
class ServiceProvider extends BaseServiceProvider
1314
{
@@ -30,11 +31,23 @@ public function boot(): void
3031
public function register(): void
3132
{
3233
$this->app->singleton(Middleware::class);
34+
35+
$this->app->singleton(BacktraceHelper::class, function () {
36+
/** @var \Sentry\State\Hub $sentry */
37+
$sentry = $this->app->make(self::$abstract);
38+
39+
$options = $sentry->getClient()->getOptions();
40+
41+
return new BacktraceHelper($options, new RepresentationSerializer($options));
42+
});
3343
}
3444

3545
private function bindEvents(): void
3646
{
37-
$handler = new EventHandler($this->app->events);
47+
$handler = new EventHandler(
48+
$this->app->events,
49+
$this->app->make(BacktraceHelper::class)
50+
);
3851

3952
$handler->subscribe();
4053
}

0 commit comments

Comments
 (0)