Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reintroduce Lumen support #685

Merged
merged 5 commits into from May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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`
stayallive marked this conversation as resolved.
Show resolved Hide resolved

## 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);
}
}