8000 Cache tracing (#914) · getsentry/sentry-laravel@71625ef · GitHub
[go: up one dir, main page]

Skip to content

Commit 71625ef

Browse files
authored
Cache tracing (#914)
1 parent c97041a commit 71625ef

File tree

9 files changed

+293
-45
lines changed

9 files changed

+293
-45
lines changed

config/sentry.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
// Capture HTTP client requests as spans
104104
'http_client_requests' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED', true),
105105

106+
// Capture Laravel cache events (hits, writes etc.) as spans
107+
'cache' => env('SENTRY_TRACE_CACHE_ENABLED', true),
108+
106109
// Capture Redis operations as spans (this enables Redis events in Laravel)
107110
'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false),
108111

src/Sentry/Laravel/Features/CacheIntegration.php

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@
99
use Illuminate\Support\Str;
1010
use Sentry\Breadcrumb;
1111
use Sentry\Laravel\Features\Concerns\ResolvesEventOrigin;
12+
use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans;
13+
use Sentry\Laravel\Features\Concerns\WorksWithSpans;
1214
use Sentry\Laravel\Integration;
1315
use Sentry\SentrySdk;
16+
use Sentry\Tracing\Span;
1417
use Sentry\Tracing\SpanContext;
18+
use Sentry\Tracing\SpanStatus;
1519

1620
class CacheIntegration extends Feature
1721
{
18-
use ResolvesEventOrigin;
22+
use WorksWithSpans, TracksPushedScopesAndSpans, ResolvesEventOrigin;
1923

2024
public function isApplicable(): bool
2125
{
22-
return $this->isTracingFeatureEnabled('redis_commands')
26+
return $this->isTracingFeatureEnabled('redis_commands', false)
27+
|| $this->isTracingFeatureEnabled('cache')
2328
|| $this->isBreadcrumbFeatureEnabled('cache');
2429
}
2530

@@ -31,19 +36,37 @@ public function onBoot(Dispatcher $events): void
3136
Events\CacheMissed::class,
3237
Events\KeyWritten::class,
3338
Events\KeyForgotten::class,
34-
], [$this, 'handleCacheEvent']);
39+
], [$this, 'handleCacheEventsForBreadcrumbs']);
40+
}
41+
42+
if ($this->isTracingFeatureEnabled('cache')) {
43+
$events->listen([
44+
Events\RetrievingKey::class,
45+
Events\RetrievingManyKeys::class,
46+
Events\CacheHit::class,
47+
Events\CacheMissed::class,
48+
49+
Events\WritingKey::class,
50+
Events\WritingManyKeys::class,
51+
Events\KeyWritten::class,
52+
Events\KeyWriteFailed::class,
53+
54+
Events\ForgettingKey::class,
55+
Events\KeyForgotten::class,
56+
Events\KeyForgetFailed::class,
57+
], [$this, 'handleCacheEventsForTracing']);
3558
}
3659

3760
if ($this->isTracingFeatureEnabled('redis_commands', false)) {
38-
$events->listen(RedisEvents\CommandExecuted::class, [$this, 'handleRedisCommand']);
61+
$events->listen(RedisEvents\CommandExecuted::class, [$this, 'handleRedisCommands']);
3962

4063
$this->container()->afterResolving(RedisManager::class, static function (RedisManager $redis): void {
4164
$redis->enableEvents();
4265
});
4366
}
4467
}
4568

46-
public function handleCacheEvent(Events\CacheEvent $event): void
69+
public function handleCacheEventsForBreadcrumbs(Events\CacheEvent $event): void
4770
{
4871
switch (true) {
4972
case $event instanceof Events\KeyWritten:
@@ -72,7 +95,64 @@ public function handleCacheEvent(Events\CacheEvent $event): void
7295
));
7396
}
7497

