Skip to content

Commit 72b6731

Browse files
authored
Move delayed transaction finisher state out of middleware (#936)
* Move delayed transaction finisher state out of middleware * Update comment
1 parent 6d916ca commit 72b6731

File tree

3 files changed

+39
-27
lines changed

3 files changed

+39
-27
lines changed

src/Sentry/Laravel/Tracing/Middleware.php

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,6 @@ class Middleware
4747
*/
4848
private $continueAfterResponse;
4949

50-
/**
51-
* Whether the terminating callback has been registered.
52-
*
53-
* @var bool
54-
*/
55-
private $registeredTerminatingCallback = false;
56-
5750
/**
5851
* Whether a defined route was matched in the application.
5952
*
@@ -115,22 +108,11 @@ public function terminate(Request $request, $response): void
115108
}
116109

117110
if ($this->continueAfterResponse) {
118-
// Ensure we do not register the terminating callback multiple times since there is no point in doing so
119-
if ($this->registeredTerminatingCallback) {
120-
return;
121-
}
122-
123-
// We need to finish the transaction after the response has been sent to the client
124-
// so we register a terminating callback to do so, this allows us to also capture
125-
// spans that are created during the termination of the application like queue
126-
// dispatched using dispatch(...)->afterResponse(). This middleware is called
127-
// before the terminating callbacks so we are 99.9% sure to be the last one
128-
// to run except if another terminating callback is registered after ours.
129-
app()->terminating(function () {
130-
$this->finishTransaction();
131-
});
132-
133-
$this->registeredTerminatingCallback = true;
111+
// Resolving the transaction finisher class will register the terminating callback
112+
// which is responsible for calling `finishTransaction`. We have registered the
113+
// class as a singleton to keep the state in the container and away from here
114+
// this way we ensure the callback is only registered once even for Octane.
115+
app(TransactionFinisher::class);
134116
} else {
135117
$this->finishTransaction();
136118
}
@@ -220,12 +202,12 @@ private function addAppBootstrapSpan(): ?Span
220202

221203
$span = $this->transaction->startChild($spanContextStart);
222204

223-
// Consume the booted timestamp, because we don't want to report the bootstrap span more than once
224-
$this->bootedTimestamp = null;
225-
226205
// Add more information about the bootstrap section if possible
227206
$this->addBootDetailTimeSpans($span);
228207

208+
// Consume the booted timestamp, because we don't want to report the boot(strap) spans more than once
209+
$this->bootedTimestamp = null;
210+
229211
return $span;
230212
}
231213

@@ -250,7 +232,7 @@ private function hydrateResponseData(SymfonyResponse $response): void
250232
$this->transaction->setHttpStatus($response->getStatusCode());
251233
}
252234

253-
private function finishTransaction(): void
235+
public function finishTransaction(): void
254236
{
255237
// We could end up multiple times here since we register a terminating callback so
256238
// double check if we have a transaction before trying to finish it since it could

src/Sentry/Laravel/Tracing/ServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public function boot(): void
5959

6060
public function register(): void
6161
{
62+
$this->app->singleton(TransactionFinisher::class);
63+
6264
$this->app->singleton(Middleware::class, function () {
6365
$continueAfterResponse = ($this->getTracingConfig()['continue_after_response'] ?? true) === true;
6466

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Sentry\Laravel\Tracing;
4+
5+
/**
6+
* @internal
7+
*/
8+
class TransactionFinisher
9+
{
10+
public function __construct()
11+
{
12+
// We need to finish the transaction after the response has been sent to the client
13+
// so we register a terminating callback to do so, this allows us to also capture
14+
// spans that are created during the termination of the application like queue
15+
// dispatches using dispatch(...)->afterResponse() which are terminating
16+
// callbacks themselfs just like we do below.
17+
//
18+
// This class is registered as a singleton in the container to ensure it's only
19+
// instantiated once and the terminating callback is only registered once.
20+
//
21+
// It should be resolved from the container before the terminating callbacks are called.
22+
// Good place is in the `terminate` callback of a middleware for example.
23+
// This way we can be 99.9% sure to be the last ones to run.
24+
app()->terminating(function () {
25+
app(Middleware::class)->finishTransaction();
26+
});
27+
}
28+
}

0 commit comments

Comments
 (0)