Skip to content

Commit d30d531

Browse files
authored
Reintroduce Lumen support (#685)
1 parent b8ca66c commit d30d531

File tree

6 files changed

+137
-21
lines changed

6 files changed

+137
-21
lines changed

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This is the official Laravel SDK for [Sentry](https://sentry.io).
2121

2222
The installation steps below work on versions 8.x, 9.x and 10.x of the Laravel framework.
2323

24-
For other Laravel or Lumen versions see:
24+
For older Laravel versions and Lumen see:
2525

2626
- [Laravel 8.x & 9.x & 10.x](https://docs.sentry.io/platforms/php/guides/laravel/)
2727
- [Laravel 6.x & 7.x](https://docs.sentry.io/platforms/php/guides/laravel/other-versions/laravel6-7/)
@@ -50,7 +50,7 @@ public function register(): void
5050
}
5151
```
5252

53-
> Alternatively, you can configure Sentry in your [Laravel Log Channel](https://docs.sentry.io/platforms/php/guides/laravel/usage/#log-channels), allowing you to log `info` and `debug` as well.
53+
> Alternatively, you can configure Sentry as a [Laravel Log Channel](https://docs.sentry.io/platforms/php/guides/laravel/usage/#log-channels), allowing you to capture `info` and `debug` logs as well.
5454
5555
### Configure
5656

@@ -78,11 +78,11 @@ try {
7878
}
7979
```
8080

81-
- To learn more about how to use the SDK [refer to our docs](https://docs.sentry.io/platforms/php/guides/laravel/)
81+
To learn more about how to use the SDK [refer to our docs](https://docs.sentry.io/platforms/php/guides/laravel/).
8282

8383
## Laravel Version Compatibility
8484

85-
The Laravel versions listed below are all currently supported:
85+
The Laravel and Lumen versions listed below are all currently supported:
8686

8787
- Laravel `>= 10.x.x` on PHP `>= 8.1` is supported starting from `3.2.0`
8888
- Laravel `>= 9.x.x` on PHP `>= 8.0` is supported starting from `2.11.0`
@@ -92,12 +92,11 @@ The Laravel versions listed below are all currently supported:
9292

9393
Please note that starting with version `>= 2.0.0` we require PHP Version `>= 7.2` because we are using our new [PHP SDK](https://github.com/getsentry/sentry-php) underneath.
9494

95-
The Laravel and Lumen version listed below were supported in previous versions:
95+
The Laravel versions listed below were supported in previous versions of the Sentry SDK for Laravel:
9696

9797
- Laravel `<= 4.2.x` is supported until `0.8.x`
9898
- Laravel `<= 5.7.x` on PHP `<= 7.0` is supported until `0.11.x`
9999
- Laravel `>= 5.x.x` on PHP `>= 7.1` is supported until `2.14.x`
100-
- Laravel Lumen is supported until `2.14.x`
101100

102101
## Contributing to the SDK
103102

composer.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@
2828
"symfony/psr-http-message-bridge": "^1.0 | ^2.0",
2929
"nyholm/psr7": "^1.0"
3030
},
31-
"conflict": {
32-
"laravel/lumen-framework": "*"
33-
},
3431
"autoload": {
3532
"psr-0": {
3633
"Sentry\\Laravel\\": "src/"

src/Sentry/Laravel/Integration.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public static function flushEvents(): void
117117
*
118118
* @return array{0: string, 1: \Sentry\Tracing\TransactionSource}
119119
*
120-
* @internal This helper is used in various places to extra meaninful info from a Laravel Route object.
120+
* @internal This helper is used in various places to extract meaningful info from a Laravel Route object.
121121
*/
122122
public static function extractNameAndSourceForRoute(Route $route): array
123123
{
@@ -127,6 +127,36 @@ public static function extractNameAndSourceForRoute(Route $route): array
127127
];
128128
}
129129

130+
/**
131+
* Extract the readable name for a Lumen route and the transaction source for where that route name came from.
132+
*
133+
* @param array $routeData The array of route data
134+
* @param string $path The path of the request
135+
*
136+
* @return array{0: string, 1: \Sentry\Tracing\TransactionSource}
137+
*
138+
* @internal This helper is used in various places to extract meaningful info from Lumen route data.
139+
*/
140+
public static function extractNameAndSourceForLumenRoute(array $routeData, string $path): array
141+
{
142+
$routeUri = array_reduce(
143+
array_keys($routeData[2]),
144+
static function ($carry, $key) use ($routeData) {
145+
$search = '/' . preg_quote($routeData[2][$key], '/') . '/';
146+
147+
// Replace the first occurrence of the route parameter value with the key name
148+
// This is by no means a perfect solution, but it's the best we can do with the data we have
149+
return preg_replace($search, "{{$key}}", $carry, 1);
150+
},
151+
$path
152+
);
153+
154+
return [
155+
'/' . ltrim($routeUri, '/'),
156+
TransactionSource::route(),
157+
];
158+
}
159+
130160
/**
131161
* Retrieve the meta tags with tracing information to link this request to front-end requests.
132162
* This propagates the Dynamic Sampling Context.

src/Sentry/Laravel/ServiceProvider.php

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
use Illuminate\Contracts\Http\Kernel as HttpKernelInterface;
88
use Illuminate\Foundation\Application as Laravel;
99
use Illuminate\Foundation\Http\Kernel as HttpKernel;
10+
use Illuminate\Http\Request;
1011
use Illuminate\Log\LogManager;
12+
use Laravel\Lumen\Application as Lumen;
1113
use RuntimeException;
1214
use Sentry\ClientBuilder;
1315
use Sentry\ClientBuilderInterface;
16+
use Sentry\Event;
17+
use Sentry\EventHint;
1418
use Sentry\Integration as SdkIntegration;
1519
use Sentry\Laravel\Console\PublishCommand;
1620
use Sentry\Laravel\Console\TestCommand;
@@ -21,6 +25,7 @@
2125
use Sentry\SentrySdk;
2226
use Sentry\State\Hub;
2327
use Sentry\State\HubInterface;
28+
use Sentry\Tracing\TransactionMetadata;
2429

2530
class ServiceProvider extends BaseServiceProvider
2631
{
@@ -61,8 +66,10 @@ public function boot(): void
6166

6267
$this->setupFeatures();
6368

64-
if ($this->app->bound(HttpKernelInterface::class)) {
65-
/** @var \Illuminate\Foundation\Http\Kernel $httpKernel */
69+
if ($this->app instanceof Lumen) {
70+
$this->app->middleware(SetRequestMiddleware::class);
71+
$this->app->middleware(SetRequestIpMiddleware::class);
72+
} elseif ($this->app->bound(HttpKernelInterface::class)) {
6673
$httpKernel = $this->app->make(HttpKernelInterface::class);
6774

6875
if ($httpKernel instanceof HttpKernel) {
@@ -88,6 +95,10 @@ public function boot(): void
8895
*/
8996
public function register(): void
9097
{
98+
if ($this->app instanceof Lumen) {
99+
$this->app->configure(static::$abstract);
100+
}
101+
91102
$this->mergeConfigFrom(__DIR__ . '/../../../config/sentry.php', static::$abstract);
92103

93104
$this->configureAndRegisterClient();
@@ -181,6 +192,39 @@ protected function configureAndRegisterClient(): void
181192
$options['environment'] = $this->app->environment();
182193
}
183194

195+
if ($this->app instanceof Lumen) {
196+
$wrapBeforeSend = function (?callable $userBeforeSend) {
197+
return function (Event $event, ?EventHint $eventHint) use ($userBeforeSend) {
198+
$request = $this->app->make(Request::class);
199+
200+
if ($request !== null) {
201+
$route = $request->route();
202+
203+
if ($route !== null) {
204+
[$routeName, $transactionSource] = Integration::extractNameAndSourceForLumenRoute($request->route(), $request->path());
205+
206+
$event->setTransaction($routeName);
207+
208+
$transactionMetadata = $event->getSdkMetadata('transaction_metadata');
209+
210+
if ($transactionMetadata instanceof TransactionMetadata) {
211+
$transactionMetadata->setSource($transactionSource);
212+
}
213+
}
214+
}
215+
216+
if ($userBeforeSend !== null) {
217+
return $userBeforeSend($event, $eventHint);
218+
}
219+
220+
return $event;
221+
};
222+
};
223+
224+
$options['before_send'] = $wrapBeforeSend($options['before_send'] ?? null);
225+
$options['before_send_transaction'] = $wrapBeforeSend($options['before_send_transaction'] ?? null);
226+
}
227+
184228
$clientBuilder = ClientBuilder::create($options);
185229

186230
// Set the Laravel SDK identifier and version

src/Sentry/Laravel/Tracing/ServiceProvider.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Illuminate\View\Engines\EngineResolver;
1414
use Illuminate\View\Factory as ViewFactory;
1515
use InvalidArgumentException;
16+
use Laravel\Lumen\Application as Lumen;
1617
use Sentry\Laravel\BaseServiceProvider;
1718
use Sentry\Laravel\Tracing\Routing\TracingCallableDispatcherTracing;
1819
use Sentry\Laravel\Tracing\Routing\TracingControllerDispatcherTracing;
@@ -31,9 +32,11 @@ public function boot(): void
3132
return;
3233
}
3334

34-
$this->app->booted(function () {
35-
$this->app->make(Middleware::class)->setBootedTimestamp();
36-
});
35+
if (!$this->app instanceof Lumen) {
36+
$this->app->booted(function () {
37+
$this->app->make(Middleware::class)->setBootedTimestamp();
38+
});
39+
}
3740

3841
$tracingConfig = $this->getUserConfig()['tracing'] ?? [];
3942

@@ -43,8 +46,9 @@ public function boot(): void
4346

4447
$this->decorateRoutingDispatchers();
4548

46-
if ($this->app->bound(HttpKernelInterface::class)) {
47-
/** @var \Illuminate\Foundation\Http\Kernel $httpKernel */
49+
if ($this->app instanceof Lumen) {
50+
$this->app->middleware(Middleware::class);
51+
} elseif ($this->app->bound(HttpKernelInterface::class)) {
4852
$httpKernel = $this->app->make(HttpKernelInterface::class);
4953

5054
if ($httpKernel instanceof HttpKernel) {

test/Sentry/IntegrationTest.php

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,56 @@ public function testExtractingNameForRouteWithoutName(): void
9595
{
9696
$route = new Route('GET', $url = '/foo', []);
9797

98-
$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
98+
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
9999
}
100100

101101
public function testExtractingNameForRouteWithAutoGeneratedName(): void
102102
{
103103
// We fake a generated name here, Laravel generates them each starting with `generated::`
104104
$route = (new Route('GET', $url = '/foo', []))->name('generated::KoAePbpBofo01ey4');
105105

106-
$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
106+
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
107107
}
108108

109109
public function testExtractingNameForRouteWithIncompleteGroupName(): void
110110
{
111111
$route = (new Route('GET', $url = '/foo', []))->name('group-name.');
112112

113-
$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
113+
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
114+
}
115+
116+
public function testExtractingNameForLumenRouteWithoutName(): void
117+
{
118+
$url = '/some-route';
119+
120+
$this->assertLumenRouteNameAndSource([0, [], []], $url, $url, TransactionSource::route());
121+
}
122+
123+
public function testExtractingNameForLumenRouteWithParamInUrl(): void
124+
{
125+
$route = [1, [], ['param1' => 'foo']];
126+
127+
$url = '/foo/bar/baz';
128+
129+
$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/bar/baz', TransactionSource::route());
130+
}
131+
132+
public function testExtractingNameForLumenRouteWithParamsInUrl(): void
133+
{
134+
$route = [1, [], ['param1' => 'foo', 'param2' => 'bar']];
135+
136+
$url = '/foo/bar/baz';
137+
138+
$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/{param2}/baz', TransactionSource::route());
139+
}
140+
141+
public function testExtractingNameForLumenRouteWithParamsWithSameValueInUrl(): void
142+
{
143+
$route = [1, [], ['param1' => 'foo', 'param2' => 'foo']];
144+
145+
$url = '/foo/foo/bar';
146+
147+
$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/{param2}/bar', TransactionSource::route());
114148
}
115149

116150
public function testExceptionReportedUsingReportHelperIsNotMarkedAsUnhandled(): void
@@ -143,11 +177,19 @@ public function testExceptionIsNotMarkedAsUnhandled(): void
143177
$this->assertFalse($hint->mechanism->isHandled());
144178
}
145179

146-
private function assetRouteNameAndSource(Route $route, string $expectedName, TransactionSource $expectedSource): void
180+
private function assertRouteNameAndSource(Route $route, string $expectedName, TransactionSource $expectedSource): void
147181
{
148182
[$actualName, $actualSource] = Integration::extractNameAndSourceForRoute($route);
149183

150184
$this->assertSame($expectedName, $actualName);
151185
$this->assertSame($expectedSource, $actualSource);
152186
}
187+
188+
private function assertLumenRouteNameAndSource(array $routeData, string $path, string $expectedName, TransactionSource $expectedSource): void
189+
{
190+
[$actualName, $actualSource] = Integration::extractNameAndSourceForLumenRoute($routeData, $path);
191+
192+
$this->assertSame($expectedName, $actualName);
193+
$this->assertSame($expectedSource, $actualSource);
194+
}
153195
}

0 commit comments

Comments
 (0)