75-
public function handleRedisCommand(RedisEvents\CommandExecuted $event): void
98+
public function handleCacheEventsForTracing(Events\CacheEvent $event): void
99+
{
100+
if ($this->maybeHandleCacheEventAsEndOfSpan($event)) {
101+
return;
102+
}
103+
104+
$this->withParentSpanIfSampled(function (Span $parentSpan) use ($event) {
105+
if ($event instanceof Events\RetrievingKey || $event instanceof Events\RetrievingManyKeys) {
106+
$keys = $event instanceof Events\RetrievingKey
107+
? [$event->key]
108+
: $event->keys;
109+
110+
$this->pushSpan(
111+
$parentSpan->startChild(
112+
SpanContext::make()
113+
->setOp('cache.get')
114+
->setData([
115+
'cache.key' => $keys,
116+
])
117+
->setDescription(implode(', ', $keys))
118+
)
119+
);
120+
}
121+
122+
if ($event instanceof Events\WritingKey || $event instanceof Events\WritingManyKeys) {
123+
$keys = $event instanceof Events\WritingKey
124+
? [$event->key]
125+
: $event->keys;
126+
127+
$this->pushSpan(
128+
$parentSpan->startChild(
129+
SpanContext::make()
130+
->setOp('cache.put')
131+
->setData([
132+
'cache.key' => $keys,
133+
'cache.ttl' => $event->seconds,
134+
])
135+
->setDescription(implode(', ', $keys))
136+
)
137+
);
138+
}
139+
140+
if ($event instanceof Events\ForgettingKey) {
141+
$this->pushSpan(
142+
$parentSpan->startChild(
143+
SpanContext::make()
144+
->setOp('cache.remove')
145 10000 +
->setData([
146+
'cache.key' => [$event->key],
147+
])
148+
->setDescription($event->key)
149+
)
150+
);
151+
}
152+
});
153+
}
154+
155+
public function handleRedisCommands(RedisEvents\CommandExecuted $event): void
76156
{
77157
$parentSpan = SentrySdk::getCurrentHub()->getSpan();
78158

@@ -116,4 +196,44 @@ public function handleRedisCommand(RedisEvents\CommandExecuted $event): void
116196

117197
$parentSpan->startChild($context);
118198
}
199+
200+
private function maybeHandleCacheEventAsEndOfSpan(Events\CacheEvent $event): bool
201+
{
202+
// End of span for RetrievingKey and RetrievingManyKeys events
203+
if ($event instanceof Events\CacheHit || $event instanceof Events\CacheMissed) {
204+
$finishedSpan = $this->maybeFinishSpan(SpanStatus::ok());
205+
206+
if ($finishedSpan !== null && count($finishedSpan->getData()['cache.key'] ?? []) === 1) {
207+
$finishedSpan->setData([
208+
'cache.hit' => $event instanceof Events\CacheHit,
209+
]);
210+
}
211+
212+
return true;
213+
}
214+
215+
// End of span for WritingKey and WritingManyKeys events
216+
if ($event instanceof Events\KeyWritten || $event instanceof Events\KeyWriteFailed) {
217+
$finishedSpan = $this->maybeFinishSpan(
218+
$event instanceof Events\KeyWritten ? SpanStatus::ok() : SpanStatus::internalError()
219+
);
220+
221+
if ($finishedSpan !== null) {
222+
$finishedSpan->setData([
223+
'cache.success' => $event instanceof Events\KeyWritten,
224+
]);
225+
}
226+
227+
return true;
228+
}
229+
230+
// End of span for ForgettingKey event
231+
if ($event instanceof Events\KeyForgotten || $event instanceof Events\KeyForgetFailed) {
232+
$this->maybeFinishSpan();
233+
234+
return true;
235+
}
236+
237+
return false;
238+
}
119239
}

src/Sentry/Laravel/Features/Concerns/TracksPushedScopesAndSpans.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Sentry\Laravel\Integration;
66
use Sentry\SentrySdk;
77
use Sentry\Tracing\Span;
8+
use Sentry\Tracing\SpanStatus;
89

910
trait TracksPushedScopesAndSpans
1011
{
@@ -72,4 +73,21 @@ protected function maybePopScope(): void
7273

7374
--$this->pushedScopeCount;
7475
}
76+
77+
protected function maybeFinishSpan(?SpanStatus $status = null): ?Span
78+
{
79+
$span = $this->maybePopSpan();
80+
81+
if ($span === null) {
82+
return null;
83+
}
84+
85+
if ($status !== null) {
86+
$span->setStatus($status);
87+
}
88+
89+
$span->finish();
90+
91+
return $span;
92+
F440 }
7593
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Features\Concerns;
4+
5+
use Sentry\SentrySdk;
6+
use Sentry\Tracing\Span;
7+
8+
trait WorksWithSpans
9+
{
10+
protected function getParentSpanIfSampled(): ?Span
11+
{
12+
$parentSpan = SentrySdk::getCurrentHub()->getSpan();
13+
14+
// If the span is not available or not sampled we don't need to do anything
15+
if ($parentSpan === null || !$parentSpan->getSampled()) {
16+
return null;
17+
}
18+
19+
return $parentSpan;
20+
}
21+
22+
/** @param callable(Span $parentSpan): void $callback */
23+
protected function withParentSpanIfSampled(callable $callback): void
24+
{
25+
$parentSpan = $this->getParentSpanIfSampled();
26+
27+
if ($parentSpan === null) {
28+
return;
29+
}
30+
31+
$callback($parentSpan);
32+
}
33+
}

