From 69b666a0ea1db24563638930d5ba2d443897d86d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 10:20:01 +0200 Subject: [PATCH 01/13] feat: Add Tracing (#358) * feat: Add Tracing Middleware Record sql queries as spans * feat: Auto prepend middleware Instrument view renders * ref: Change code comments * ref: Use terminate to send transaction * Rename view engine decorator * Improve transaction context name/description * Prevent crashes when using missing defines * Do not remove all leading `/` keep 1 * Add fallback autoload + bootstrap span * Set the transaction name from the event handler * Cleanup query span * Prevent errors on Laravel 5.3 and below * CS * feat: Use correct route and add data * ref: Add name * fix: Route naming * feat: Start transaction in serviceProvider Move all renders as a child * ref: Rename to view.render * ref: Move back to starting transaction in middleware Use fromTraceparent function to start a trace * ref: Small refactor * feat: Update composer.json * ref: Add traces_sample_rate * Refactor active span retrieval * Guard against the active span not being set * Docblock updates * Correctly return rendered view without span * Move tracing related code to the Tracing namespace * Improve the route name detection order * Do not use route names ending with a `.` * Fix not wrapping the view enines when resolver is already resolved * feat: Rework code to use transaction on the hub Co-authored-by: Alex Bouma --- composer.json | 7 +- config/sentry.php | 1 + src/Sentry/Laravel/EventHandler.php | 39 +++--- src/Sentry/Laravel/Integration.php | 83 ++++++++++++- src/Sentry/Laravel/ServiceProvider.php | 1 + src/Sentry/Laravel/Tracing/Middleware.php | 117 ++++++++++++++++++ .../Laravel/Tracing/ServiceProvider.php | 63 ++++++++++ .../Laravel/Tracing/ViewEngineDecorator.php | 59 +++++++++ 8 files changed, 347 insertions(+), 23 deletions(-) create mode 100644 src/Sentry/Laravel/Tracing/Middleware.php create mode 100644 src/Sentry/Laravel/Tracing/ServiceProvider.php create mode 100644 src/Sentry/Laravel/Tracing/ViewEngineDecorator.php diff --git a/composer.json b/composer.json index 33620d4d..f5033cf4 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,10 @@ "require": { "php": "^7.1", "illuminate/support": "5.0 - 5.8 | ^6.0 | ^7.0", - "sentry/sdk": "^2.1" + "sentry/sdk": "3.x-dev" }, + "minimum-stability": "dev", + "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^8.0", "laravel/framework": "^6.0", @@ -53,7 +55,8 @@ }, "laravel": { "providers": [ - "Sentry\\Laravel\\ServiceProvider" + "Sentry\\Laravel\\ServiceProvider", + "Sentry\\Laravel\\Tracing\\ServiceProvider" ], "aliases": { "Sentry": "Sentry\\Laravel\\Facade" diff --git a/config/sentry.php b/config/sentry.php index 8c91c3db..949de548 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -27,4 +27,5 @@ // @see: https://docs.sentry.io/error-reporting/configuration/?platform=php#send-default-pii 'send_default_pii' => false, + 'traces_sample_rate' => 0, ]; diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php index 896259e7..326c506f 100644 --- a/src/Sentry/Laravel/EventHandler.php +++ b/src/Sentry/Laravel/EventHandler.php @@ -16,11 +16,12 @@ use Illuminate\Queue\QueueManager; use Illuminate\Routing\Events\RouteMatched; use Illuminate\Routing\Route; -use Illuminate\Support\Str; use RuntimeException; use Sentry\Breadcrumb; use Sentry\SentrySdk; use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; +use Sentry\Tracing\Transaction; class EventHandler { @@ -194,26 +195,16 @@ public function __call($method, $arguments) */ protected function routerMatchedHandler(Route $route) { - $routeName = null; + $routeName = Integration::extractNameForRoute($route) ?? ''; - if ($route->getName()) { - // someaction (route name/alias) - $routeName = $route->getName(); + $transaction = SentrySdk::getCurrentHub()->getTransaction(); - // 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::startsWith($routeName, 'generated::')) { - $routeName = null; - } - } - - if (empty($routeName) && $route->getActionName()) { - // SomeController@someAction (controller action) - $routeName = $route->getActionName(); - } elseif (empty($routeName) || $routeName === 'Closure') { - // /someaction // Fallback to the url - $routeName = $route->uri(); + if ($transaction instanceof Transaction) { + $transaction->setName($routeName); + $transaction->setData([ + 'action' => $route->getActionName(), + 'name' => $route->getName() + ]); } Integration::addBreadcrumb(new Breadcrumb( @@ -287,6 +278,16 @@ private function addQueryBreadcrumb($query, $bindings, $time, $connectionName) $data['bindings'] = $bindings; } + $transaction = SentrySdk::getCurrentHub()->getTransaction(); + if (null !== $transaction) { + $context = new SpanContext(); + $context->op = 'sql.query'; + $context->description = $query; + $context->startTimestamp = microtime(true) - $time / 1000; + $context->endTimestamp = $context->startTimestamp + $time / 1000; + $transaction->startChild($context); + } + Integration::addBreadcrumb(new Breadcrumb( Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, diff --git a/src/Sentry/Laravel/Integration.php b/src/Sentry/Laravel/Integration.php index 7c6eff68..01d215c9 100644 --- a/src/Sentry/Laravel/Integration.php +++ b/src/Sentry/Laravel/Integration.php @@ -2,8 +2,11 @@ namespace Sentry\Laravel; +use Illuminate\Routing\Route; +use Illuminate\Support\Str; use Sentry\FlushableClientInterface; use Sentry\SentrySdk; +use Sentry\Tracing\Span; use function Sentry\addBreadcrumb; use function Sentry\configureScope; use Sentry\Breadcrumb; @@ -30,7 +33,9 @@ public function setupOnce(): void return $event; } - $event->setTransaction($self->getTransaction()); + if (null === $event->getTransaction()) { + $event->setTransaction($self->getTransaction()); + } return $event; }); @@ -71,7 +76,7 @@ public static function configureScope(callable $callback): void /** * @return null|string */ - public static function getTransaction() + public static function getTransaction(): ?string { return self::$transaction; } @@ -98,4 +103,78 @@ public static function flushEvents(): void $client->flush(); } } + + /** + * Extract the readable name for a route. + * + * @param \Illuminate\Routing\Route $route + * + * @return string|null + */ + public static function extractNameForRoute(Route $route): ?string + { + $routeName = null; + + if (empty($routeName) && $route->getName()) { + // someaction (route name/alias) + $routeName = $route->getName(); + + // 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::startsWith($routeName, 'generated::')) { + $routeName = 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($routeName, '.')) { + $routeName = null; + } + } + + if (empty($routeName) && $route->getActionName()) { + // SomeController@someAction (controller action) + $routeName = ltrim($route->getActionName(), '\\'); + } + + if (empty($routeName) || $routeName === 'Closure') { + // /someaction // Fallback to the url + $routeName = '/' . ltrim($route->uri(), '/'); + } + + return $routeName; + } + + /** + * 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('', $span->toTraceparent()); + // $content .= sprintf('', $span->getDescription()); + + 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(); + } } diff --git a/src/Sentry/Laravel/ServiceProvider.php b/src/Sentry/Laravel/ServiceProvider.php index 11f48488..7005bc6d 100644 --- a/src/Sentry/Laravel/ServiceProvider.php +++ b/src/Sentry/Laravel/ServiceProvider.php @@ -2,6 +2,7 @@ namespace Sentry\Laravel; +use Illuminate\Contracts\Http\Kernel as HttpKernelInterface; use Sentry\SentrySdk; use Sentry\State\Hub; use Sentry\ClientBuilder; diff --git a/src/Sentry/Laravel/Tracing/Middleware.php b/src/Sentry/Laravel/Tracing/Middleware.php new file mode 100644 index 00000000..074e7c04 --- /dev/null +++ b/src/Sentry/Laravel/Tracing/Middleware.php @@ -0,0 +1,117 @@ +bound('sentry')) { + $this->startTransaction($request, app('sentry')); + } + + return $next($request); + } + + /** + * Handle the application termination. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * + * @return void + */ + public function terminate($request, $response): void + { + if ($this->transaction !== null && app()->bound('sentry')) { + $this->transaction->finish(); + } + } + + private function startTransaction(Request $request, Hub $sentry): void + { + $path = '/' . ltrim($request->path(), '/'); + $fallbackTime = microtime(true); + $sentryTraceHeader = $request->header('sentry-trace'); + + $context = $sentryTraceHeader + ? TransactionContext::fromTraceparent($sentryTraceHeader) + : new TransactionContext; + + $context->op = 'http.server'; + $context->name = $path; + $context->data = [ + 'url' => $path, + 'method' => strtoupper($request->method()), + ]; + $context->startTimestamp = $request->server('REQUEST_TIME_FLOAT', $fallbackTime); + + $this->transaction = $sentry->startTransaction($context); + + // Setting the Transaction on the Hub + SentrySdk::getCurrentHub()->setSpan($this->transaction); + + if (!$this->addBootTimeSpans()) { + // @TODO: We might want to move this together with the `RouteMatches` listener to some central place and or do this from the `EventHandler` + app()->booted(function () use ($request, $fallbackTime): void { + $spanContextStart = new SpanContext(); + $spanContextStart->op = 'app.bootstrap'; + $spanContextStart->startTimestamp = defined('LARAVEL_START') ? LARAVEL_START : $request->server('REQUEST_TIME_FLOAT', $fallbackTime); + $spanContextStart->endTimestamp = microtime(true); + $this->transaction->startChild($spanContextStart); + }); + } + } + + private function addBootTimeSpans(): bool + { + if (!defined('LARAVEL_START') || !LARAVEL_START) { + return false; + } + + if (!defined('SENTRY_AUTOLOAD') || !SENTRY_AUTOLOAD) { + return false; + } + + if (!defined('SENTRY_BOOTSTRAP') || !SENTRY_BOOTSTRAP) { + return false; + } + + $spanContextStart = new SpanContext(); + $spanContextStart->op = 'autoload'; + $spanContextStart->startTimestamp = LARAVEL_START; + $spanContextStart->endTimestamp = SENTRY_AUTOLOAD; + $this->transaction->startChild($spanContextStart); + + $spanContextStart = new SpanContext(); + $spanContextStart->op = 'bootstrap'; + $spanContextStart->startTimestamp = SENTRY_AUTOLOAD; + $spanContextStart->endTimestamp = SENTRY_BOOTSTRAP; + $this->transaction->startChild($spanContextStart); + + return true; + } +} diff --git a/src/Sentry/Laravel/Tracing/ServiceProvider.php b/src/Sentry/Laravel/Tracing/ServiceProvider.php new file mode 100644 index 00000000..084ee835 --- /dev/null +++ b/src/Sentry/Laravel/Tracing/ServiceProvider.php @@ -0,0 +1,63 @@ +app->bound(HttpKernelInterface::class)) { + /** @var \Illuminate\Contracts\Http\Kernel $httpKernel */ + $httpKernel = $this->app->make(HttpKernelInterface::class); + + $httpKernel->prependMiddleware(Middleware::class); + } + } + + public function register(): void + { + $this->app->singleton(Middleware::class); + + $viewEngineWrapper = function (EngineResolver $engineResolver): void { + foreach (['file', 'php', 'blade'] as $engineName) { + try { + $realEngine = $engineResolver->resolve($engineName); + + $engineResolver->register($engineName, function () use ($realEngine) { + return $this->wrapViewEngine($realEngine); + }); + } catch (InvalidArgumentException $e) { + // The `file` engine was introduced in Laravel 5.4. On lower Laravel versions + // resolving that driver will throw an `InvalidArgumentException`. We can + // ignore this exception because we can't wrap drivers that don't exist + } + } + }; + + if ($this->app->resolved('view.engine.resolver')) { + $viewEngineWrapper($this->app->make('view.engine.resolver')); + } else { + $this->app->afterResolving('view.engine.resolver', $viewEngineWrapper); + } + } + + private function wrapViewEngine(Engine $realEngine): Engine + { + /** @var ViewFactory $viewFactory */ + $viewFactory = $this->app->make('view'); + + /** @noinspection UnusedFunctionResultInspection */ + $viewFactory->composer('*', static function (View $view) use ($viewFactory) : void { + $viewFactory->share(ViewEngineDecorator::SHARED_KEY, $view->name()); + }); + + return new ViewEngineDecorator($realEngine, $viewFactory); + } +} diff --git a/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php new file mode 100644 index 00000000..c059273c --- /dev/null +++ b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php @@ -0,0 +1,59 @@ +engine = $engine; + $this->viewFactory = $viewFactory; + } + + /** + * {@inheritdoc} + */ + public function get($path, array $data = []): string + { + $parentSpan = Integration::currentTracingSpan(); + + if ($parentSpan === null) { + return $this->engine->get($path, $data); + } + + $context = new SpanContext(); + $context->op = 'view.render'; + $context->description = $this->viewFactory->shared(self::SHARED_KEY, basename($path)); + + $span = $parentSpan->startChild($context); + + $result = $this->engine->get($path, $data); + + $span->finish(); + + return $result; + } + + /** + * Laravel uses this function internally + */ + public function getCompiler(): CompilerInterface + { + return $this->engine->getCompiler(); + } +} From bde49a17556f097feb81c5efbec95307ea80fcbd Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 11:31:33 +0200 Subject: [PATCH 02/13] meta: Bump version 2.0.0-beta.0 --- src/Sentry/Laravel/Tracing/Middleware.php | 1 - src/Sentry/Laravel/Version.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Sentry/Laravel/Tracing/Middleware.php b/src/Sentry/Laravel/Tracing/Middleware.php index 074e7c04..ebb6f06f 100644 --- a/src/Sentry/Laravel/Tracing/Middleware.php +++ b/src/Sentry/Laravel/Tracing/Middleware.php @@ -6,7 +6,6 @@ use Illuminate\Http\Request; use Sentry\SentrySdk; use Sentry\State\Hub; -use Sentry\State\Scope; use Sentry\Tracing\SpanContext; use Sentry\Tracing\TransactionContext; diff --git a/src/Sentry/Laravel/Version.php b/src/Sentry/Laravel/Version.php index 1247c1b4..11a4cceb 100644 --- a/src/Sentry/Laravel/Version.php +++ b/src/Sentry/Laravel/Version.php @@ -5,5 +5,5 @@ final class Version { public const SDK_IDENTIFIER = 'sentry.php.laravel'; - public const SDK_VERSION = '1.8.0'; + public const SDK_VERSION = '2.0.0-beta.0'; } From 0d1da2d88b2bbb38ac956514af0c039e1d67a256 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 13:16:48 +0200 Subject: [PATCH 03/13] meta: Change version 2.0.0-beta1 --- src/Sentry/Laravel/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Laravel/Version.php b/src/Sentry/Laravel/Version.php index 11a4cceb..36cf7fa5 100644 --- a/src/Sentry/Laravel/Version.php +++ b/src/Sentry/Laravel/Version.php @@ -5,5 +5,5 @@ final class Version { public const SDK_IDENTIFIER = 'sentry.php.laravel'; - public const SDK_VERSION = '2.0.0-beta.0'; + public const SDK_VERSION = '2.0.0-beta1'; } From 339dd08f2061f77c26995869468768d03fc1f44e Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 14:03:26 +0200 Subject: [PATCH 04/13] meta: Define beta version 3.0.0-beta1 for php sdk --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f5033cf4..9dba5868 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "require": { "php": "^7.1", "illuminate/support": "5.0 - 5.8 | ^6.0 | ^7.0", - "sentry/sdk": "3.x-dev" + "sentry/sdk": "3.0.0-beta1" }, "minimum-stability": "dev", "prefer-stable": true, From a101335a81282f5c799b4f73aeed699a7f66c07a Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 3 Sep 2020 14:06:36 +0200 Subject: [PATCH 05/13] meta: Changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b2aec5f..33710e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## 2.0.0-beta1 + +**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are +using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using +[sentry.io](https://sentry.io) nothing will change and no action is needed. + +- Using `3.0.0-beta1` of Sentry PHP SDK +- Add support for Tracing, enable it by setting `traces_sample_rate` in the config to a value > 1 + ## 1.8.0 - Add `send_default_pii` option by default to published config file (#340) From 98acba16de11f34ad76918fbf52e90fa2fc6d23d Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 17 Sep 2020 11:24:51 +0200 Subject: [PATCH 06/13] Update CHANGELOG.md Co-authored-by: Alex Bouma --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f876236d..865de8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ using an on-premise installation it requires Sentry version `>= v20.6.0` to work [sentry.io](https://sentry.io) nothing will change and no action is needed. - Using `3.0.0-beta1` of Sentry PHP SDK -- Add support for Tracing, enable it by setting `traces_sample_rate` in the config to a value > 1 +- Add support for Tracing, enable it by setting `traces_sample_rate` in the config to a value > 0 (the value should be larger than `0.0` and smaller or equal than `1.0` (to send everything)) ## 1.9.0 From fa048e0e9da4bf1daf4fe18273408b8bc6cf43b6 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 22 Sep 2020 12:14:49 +0200 Subject: [PATCH 07/13] feat: Add publish command (#379) * feat: Add publish command * fix: Typos --- config/sentry.php | 2 +- src/Sentry/Laravel/PublishConfigCommand.php | 86 +++++++++++++++++++++ src/Sentry/Laravel/ServiceProvider.php | 2 + src/Sentry/Laravel/TestCommand.php | 41 +++++++--- 4 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 src/Sentry/Laravel/PublishConfigCommand.php diff --git a/config/sentry.php b/config/sentry.php index 02f64bd5..f71f830d 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -30,5 +30,5 @@ // @see: https://docs.sentry.io/error-reporting/configuration/?platform=php#send-default-pii 'send_default_pii' => false, - 'traces_sample_rate' => 0, + 'traces_sample_rate' => \floatval(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)), ]; diff --git a/src/Sentry/Laravel/PublishConfigCommand.php b/src/Sentry/Laravel/PublishConfigCommand.php new file mode 100644 index 00000000..1296749e --- /dev/null +++ b/src/Sentry/Laravel/PublishConfigCommand.php @@ -0,0 +1,86 @@ +info('[Sentry] Publishing config ...'); + $this->call('vendor:publish', [ + '--provider' => 'Sentry\Laravel\ServiceProvider' + ]); + + if ($this->confirm('Enable Performance Monitoring?', true)) { + $this->setEnvironmentValue(['SENTRY_TRACES_SAMPLE_RATE' => 1.0]); + + $this->info('[Sentry] Added `SENTRY_TRACES_SAMPLE_RATE=1` to your .env file.'); + + $testCommandPrompt = 'Want to send a test Event & Transaction?'; + $args = ['--transaction' => true]; + } else { + $testCommandPrompt = 'Want to send a test Event?'; + $args = []; + } + + if ($this->confirm($testCommandPrompt, true)) { + $this->call('sentry:test', $args); + } + } + + public function setEnvironmentValue(array $values) + { + $envFile = app()->environmentFilePath(); + $str = file_get_contents($envFile); + + if (count($values) > 0) { + foreach ($values as $envKey => $envValue) { + + $str .= "\n"; // In case the searched variable is in the last line without \n + $keyPosition = strpos($str, "{$envKey}="); + $endOfLinePosition = strpos($str, "\n", $keyPosition); + $oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition); + + // If key does not exist, add it + if (!$keyPosition || !$endOfLinePosition || !$oldLine) { + $str .= "{$envKey}={$envValue}\n"; + } else { + $str = str_replace($oldLine, "{$envKey}={$envValue}", $str); + } + + } + } + + $str = substr($str, 0, -1); + if (!file_put_contents($envFile, $str)) return false; + return true; + } +} diff --git a/src/Sentry/Laravel/ServiceProvider.php b/src/Sentry/Laravel/ServiceProvider.php index 64b17e98..cb0538b8 100644 --- a/src/Sentry/Laravel/ServiceProvider.php +++ b/src/Sentry/Laravel/ServiceProvider.php @@ -13,6 +13,7 @@ use Sentry\Integration as SdkIntegration; use Illuminate\Foundation\Application as Laravel; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; +use Illuminate\Support\Facades\Storage; class ServiceProvider extends IlluminateServiceProvider { @@ -92,6 +93,7 @@ protected function registerArtisanCommands(): void { $this->commands([ TestCommand::class, + PublishConfigCommand::class, ]); } diff --git a/src/Sentry/Laravel/TestCommand.php b/src/Sentry/Laravel/TestCommand.php index 08a69185..e344f542 100644 --- a/src/Sentry/Laravel/TestCommand.php +++ b/src/Sentry/Laravel/TestCommand.php @@ -4,6 +4,8 @@ use Exception; use Illuminate\Console\Command; +use Sentry\Tracing\SpanContext; +use Sentry\Tracing\TransactionContext; class TestCommand extends Command { @@ -19,7 +21,7 @@ class TestCommand extends Command * * @var string */ - protected $signature = 'sentry:test'; + protected $signature = 'sentry:test {--transaction}'; /** * The console command description. @@ -43,32 +45,51 @@ public function handle() $hub = app('sentry'); if ($hub->getClient()->getOptions()->getDsn()) { - $this->info('[sentry] Client DSN discovered!'); + $this->info('[Sentry] DSN discovered!'); } else { - $this->error('[sentry] Could not discover DSN!'); - $this->error('[sentry] Please check if you DSN is set properly in your config or `.env` as `SENTRY_LARAVEL_DSN`.'); + $this->error('[Sentry] Could not discover DSN!'); + $this->error('[Sentry] Please check if you DSN is set properly in your config or `.env` as `SENTRY_LARAVEL_DSN`.'); return; } - $this->info('[sentry] Generating test event'); + if ($this->option('transaction')) { + $hub->getClient()->getOptions()->setTracesSampleRate(1); + } + + $transactionContext = new TransactionContext(); + $transactionContext->setName('Sentry Test Transaction'); + $transactionContext->setOp('sentry.test'); + $transaction = $hub->startTransaction($transactionContext); + + $spanContext = new SpanContext(); + $spanContext->setOp('sentry.sent'); + $span1 = $transaction->startChild($spanContext); + + $this->info('[Sentry] Generating test Event'); $ex = $this->generateTestException('command name', ['foo' => 'bar']); $hub->captureException($ex); - $this->info('[sentry] Sending test event'); + $this->info('[Sentry] Sending test Event'); + + $span1->finish(); + $result = $transaction->finish(); + if ($result) { + $this->info('[Sentry] Sending test Transaction'); + } $lastEventId = $hub->getLastEventId(); if (!$lastEventId) { - $this->error('[sentry] There was an error sending the test event.'); - $this->error('[sentry] Please check if you DSN is set properly in your config or `.env` as `SENTRY_LARAVEL_DSN`.'); + $this->error('[Sentry] There was an error sending the test event.'); + $this->error('[Sentry] Please check if you DSN is set properly in your config or `.env` as `SENTRY_LARAVEL_DSN`.'); } else { - $this->info("[sentry] Event sent with ID: {$lastEventId}"); + $this->info("[Sentry] Event sent with ID: {$lastEventId}"); } } catch (Exception $e) { - $this->error("[sentry] {$e->getMessage()}"); + $this->error("[Sentry] {$e->getMessage()}"); } error_reporting($old_error_reporting); From 9ce1ae39dccc2f4d0f8a316f0885bd757454c9a4 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 24 Sep 2020 11:35:57 +0200 Subject: [PATCH 08/13] feat: Counterpart to PHP polish apm pr (#384) --- src/Sentry/Laravel/EventHandler.php | 8 +++--- src/Sentry/Laravel/Tracing/Middleware.php | 28 +++++++++---------- .../Laravel/Tracing/ViewEngineDecorator.php | 4 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php index 326c506f..6018b03e 100644 --- a/src/Sentry/Laravel/EventHandler.php +++ b/src/Sentry/Laravel/EventHandler.php @@ -281,10 +281,10 @@ private function addQueryBreadcrumb($query, $bindings, $time, $connectionName) $transaction = SentrySdk::getCurrentHub()->getTransaction(); if (null !== $transaction) { $context = new SpanContext(); - $context->op = 'sql.query'; - $context->description = $query; - $context->startTimestamp = microtime(true) - $time / 1000; - $context->endTimestamp = $context->startTimestamp + $time / 1000; + $context->setOp('sql.query'); + $context->setDescription($query); + $context->setStartTimestamp(microtime(true) - $time / 1000); + $context->setEndTimestamp($context->getStartTimestamp() + $time / 1000); $transaction->startChild($context); } diff --git a/src/Sentry/Laravel/Tracing/Middleware.php b/src/Sentry/Laravel/Tracing/Middleware.php index ebb6f06f..41fd27d4 100644 --- a/src/Sentry/Laravel/Tracing/Middleware.php +++ b/src/Sentry/Laravel/Tracing/Middleware.php @@ -60,13 +60,13 @@ private function startTransaction(Request $request, Hub $sentry): void ? TransactionContext::fromTraceparent($sentryTraceHeader) : new TransactionContext; - $context->op = 'http.server'; - $context->name = $path; - $context->data = [ + $context->setOp('http.server'); + $context->setName($path); + $context->setData([ 'url' => $path, 'method' => strtoupper($request->method()), - ]; - $context->startTimestamp = $request->server('REQUEST_TIME_FLOAT', $fallbackTime); + ]); + $context->setStartTimestamp($request->server('REQUEST_TIME_FLOAT', $fallbackTime)); $this->transaction = $sentry->startTransaction($context); @@ -77,9 +77,9 @@ private function startTransaction(Request $request, Hub $sentry): void // @TODO: We might want to move this together with the `RouteMatches` listener to some central place and or do this from the `EventHandler` app()->booted(function () use ($request, $fallbackTime): void { $spanContextStart = new SpanContext(); - $spanContextStart->op = 'app.bootstrap'; - $spanContextStart->startTimestamp = defined('LARAVEL_START') ? LARAVEL_START : $request->server('REQUEST_TIME_FLOAT', $fallbackTime); - $spanContextStart->endTimestamp = microtime(true); + $spanContextStart->setOp('app.bootstrap'); + $spanContextStart->setStartTimestamp(defined('LARAVEL_START') ? LARAVEL_START : $request->server('REQUEST_TIME_FLOAT', $fallbackTime)); + $spanContextStart->setEndTimestamp(microtime(true)); $this->transaction->startChild($spanContextStart); }); } @@ -100,15 +100,15 @@ private function addBootTimeSpans(): bool } $spanContextStart = new SpanContext(); - $spanContextStart->op = 'autoload'; - $spanContextStart->startTimestamp = LARAVEL_START; - $spanContextStart->endTimestamp = SENTRY_AUTOLOAD; + $spanContextStart->setOp('autoload'); + $spanContextStart->setStartTimestamp(LARAVEL_START); + $spanContextStart->setEndTimestamp(SENTRY_AUTOLOAD); $this->transaction->startChild($spanContextStart); $spanContextStart = new SpanContext(); - $spanContextStart->op = 'bootstrap'; - $spanContextStart->startTimestamp = SENTRY_AUTOLOAD; - $spanContextStart->endTimestamp = SENTRY_BOOTSTRAP; + $spanContextStart->setOp('bootstrap'); + $spanContextStart->setStartTimestamp(SENTRY_AUTOLOAD); + $spanContextStart->setEndTimestamp(SENTRY_BOOTSTRAP); $this->transaction->startChild($spanContextStart); return true; diff --git a/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php index c059273c..cc715bd7 100644 --- a/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php +++ b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php @@ -37,8 +37,8 @@ public function get($path, array $data = []): string } $context = new SpanContext(); - $context->op = 'view.render'; - $context->description = $this->viewFactory->shared(self::SHARED_KEY, basename($path)); + $context->setOp('view.render'); + $context->setDescription($this->viewFactory->shared(self::SHARED_KEY, basename($path))); $span = $parentSpan->startChild($context); From 1a9cf4d8b9cf1a2db6eb5863f337d61decca2866 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 09:58:15 +0200 Subject: [PATCH 09/13] meta: Prepare 2.x --- CHANGELOG.md | 14 ++++++++++++++ composer.json | 6 ++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865de8b4..28b9180f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## Unreleased +## 2.0.0 + +**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are +using an on-premise installation it requires Sentry version `>= v20.6.0` to work. If you are using +[sentry.io](https://sentry.io) nothing will change and no action is needed. + +**Tracing API / Monitor Performance** + +In this version we released API for Tracing. `\Sentry\startTransaction` is your entry point for manual instrumentation. +More information can be found in our [Performance](https://docs.sentry.io/platforms/php/guides/laravel/performance/) docs. + +- Using `^3.0` of Sentry PHP SDK +- Add support for Tracing, enable it by setting `traces_sample_rate` in the config to a value > 0 (the value should be larger than `0.0` and smaller or equal than `1.0` (to send everything)) + ## 2.0.0-beta1 **Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are diff --git a/composer.json b/composer.json index 8350b810..0a91daf1 100644 --- a/composer.json +++ b/composer.json @@ -21,12 +21,10 @@ } ], "require": { - "php": "^7.1", + "php": "^7.2", "illuminate/support": "5.0 - 5.8 | ^6.0 | ^7.0 | ^8.0", - "sentry/sdk": "3.0.0-beta1" + "sentry/sdk": "^3.0" }, - "minimum-stability": "dev", - "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^8.0", "laravel/framework": "^7.0", From 971e672e88a6ee392a6aee806d3ff6b7f07406c2 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 09:59:55 +0200 Subject: [PATCH 10/13] fix: Travis --- .travis.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 41e52e20..bcad5577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,32 +8,32 @@ php: env: matrix: # All versions below should be test on PHP ^7.1 (Sentry SDK requrement) - - LARAVEL=5.1.* TESTBENCH=3.1.* PHPUNIT=5.7.* SENTRY=^2.3 - - LARAVEL=5.2.* TESTBENCH=3.2.* PHPUNIT=5.7.* SENTRY=^2.3 - - LARAVEL=5.3.* TESTBENCH=3.3.* PHPUNIT=5.7.* SENTRY=^2.3 - - LARAVEL=5.4.* TESTBENCH=3.4.* PHPUNIT=5.7.* SENTRY=^2.3 - - LARAVEL=5.5.* TESTBENCH=3.5.* PHPUNIT=6.5.* SENTRY=^2.3 - - LARAVEL=5.6.* TESTBENCH=3.6.* PHPUNIT=7.5.* SENTRY=^2.3 - - LARAVEL=5.7.* TESTBENCH=3.7.* PHPUNIT=7.5.* SENTRY=^2.3 - - LARAVEL=5.8.* TESTBENCH=3.8.* PHPUNIT=7.5.* SENTRY=^2.3 + - LARAVEL=5.1.* TESTBENCH=3.1.* PHPUNIT=5.7.* SENTRY=^3.0 + - LARAVEL=5.2.* TESTBENCH=3.2.* PHPUNIT=5.7.* SENTRY=^3.0 + - LARAVEL=5.3.* TESTBENCH=3.3.* PHPUNIT=5.7.* SENTRY=^3.0 + - LARAVEL=5.4.* TESTBENCH=3.4.* PHPUNIT=5.7.* SENTRY=^3.0 + - LARAVEL=5.5.* TESTBENCH=3.5.* PHPUNIT=6.5.* SENTRY=^3.0 + - LARAVEL=5.6.* TESTBENCH=3.6.* PHPUNIT=7.5.* SENTRY=^3.0 + - LARAVEL=5.7.* TESTBENCH=3.7.* PHPUNIT=7.5.* SENTRY=^3.0 + - LARAVEL=5.8.* TESTBENCH=3.8.* PHPUNIT=7.5.* SENTRY=^3.0 # All versions below only support PHP ^7.2 (Laravel requirement) - - LARAVEL=^6.0 TESTBENCH=4.7.* PHPUNIT=8.4.* SENTRY=^2.3 - - LARAVEL=^7.0 TESTBENCH=5.1.* PHPUNIT=8.4.* SENTRY=^2.3 + - LARAVEL=^6.0 TESTBENCH=4.7.* PHPUNIT=8.4.* SENTRY=^3.0 + - LARAVEL=^7.0 TESTBENCH=5.1.* PHPUNIT=8.4.* SENTRY=^3.0 # We add one more test using the next version of Laravel which only support PHP ^7.3 (Laravel requirement) - - LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^2.3 COMPOSER_STABILITY=dev + - LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^3.0 COMPOSER_STABILITY=dev matrix: fast_finish: true allow_failures: - php: 7.3 - env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^2.3 COMPOSER_STABILITY=dev + env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^3.0 COMPOSER_STABILITY=dev - php: 7.4 - env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^2.3 COMPOSER_STABILITY=dev + env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^3.0 COMPOSER_STABILITY=dev exclude: - php: 7.2 - env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^2.3 COMPOSER_STABILITY=dev + env: LARAVEL=8.x-dev@dev TESTBENCH=^6.0 PHPUNIT=8.4.* SENTRY=^3.0 COMPOSER_STABILITY=dev cache: directories: From 97e237d02de6d02b1f19396011d8fcfee4927e92 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 10:10:50 +0200 Subject: [PATCH 11/13] ref: Fix tests --- test/Sentry/IntegrationsOptionTest.php | 2 +- test/Sentry/ServiceProviderTest.php | 8 ++++---- test/Sentry/ServiceProviderWithCustomAliasTest.php | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/Sentry/IntegrationsOptionTest.php b/test/Sentry/IntegrationsOptionTest.php index 096e3d47..c8f0b4dc 100644 --- a/test/Sentry/IntegrationsOptionTest.php +++ b/test/Sentry/IntegrationsOptionTest.php @@ -100,7 +100,7 @@ public function testDisabledIntegrationsAreNotPresentWithCustomIntegrations() $integrations = $this->getHubFromContainer()->getClient()->getOptions()->getIntegrations(); $found = false; - + foreach ($integrations as $integration) { $this->ensureIsNotDisabledIntegration($integration); diff --git a/test/Sentry/ServiceProviderTest.php b/test/Sentry/ServiceProviderTest.php index f3bd6702..218d3121 100644 --- a/test/Sentry/ServiceProviderTest.php +++ b/test/Sentry/ServiceProviderTest.php @@ -51,10 +51,10 @@ public function testDsnWasSetFromConfig() /** @var \Sentry\Options $options */ $options = app('sentry')->getClient()->getOptions(); - $this->assertEquals('http://sentry.dev', $options->getDsn()); - $this->assertEquals(123, $options->getProjectId()); - $this->assertEquals('publickey', $options->getPublicKey()); - $this->assertEquals('secretkey', $options->getSecretKey()); + $this->assertEquals('http://sentry.dev', $options->getDsn()->getScheme() . '://' . $options->getDsn()->getHost()); + $this->assertEquals(123, $options->getDsn()->getProjectId()); + $this->assertEquals('publickey', $options->getDsn()->getPublicKey()); + $this->assertEquals('secretkey', $options->getDsn()->getSecretKey()); } /** diff --git a/test/Sentry/ServiceProviderWithCustomAliasTest.php b/test/Sentry/ServiceProviderWithCustomAliasTest.php index 8d0ee77b..2f44f3d7 100644 --- a/test/Sentry/ServiceProviderWithCustomAliasTest.php +++ b/test/Sentry/ServiceProviderWithCustomAliasTest.php @@ -51,10 +51,10 @@ public function testDsnWasSetFromConfig() /** @var \Sentry\Options $options */ $options = app('custom-sentry')->getClient()->getOptions(); - $this->assertEquals('http://sentry.dev', $options->getDsn()); - $this->assertEquals(123, $options->getProjectId()); - $this->assertEquals('publickey', $options->getPublicKey()); - $this->assertEquals('secretkey', $options->getSecretKey()); + $this->assertEquals('http://sentry.dev', $options->getDsn()->getScheme() . '://' . $options->getDsn()->getHost()); + $this->assertEquals(123, $options->getDsn()->getProjectId()); + $this->assertEquals('publickey', $options->getDsn()->getPublicKey()); + $this->assertEquals('secretkey', $options->getDsn()->getSecretKey()); } /** From 48575dd7162067d7f7900623973869c7adf61285 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 10:21:57 +0200 Subject: [PATCH 12/13] fix: tests --- test/Sentry/IntegrationsOptionTest.php | 37 +++----------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/test/Sentry/IntegrationsOptionTest.php b/test/Sentry/IntegrationsOptionTest.php index c8f0b4dc..6507a0da 100644 --- a/test/Sentry/IntegrationsOptionTest.php +++ b/test/Sentry/IntegrationsOptionTest.php @@ -97,39 +97,10 @@ public function testDisabledIntegrationsAreNotPresentWithCustomIntegrations() ], ]); - $integrations = $this->getHubFromContainer()->getClient()->getOptions()->getIntegrations(); - - $found = false; - - foreach ($integrations as $integration) { - $this->ensureIsNotDisabledIntegration($integration); - - if ($integration instanceof IntegrationsOptionTestIntegrationStub) { - $found = true; - } - } - - $this->assertTrue($found, 'No IntegrationsOptionTestIntegrationStub found in final integrations enabled'); - } - - /** - * Make sure the passed integration is not one of the disabled integrations. - * - * @param \Sentry\Integration\IntegrationInterface $integration - */ - private function ensureIsNotDisabledIntegration(IntegrationInterface $integration) - { - if ($integration instanceof ErrorListenerIntegration) { - $this->fail('Should not have ErrorListenerIntegration registered'); - } - - if ($integration instanceof ExceptionListenerIntegration) { - $this->fail('Should not have ExceptionListenerIntegration registered'); - } - - if ($integration instanceof FatalErrorListenerIntegration) { - $this->fail('Should not have FatalErrorListenerIntegration registered'); - } + $this->assertNotNull($this->getHubFromContainer()->getClient()->getIntegration(IntegrationsOptionTestIntegrationStub::class)); + $this->assertNull($this->getHubFromContainer()->getClient()->getIntegration(ErrorListenerIntegration::class)); + $this->assertNull($this->getHubFromContainer()->getClient()->getIntegration(ExceptionListenerIntegration::class)); + $this->assertNull($this->getHubFromContainer()->getClient()->getIntegration(FatalErrorListenerIntegration::class)); } } From c9684d8daa06f24ef77bd3b38b37f56c5db073e1 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 28 Sep 2020 10:29:13 +0200 Subject: [PATCH 13/13] ref: Master --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0a91daf1..c0d90988 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.x-dev", + "dev-master": "2.x-dev", "dev-0.x": "0.x-dev" }, "laravel": {