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

Improve tracing #387

Merged
merged 9 commits into from Sep 29, 2020
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Improve performance tracing by nesting `view.render` spans and adding a `app.handle` span showing how long the actual application code runs after Laravel bootstrapping (#387)

## 2.0.0

**Breaking Change**: This version uses the [envelope endpoint](https://develop.sentry.dev/sdk/envelopes/). If you are
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -29,7 +29,8 @@
"phpunit/phpunit": "^8.0",
"laravel/framework": "^7.0",
"orchestra/testbench": "^5.0",
"friendsofphp/php-cs-fixer": "2.16.*"
"friendsofphp/php-cs-fixer": "2.16.*",
"mockery/mockery": "1.3.*"
},
"autoload": {
"psr-0": {
Expand Down
39 changes: 39 additions & 0 deletions src/Sentry/Laravel/BaseServiceProvider.php
@@ -0,0 +1,39 @@
<?php

namespace Sentry\Laravel;

use Illuminate\Support\ServiceProvider;

abstract class BaseServiceProvider extends ServiceProvider
{
/**
* Abstract type to bind Sentry as in the Service Container.
*
* @var string
*/
public static $abstract = 'sentry';

/**
* Check if a DSN was set in the config.
*
* @return bool
*/
protected function hasDsnSet(): bool
{
$config = $this->getUserConfig();

return !empty($config['dsn']);
}

/**
* Retrieve the user configuration.
*
* @return array
*/
protected function getUserConfig(): array
{
$config = $this->app['config'][static::$abstract];

return empty($config) ? [] : $config;
}
}
22 changes: 0 additions & 22 deletions src/Sentry/Laravel/EventHandler.php
Expand Up @@ -20,8 +20,6 @@
use Sentry\Breadcrumb;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\Transaction;

class EventHandler
{
Expand Down Expand Up @@ -197,16 +195,6 @@ protected function routerMatchedHandler(Route $route)
{
$routeName = Integration::extractNameForRoute($route) ?? '<unlabeled transaction>';

$transaction = SentrySdk::getCurrentHub()->getTransaction();

if ($transaction instanceof Transaction) {
$transaction->setName($routeName);
$transaction->setData([
'action' => $route->getActionName(),
'name' => $route->getName()
]);
}

Integration::addBreadcrumb(new Breadcrumb(
Breadcrumb::LEVEL_INFO,
Breadcrumb::TYPE_NAVIGATION,
Expand Down Expand Up @@ -278,16 +266,6 @@ private function addQueryBreadcrumb($query, $bindings, $time, $connectionName)
$data['bindings'] = $bindings;
}

$transaction = SentrySdk::getCurrentHub()->getTransaction();
if (null !== $transaction) {
$context = new SpanContext();
$context->setOp('sql.query');
$context->setDescription($query);
$context->setStartTimestamp(microtime(true) - $time / 1000);
$context->setEndTimestamp($context->getStartTimestamp() + $time / 1000);
$transaction->startChild($context);
}

Integration::addBreadcrumb(new Breadcrumb(
Breadcrumb::LEVEL_INFO,
Breadcrumb::TYPE_DEFAULT,
Expand Down
6 changes: 3 additions & 3 deletions src/Sentry/Laravel/PublishConfigCommand.php
Expand Up @@ -63,7 +63,6 @@ public function setEnvironmentValue(array $values)

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);
Expand All @@ -75,12 +74,13 @@ public function setEnvironmentValue(array $values)
} else {
$str = str_replace($oldLine, "{$envKey}={$envValue}", $str);
}

}
}

$str = substr($str, 0, -1);
if (!file_put_contents($envFile, $str)) return false;
if (!file_put_contents($envFile, $str)) {
return false;
}
return true;
}
}
36 changes: 1 addition & 35 deletions src/Sentry/Laravel/ServiceProvider.php
Expand Up @@ -2,7 +2,6 @@

namespace Sentry\Laravel;

use Illuminate\Contracts\Http\Kernel as HttpKernelInterface;
use Sentry\SentrySdk;
use Sentry\State\Hub;
use Sentry\ClientBuilder;
Expand All @@ -12,18 +11,9 @@
use Laravel\Lumen\Application as Lumen;
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
class ServiceProvider extends BaseServiceProvider
{
/**
* Abstract type to bind Sentry as in the Service Container.
*
* @var string
*/
public static $abstract = 'sentry';

/**
* Boot the service provider.
*/
Expand Down Expand Up @@ -182,18 +172,6 @@ protected function configureAndRegisterClient(): void
$this->app->alias(static::$abstract, HubInterface::class);
}

/**
* Check if a DSN was set in the config.
*
* @return bool
*/
protected function hasDsnSet(): bool
{
$config = $this->getUserConfig();

return !empty($config['dsn']);
}

/**
* Resolve the integrations from the user configuration with the container.
*
Expand Down Expand Up @@ -224,18 +202,6 @@ private function resolveIntegrationsFromUserConfig(): array
return $integrations;
}

/**
* Retrieve the user configuration.
*
* @return array
*/
private function getUserConfig(): array
{
$config = $this->app['config'][static::$abstract];

return empty($config) ? [] : $config;
}

/**
* Get the services provided by the provider.
*
Expand Down
157 changes: 157 additions & 0 deletions src/Sentry/Laravel/Tracing/EventHandler.php
@@ -0,0 +1,157 @@
<?php

namespace Sentry\Laravel\Tracing;

use Exception;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Routing\Route;
use RuntimeException;
use Sentry\Laravel\Integration;
use Sentry\SentrySdk;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\Transaction;

class EventHandler
{
/**
* Map event handlers to events.
*
* @var array
*/
protected static $eventHandlerMap = [
'router.matched' => 'routerMatched', // Until Laravel 5.1
RouteMatched::class => 'routeMatched', // Since Laravel 5.2

'illuminate.query' => 'query', // Until Laravel 5.1
QueryExecuted::class => 'queryExecuted', // Since Laravel 5.2
];

/**
* The Laravel event dispatcher.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
private $events;

/**
* EventHandler constructor.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
*/
public function __construct(Dispatcher $events)
{
$this->events = $events;
}

/**
* Attach all event handlers.
*/
public function subscribe(): void
{
foreach (static::$eventHandlerMap as $eventName => $handler) {
$this->events->listen($eventName, [$this, $handler]);
}
}

/**
* Pass through the event and capture any errors.
*
* @param string $method
* @param array $arguments
*/
public function __call($method, $arguments)
{
$handlerMethod = $handlerMethod = "{$method}Handler";

if (!method_exists($this, $handlerMethod)) {
throw new RuntimeException("Missing tracing event handler: {$handlerMethod}");
}

$parentSpan = Integration::currentTracingSpan();

// If there is no tracing span active there is no need to handle the event
if ($parentSpan === null) {
return;
}

try {
call_user_func_array([$this, $handlerMethod], $arguments);
} catch (Exception $exception) {
// Ignore
}
}

/**
* Until Laravel 5.1
*
* @param \Illuminate\Routing\Route $route
*/
protected function routerMatchedHandler(Route $route): void
{
$transaction = SentrySdk::getCurrentHub()->getTransaction();

if ($transaction instanceof Transaction) {
$routeName = Integration::extractNameForRoute($route) ?? '<unlabeled transaction>';

$transaction->setName($routeName);
$transaction->setData([
'action' => $route->getActionName(),
'name' => $route->getName(),
]);
}
}

/**
* Since Laravel 5.2
*
* @param \Illuminate\Routing\Events\RouteMatched $match
*/
protected function routeMatchedHandler(RouteMatched $match): void
{
$this->routerMatchedHandler($match->route);
}

/**
* Until Laravel 5.1
*
* @param string $query
* @param array $bindings
* @param int $time
* @param string $connectionName
*/
protected function queryHandler($query, $bindings, $time, $connectionName): void
{
$this->recordQuerySpan($query, $time);
}

/**
* Since Laravel 5.2
*
* @param \Illuminate\Database\Events\QueryExecuted $query
*/
protected function queryExecutedHandler(QueryExecuted $query): void
{
$this->recordQuerySpan($query->sql, $query->time);
}

/**
* Helper to add an query breadcrumb.
*
* @param string $query
* @param float|null $time
*/
private function recordQuerySpan($query, $time): void
{
$parentSpan = Integration::currentTracingSpan();

$context = new SpanContext();
$context->setOp('sql.query');
$context->setDescription($query);
$context->setStartTimestamp(microtime(true) - $time / 1000);
$context->setEndTimestamp($context->getStartTimestamp() + $time / 1000);

$parentSpan->startChild($context);
}
}