|
3 | 3 | namespace Sentry\Laravel\Tracing;
|
4 | 4 |
|
5 | 5 | use Closure;
|
| 6 | +use Illuminate\Contracts\Foundation\Application; |
6 | 7 | use Illuminate\Http\Request;
|
7 | 8 | use Sentry\SentrySdk;
|
8 | 9 | use Sentry\State\HubInterface;
|
@@ -35,6 +36,23 @@ class Middleware
|
35 | 36 | */
|
36 | 37 | private $bootedTimestamp;
|
37 | 38 |
|
| 39 | + /** |
| 40 | + * The Laravel application instance. |
| 41 | + * |
| 42 | + * @var Application|null |
| 43 | + */ |
| 44 | + private $app; |
| 45 | + |
| 46 | + /** |
| 47 | + * Construct the Sentry tracing middleware. |
| 48 | + * |
| 49 | + * @param Application|null $app |
| 50 | + */ |
| 51 | + public function __construct(?Application $app) |
| 52 | + { |
| 53 | + $this->app = $app; |
| 54 | + } |
| 55 | + |
38 | 56 | /**
|
39 | 57 | * Handle an incoming request.
|
40 | 58 | *
|
@@ -74,17 +92,26 @@ public function terminate(Request $request, $response): void
|
74 | 92 |
|
75 | 93 | if ($this->appSpan !== null) {
|
76 | 94 | $this->appSpan->finish();
|
| 95 | + $this->appSpan = null; |
77 | 96 | }
|
78 | 97 |
|
79 |
| - // Make sure we set the transaction and not have a child span in the Sentry SDK |
80 |
| - // If the transaction is not on the scope during finish, the trace.context is wrong |
81 |
| - SentrySdk::getCurrentHub()->setSpan($this->transaction); |
82 |
| - |
83 | 98 | if ($response instanceof SymfonyResponse) {
|
84 | 99 | $this->hydrateResponseData($response);
|
85 | 100 | }
|
86 | 101 |
|
87 |
| - $this->transaction->finish(); |
| 102 | + if ($this->app === null) { |
| 103 | + $this->finishTransaction(); |
| 104 | + } else { |
| 105 | + // We need to finish the transaction after the response has been sent to the client |
| 106 | + // so we register a terminating callback to do so, this allows us to also capture |
| 107 | + // spans that are created during the termination of the application like queue |
| 108 | + // dispatched using dispatch(...)->afterResponse(). This middleware is called |
| 109 | + // before the terminating callbacks so we are 99.9% sure to be the last one |
| 110 | + // to run except if another terminating callback is registered after ours. |
| 111 | + $this->app->terminating(function () { |
| 112 | + $this->finishTransaction(); |
| 113 | + }); |
| 114 | + } |
88 | 115 | }
|
89 | 116 |
|
90 | 117 | /**
|
@@ -193,4 +220,14 @@ private function hydrateResponseData(SymfonyResponse $response): void
|
193 | 220 | {
|
194 | 221 | $this->transaction->setHttpStatus($response->getStatusCode());
|
195 | 222 | }
|
| 223 | + |
| 224 | + private function finishTransaction(): void |
| 225 | + { |
| 226 | + // Make sure we set the transaction and not have a child span in the Sentry SDK |
| 227 | + // If the transaction is not on the scope during finish, the trace.context is wrong |
| 228 | + SentrySdk::getCurrentHub()->setSpan($this->transaction); |
| 229 | + |
| 230 | + $this->transaction->finish(); |
| 231 | + $this->transaction = null; |
| 232 | + } |
196 | 233 | }
|
0 commit comments