src/Sentry/Laravel/Features/HttpClientIntegration.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,7 @@ public function handleResponseReceivedHandlerForTracing(ResponseReceived $event)
108108

109109
public function handleConnectionFailedHandlerForTracing(ConnectionFailed $event): void
110110
{
111-
$span = $this->maybePopSpan();
112-
113-
if ($span !== null) {
114-
$span->setStatus(SpanStatus::internalError());
115-
$span->finish();
116-
}
111+
$this->maybeFinishSpan(SpanStatus::internalError());
117112
}
118113

119114
public function handleResponseReceivedHandlerForBreadcrumb(ResponseReceived $event): void

src/Sentry/Laravel/Features/LivewirePackageIntegration.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,7 @@ public function handleComponentHydrate(Component $component): void
168168

169169
public function handleComponentDehydrate(Component $component): void
170170
{
171-
$span = $this->maybePopSpan();
172-
173-
if ($span !== null) {
174-
$span->finish();
175-
}
171+
$span = $this->maybeFinishSpan();
176172
}
177173

178174
private function updateTransactionName(string $componentName): void

src/Sentry/Laravel/Features/NotificationsIntegration.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function handleNotificationSending(NotificationSending $event): void
5858

5959
public function handleNotificationSent(NotificationSent $event): void
6060
{
61-
$this->finishSpanWithStatus(SpanStatus::ok());
61+
$this->maybeFinishSpan(SpanStatus::ok());
6262

6363
if ($this->isBreadcrumbFeatureEnabled(self::FEATURE_KEY)) {
6464
Integration::addBreadcrumb(new Breadcrumb(
@@ -75,16 +75,6 @@ public function handleNotificationSent(NotificationSent $event): void
7575
}
7676
}
7777

78-
private function finishSpanWithStatus(SpanStatus $status): void
79-
{
80-
$span = $this->maybePopSpan();
81-
82-
if ($span !== null) {
83-
$span->setStatus($status);
84-
$span->finish();
85-
}
86-
}
87-
8878
private function formatNotifiable(object $notifiable): string
8979
{
9080
$notifiable = get_class($notifiable);

src/Sentry/Laravel/Features/QueueIntegration.php

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,12 @@ public function handleJobQueueingEvent(JobQueueing $event): void
112112

113113
public function handleJobQueuedEvent(JobQueued $event): void
114114
{
115-
$span = $this->maybePopSpan();
116-
117-
if ($span !== null) {
118-
$span->finish();
119-
}
115+
$this->maybeFinishSpan();
120116
}
121117

122118
public function handleJobProcessedQueueEvent(JobProcessed $event): void
123119
{
124-
$this->finishJobWithStatus(SpanStatus::ok());
120+
$this->maybeFinishSpan(SpanStatus::ok());
125121

126122
$this->maybePopScope();
127123
}
@@ -225,21 +221,11 @@ public function handleWorkerStoppingQueueEvent(WorkerStopping $event): void
225221

226222
public function handleJobExceptionOccurredQueueEvent(JobExceptionOccurred $event): void
227223
{
228-
$this->finishJobWithStatus(SpanStatus::internalError());
224+
$this->maybeFinishSpan(SpanStatus::internalError());
229225

230226
Integration::flushEvents();
231227
}
232228

233-
private function finishJobWithStatus(SpanStatus $status): void
234-
{
235-
$span = $this->maybePopSpan();
236-
237-
if ($span !== null) {
238-
$span->setStatus($status);
239-
$span->finish();
240-
}
241-
}
242-
243229
private function normalizeQueueName(?string $queue): string
244230
{
245231
if ($queue === null) {

0 commit comments

Comments
 (0)
0