/
Integration.php
328 lines (279 loc) · 9.65 KB
/
Integration.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
<?php
namespace Sentry\Laravel;
use Illuminate\Routing\Route;
use Illuminate\Support\Str;
use Sentry\SentrySdk;
use Sentry\Tracing\Span;
use Sentry\Tracing\TransactionSource;
use function Sentry\addBreadcrumb;
use function Sentry\configureScope;
use Sentry\Breadcrumb;
use Sentry\Event;
use Sentry\Integration\IntegrationInterface;
use Sentry\State\Scope;
class Integration implements IntegrationInterface
{
/**
* @var null|string
*/
private static $transaction;
/**
* @var null|string
*/
private static $baseControllerNamespace;
/**
* {@inheritdoc}
*/
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$self = SentrySdk::getCurrentHub()->getIntegration(self::class);
if (!$self instanceof self) {
return $event;
}
if (empty($event->getTransaction())) {
$event->setTransaction(self::getTransaction());
}
return $event;
});
}
/**
* Adds a breadcrumb if the integration is enabled for Laravel.
*
* @param Breadcrumb $breadcrumb
*/
public static function addBreadcrumb(Breadcrumb $breadcrumb): void
{
$self = SentrySdk::getCurrentHub()->getIntegration(self::class);
if (!$self instanceof self) {
return;
}
addBreadcrumb($breadcrumb);
}
/**
* Configures the scope if the integration is enabled for Laravel.
*
* @param callable $callback
*/
public static function configureScope(callable $callback): void
{
$self = SentrySdk::getCurrentHub()->getIntegration(self::class);
if (!$self instanceof self) {
return;
}
configureScope($callback);
}
/**
* @return null|string
*/
public static function getTransaction(): ?string
{
return self::$transaction;
}
/**
* @param null|string $transaction
*/
public static function setTransaction(?string $transaction): void
{
self::$transaction = $transaction;
}
/**
* @param null|string $namespace
*/
public static function setControllersBaseNamespace(?string $namespace): void
{
self::$baseControllerNamespace = $namespace !== null ? trim($namespace, '\\') : null;
}
/**
* Block until all async events are processed for the HTTP transport.
*
* @internal This is not part of the public API and is here temporarily until
* the underlying issue can be resolved, this method will be removed.
*/
public static function flushEvents(): void
{
$client = SentrySdk::getCurrentHub()->getClient();
if ($client !== null) {
$client->flush();
}
}
/**
* Extract the readable name for a route.
*
* @param \Illuminate\Routing\Route $route
*
* @return string
*
* @internal This helper is used in various places to extra meaninful info from a Laravel Route object.
* @deprecated This will be removed in version 3.0, use `extractNameAndSourceForRoute` instead.
*/
public static function extractNameForRoute(Route $route): string
{
return self::extractNameAndSourceForRoute($route)[0];
}
/**
* Extract the readable name for a route and the transaction source for where that route name came from.
*
* @param \Illuminate\Routing\Route $route
*
* @return array{0: string, 1: \Sentry\Tracing\TransactionSource}
*
* @internal This helper is used in various places to extra meaninful info from a Laravel Route object.
*/
public static function extractNameAndSourceForRoute(Route $route): array
{
$source = null;
$routeName = null;
// some.action (route name/alias)
if ($route->getName()) {
$source = TransactionSource::component();
$routeName = self::extractNameForNamedRoute($route->getName());
}
// Some\Controller@someAction (controller action)
if (empty($routeName) && $route->getActionName()) {
$source = TransactionSource::component();
$routeName = self::extractNameForActionRoute($route->getActionName());
}
// /some/{action} // Fallback to the route uri (with parameter placeholders)
if (empty($routeName) || $routeName === 'Closure') {
$source = TransactionSource::route();
$routeName = '/' . ltrim($route->uri(), '/');
}
return [$routeName, $source];
}
/**
* Extract the readable name for a Lumen route.
*
* @param array $routeData The array of route data
* @param string $path The path of the request
*
* @return string
*
* @internal This helper is used in various places to extra meaninful info from a Lumen route data.
* @deprecated This will be removed in version 3.0, use `extractNameAndSourceForLumenRoute` instead.
*/
public static function extractNameForLumenRoute(array $routeData, string $path): string
{
return self::extractNameAndSourceForLumenRoute($routeData, $path)[0];
}
/**
* Extract the readable name for a Lumen route and the transaction source for where that route name came from.
*
* @param array $routeData The array of route data
* @param string $path The path of the request
*
* @return array{0: string, 1: \Sentry\Tracing\TransactionSource}
*
* @internal This helper is used in various places to extra meaninful info from a Lumen route data.
*/
public static function extractNameAndSourceForLumenRoute(array $routeData, string $path): array
{
$source = null;
$routeName = null;
$route = $routeData[1] ?? [];
// some.action (route name/alias)
if (!empty($route['as'])) {
$source = TransactionSource::component();
$routeName = self::extractNameForNamedRoute($route['as']);
}
// Some\Controller@someAction (controller action)
if (empty($routeName) && !empty($route['uses'])) {
$source = TransactionSource::component();
$routeName = self::extractNameForActionRoute($route['uses']);
}
// /some/{action} // Fallback to the route uri (with parameter placeholders)
if (empty($routeName) || $routeName === 'Closure') {
$routeUri = array_reduce(
array_keys($routeData[2]),
static function ($carry, $key) use ($routeData) {
return str_replace($routeData[2][$key], "{{$key}}", $carry);
},
$path
);
$source = TransactionSource::url();
$routeName = '/' . ltrim($routeUri, '/');
}
return [$routeName, $source];
}
/**
* Take a route name and return it only if it's a usable route name.
*
* @param string $name
*
* @return string|null
*/
private static function extractNameForNamedRoute(string $name): ?string
{
// Laravel 7 route caching generates a route names if the user didn't specify one
// theirselfs to optimize route matching. These route names are useless to the
// developer so if we encounter a generated route name we discard the value
if (Str::contains($name, 'generated::')) {
return null;
}
// If the route name ends with a `.` we assume an incomplete group name prefix
// we discard this value since it will most likely not mean anything to the
// developer and will be duplicated by other unnamed routes in the group
if (Str::endsWith($name, '.')) {
return null;
}
return $name;
}
/**
* Take a controller action and strip away the base namespace if needed.
*
* @param string $action
*
* @return string
*/
private static function extractNameForActionRoute(string $action): string
{
$routeName = ltrim($action, '\\');
$baseNamespace = self::$baseControllerNamespace ?? '';
if (empty($baseNamespace)) {
return $routeName;
}
// Strip away the base namespace from the action name
// @see: Str::after, but this is not available before Laravel 5.4 so we use a inlined version
return array_reverse(explode($baseNamespace . '\\', $routeName, 2))[0];
}
/**
* Retrieve the meta tags with tracing information to link this request to front-end requests.
*
* @return string
*/
public static function sentryTracingMeta(): string
{
$span = self::currentTracingSpan();
if ($span === null) {
return '';
}
$content = sprintf('<meta name="sentry-trace" content="%s"/>', $span->toTraceparent());
return $content;
}
/**
* Retrieve the meta tags with baggage information to link this request to front-end requests.
* This propagates the Dynamic Sampling Context.
*
* @return string
*/
public static function sentryBaggageMeta(): string
{
$span = self::currentTracingSpan();
if ($span === null) {
return '';
}
$content = sprintf('<meta name="baggage" content="%s"/>', $span->toBaggage());
return $content;
}
/**
* Get the current active tracing span from the scope.
*
* @return \Sentry\Tracing\Span|null
*
* @internal This is used internally as an easy way to retrieve the current active tracing span.
*/
public static function currentTracingSpan(): ?Span
{
return SentrySdk::getCurrentHub()->getSpan();
}
}