Skip to content

Commit

Permalink
Reintroduce Lumen support (#685)
Browse files Browse the repository at this point in the history
  • Loading branch information
stayallive committed May 8, 2023
1 parent b8ca66c commit d30d531
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 21 deletions.
11 changes: 5 additions & 6 deletions README.md
Expand Up @@ -21,7 +21,7 @@ This is the official Laravel SDK for [Sentry](https://sentry.io).

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

For other Laravel or Lumen versions see:
For older Laravel versions and Lumen see:

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

> 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.
> 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.
### Configure

Expand Down Expand Up @@ -78,11 +78,11 @@ try {
}
```

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

## Laravel Version Compatibility

The Laravel versions listed below are all currently supported:
The Laravel and Lumen versions listed below are all currently supported:

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

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.

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

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

## Contributing to the SDK

Expand Down
3 changes: 0 additions & 3 deletions composer.json
Expand Up @@ -28,9 +28,6 @@
"symfony/psr-http-message-bridge": "^1.0 | ^2.0",
"nyholm/psr7": "^1.0"
},
"conflict": {
"laravel/lumen-framework": "*"
},
"autoload": {
"psr-0": {
"Sentry\\Laravel\\": "src/"
Expand Down
32 changes: 31 additions & 1 deletion src/Sentry/Laravel/Integration.php
Expand Up @@ -117,7 +117,7 @@ public static function flushEvents(): void
*
* @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.
* @internal This helper is used in various places to extract meaningful info from a Laravel Route object.
*/
public static function extractNameAndSourceForRoute(Route $route): array
{
Expand All @@ -127,6 +127,36 @@ public static function extractNameAndSourceForRoute(Route $route): array
];
}

/**
* 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 extract meaningful info from Lumen route data.
*/
public static function extractNameAndSourceForLumenRoute(array $routeData, string $path): array
{
$routeUri = array_reduce(
array_keys($routeData[2]),
static function ($carry, $key) use ($routeData) {
$search = '/' . preg_quote($routeData[2][$key], '/') . '/';

// Replace the first occurrence of the route parameter value with the key name
// This is by no means a perfect solution, but it's the best we can do with the data we have
return preg_replace($search, "{{$key}}", $carry, 1);
},
$path
);

return [
'/' . ltrim($routeUri, '/'),
TransactionSource::route(),
];
}

/**
* Retrieve the meta tags with tracing information to link this request to front-end requests.
* This propagates the Dynamic Sampling Context.
Expand Down
48 changes: 46 additions & 2 deletions src/Sentry/Laravel/ServiceProvider.php
Expand Up @@ -7,10 +7,14 @@
use Illuminate\Contracts\Http\Kernel as HttpKernelInterface;
use Illuminate\Foundation\Application as Laravel;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Http\Request;
use Illuminate\Log\LogManager;
use Laravel\Lumen\Application as Lumen;
use RuntimeException;
use Sentry\ClientBuilder;
use Sentry\ClientBuilderInterface;
use Sentry\Event;
use Sentry\EventHint;
use Sentry\Integration as SdkIntegration;
use Sentry\Laravel\Console\PublishCommand;
use Sentry\Laravel\Console\TestCommand;
Expand All @@ -21,6 +25,7 @@
use Sentry\SentrySdk;
use Sentry\State\Hub;
use Sentry\State\HubInterface;
use Sentry\Tracing\TransactionMetadata;

class ServiceProvider extends BaseServiceProvider
{
Expand Down Expand Up @@ -61,8 +66,10 @@ public function boot(): void

$this->setupFeatures();

if ($this->app->bound(HttpKernelInterface::class)) {
/** @var \Illuminate\Foundation\Http\Kernel $httpKernel */
if ($this->app instanceof Lumen) {
$this->app->middleware(SetRequestMiddleware::class);
$this->app->middleware(SetRequestIpMiddleware::class);
} elseif ($this->app->bound(HttpKernelInterface::class)) {
$httpKernel = $this->app->make(HttpKernelInterface::class);

if ($httpKernel instanceof HttpKernel) {
Expand All @@ -88,6 +95,10 @@ public function boot(): void
*/
public function register(): void
{
if ($this->app instanceof Lumen) {
$this->app->configure(static::$abstract);
}

$this->mergeConfigFrom(__DIR__ . '/../../../config/sentry.php', static::$abstract);

$this->configureAndRegisterClient();
Expand Down Expand Up @@ -181,6 +192,39 @@ protected function configureAndRegisterClient(): void
$options['environment'] = $this->app->environment();
}

if ($this->app instanceof Lumen) {
$wrapBeforeSend = function (?callable $userBeforeSend) {
return function (Event $event, ?EventHint $eventHint) use ($userBeforeSend) {
$request = $this->app->make(Request::class);

if ($request !== null) {
$route = $request->route();

if ($route !== null) {
[$routeName, $transactionSource] = Integration::extractNameAndSourceForLumenRoute($request->route(), $request->path());

$event->setTransaction($routeName);

$transactionMetadata = $event->getSdkMetadata('transaction_metadata');

if ($transactionMetadata instanceof TransactionMetadata) {
$transactionMetadata->setSource($transactionSource);
}
}
}

if ($userBeforeSend !== null) {
return $userBeforeSend($event, $eventHint);
}

return $event;
};
};

$options['before_send'] = $wrapBeforeSend($options['before_send'] ?? null);
$options['before_send_transaction'] = $wrapBeforeSend($options['before_send_transaction'] ?? null);
}

$clientBuilder = ClientBuilder::create($options);

// Set the Laravel SDK identifier and version
Expand Down
14 changes: 9 additions & 5 deletions src/Sentry/Laravel/Tracing/ServiceProvider.php
Expand Up @@ -13,6 +13,7 @@
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Factory as ViewFactory;
use InvalidArgumentException;
use Laravel\Lumen\Application as Lumen;
use Sentry\Laravel\BaseServiceProvider;
use Sentry\Laravel\Tracing\Routing\TracingCallableDispatcherTracing;
use Sentry\Laravel\Tracing\Routing\TracingControllerDispatcherTracing;
Expand All @@ -31,9 +32,11 @@ public function boot(): void
return;
}

$this->app->booted(function () {
$this->app->make(Middleware::class)->setBootedTimestamp();
});
if (!$this->app instanceof Lumen) {
$this->app->booted(function () {
$this->app->make(Middleware::class)->setBootedTimestamp();
});
}

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

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

$this->decorateRoutingDispatchers();

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

if ($httpKernel instanceof HttpKernel) {
Expand Down
50 changes: 46 additions & 4 deletions test/Sentry/IntegrationTest.php
Expand Up @@ -95,22 +95,56 @@ public function testExtractingNameForRouteWithoutName(): void
{
$route = new Route('GET', $url = '/foo', []);

$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
}

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

$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
}

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

$this->assetRouteNameAndSource($route, $url, TransactionSource::route());
$this->assertRouteNameAndSource($route, $url, TransactionSource::route());
}

public function testExtractingNameForLumenRouteWithoutName(): void
{
$url = '/some-route';

$this->assertLumenRouteNameAndSource([0, [], []], $url, $url, TransactionSource::route());
}

public function testExtractingNameForLumenRouteWithParamInUrl(): void
{
$route = [1, [], ['param1' => 'foo']];

$url = '/foo/bar/baz';

$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/bar/baz', TransactionSource::route());
}

public function testExtractingNameForLumenRouteWithParamsInUrl(): void
{
$route = [1, [], ['param1' => 'foo', 'param2' => 'bar']];

$url = '/foo/bar/baz';

$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/{param2}/baz', TransactionSource::route());
}

public function testExtractingNameForLumenRouteWithParamsWithSameValueInUrl(): void
{
$route = [1, [], ['param1' => 'foo', 'param2' => 'foo']];

$url = '/foo/foo/bar';

$this->assertLumenRouteNameAndSource($route, $url, '/{param1}/{param2}/bar', TransactionSource::route());
}

public function testExceptionReportedUsingReportHelperIsNotMarkedAsUnhandled(): void
Expand Down Expand Up @@ -143,11 +177,19 @@ public function testExceptionIsNotMarkedAsUnhandled(): void
$this->assertFalse($hint->mechanism->isHandled());
}

private function assetRouteNameAndSource(Route $route, string $expectedName, TransactionSource $expectedSource): void
private function assertRouteNameAndSource(Route $route, string $expectedName, TransactionSource $expectedSource): void
{
[$actualName, $actualSource] = Integration::extractNameAndSourceForRoute($route);

$this->assertSame($expectedName, $actualName);
$this->assertSame($expectedSource, $actualSource);
}

private function assertLumenRouteNameAndSource(array $routeData, string $path, string $expectedName, TransactionSource $expectedSource): void
{
[$actualName, $actualSource] = Integration::extractNameAndSourceForLumenRoute($routeData, $path);

$this->assertSame($expectedName, $actualName);
$this->assertSame($expectedSource, $actualSource);
}
}

0 comments on commit d30d531

Please sign in to comment.