Skip to content

Commit

Permalink
Merge pull request #2 from Skillshare/upgrade_sentry
Browse files Browse the repository at this point in the history
Upgrade sentry to latest ~2.1
  • Loading branch information
xiian authored Jul 9, 2020
2 parents ec63640 + 701b78c commit a739d8b
Show file tree
Hide file tree
Showing 7 changed files with 489 additions and 54 deletions.
32 changes: 19 additions & 13 deletions SentryComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
use CWebApplication;
use CWebUser;
use IApplicationComponent;
use Raven_Client;
use Raven_ErrorHandler;
use Sentry\ClientBuilder;
use Sentry\ClientInterface;
use Sentry\ErrorHandler;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Yii;

class SentryComponent extends CApplicationComponent
Expand Down Expand Up @@ -55,7 +58,7 @@ class SentryComponent extends CApplicationComponent
public $useRavenErrorHandler = false;

/**
* @var Raven_Client instance.
* @var ClientInterface instance.
*/
protected $raven;

Expand All @@ -75,7 +78,7 @@ public function init()

/**
* Get Raven_Client instance.
* @return Raven_Client
* @return ClientInterface
*/
public function getRaven()
{
Expand Down Expand Up @@ -136,11 +139,15 @@ public function registerRavenJs(array $options = null, array $plugins = null, ar
*/
protected function registerRaven()
{
$this->raven = new Raven_Client($this->dsn, $this->options);

if ($userContext = $this->getUserContext()) {
$this->raven->user_context($userContext);
}
$this->raven = ClientBuilder::create($this->options)->getClient();
SentrySdk::getCurrentHub()->bindClient($this->raven);

$userContext = $this->getUserContext();
SentrySdk::getCurrentHub()->configureScope(function (Scope $scope) use ($userContext) {
if ($userContext) {
$scope->setUser($userContext, true);
}
});
}

/**
Expand Down Expand Up @@ -195,10 +202,9 @@ protected function registerRavenErrorHandler()
{
$raven = $this->getRaven();
if ($raven) {
$handler = new Raven_ErrorHandler($raven);
$handler->registerExceptionHandler();
$handler->registerErrorHandler();
$handler->registerShutdownFunction();
ErrorHandler::registerOnceExceptionHandler();
ErrorHandler::registerOnceErrorHandler();
ErrorHandler::registerOnceFatalErrorHandler();

return true;
}
Expand Down
68 changes: 44 additions & 24 deletions SentryLogRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

use CLogger;
use CLogRoute;
use Raven_Client;
use Sentry\ClientInterface;
use Sentry\SentrySdk;
use Sentry\Severity;
use Sentry\State\Scope;
use Yii;

class SentryLogRoute extends CLogRoute
Expand All @@ -21,10 +24,31 @@ class SentryLogRoute extends CLogRoute

/**
* Raven_Client instance from SentryComponent->getRaven();
* @var Raven_Client
* @var ClientInterface
*/
protected $raven;

public static function getSeverityFromLogLevel(string $level): Severity
{
$severityLevels = [
CLogger::LEVEL_PROFILE => Severity::debug(),
CLogger::LEVEL_TRACE => Severity::debug(),
'debug' => Severity::debug(),
CLogger::LEVEL_INFO => Severity::info(),
CLogger::LEVEL_WARNING => Severity::warning(),
'warn' => Severity::warning(),
CLogger::LEVEL_ERROR => Severity::error(),
'fatal' => Severity::fatal(),
];

$level = strtolower($level);
if (array_key_exists($level, $severityLevels)) {
return $severityLevels[$level];
}

return Severity::error();
}

/**
* @inheritdoc
*/
Expand All @@ -38,27 +62,22 @@ protected function processLogs($logs)
return;
}

foreach ($logs as $log) {
list($message, $level, $category, $timestamp) = $log;

$title = preg_replace('#Stack trace:.+#s', '', $message); // remove stack trace from title
// ensure %'s in messages aren't interpreted as replacement
// characters by the vsprintf inside raven
$title = str_replace('%', '%%', $title);
$raven->captureMessage(
$title,
array(
'extra' => array(
'category' => $category,
),
),
array(
'level' => $level,
'timestamp' => $timestamp,
),
$this->getStackTrace($message)
);
}
SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($raven, $logs) {
foreach ($logs as $log) {
[$message, $level, $category, $timestamp] = $log;

$title = preg_replace('#Stack trace:.+#s', '', $message); // remove stack trace from title
// ensure %'s in messages aren't interpreted as replacement
// characters by the vsprintf inside raven
$title = str_replace('%', '%%', $title);

$scope->setExtras([
'category' => $category,
'timestamp' => $timestamp, // TODO: I dont know if this has en effect
]);
$raven->captureMessage($title, self::getSeverityFromLogLevel($level), $scope);
}
});
}

/**
Expand Down Expand Up @@ -92,7 +111,8 @@ protected function getStackTrace($log)

/**
* Return Raven_Client instance or false if error.
* @return Raven_Client|bool
*
* @return ClientInterface|bool
*/
protected function getRaven()
{
Expand Down
217 changes: 217 additions & 0 deletions Tests/Unit/SentryComponentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php

namespace Skillshare\YiiSentry\Test;

use CWebApplication;
use IApplicationComponent;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use Mockery\MockInterface;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use Sentry\Client;
use Sentry\ClientInterface;
use Sentry\Event;
use Sentry\SentrySdk;
use Sentry\Severity;
use Skillshare\YiiSentry\SentryComponent;
use Yii;

/**
* @coversDefaultClass \Skillshare\YiiSentry\SentryComponent
*/
class SentryComponentTest extends TestCase
{
use MockeryPHPUnitIntegration;

private CWebApplication $app;

protected function setUp(): void
{
// Re-init Sentry
SentrySdk::init();

// Resets the app entirely between runs
Yii::setApplication(null);
$config = [
'basePath' => dirname(__DIR__) . '/runtime',
'components' => [
'db' => [
'connectionString' => 'sqlite::memory:',
],
],
];
$this->app = Yii::createWebApplication($config);
}

/**
* @covers ::getRaven
* @uses \Skillshare\YiiSentry\SentryComponent::getComponent
* @uses \Skillshare\YiiSentry\SentryComponent::getUserContext
* @uses \Skillshare\YiiSentry\SentryComponent::registerRaven
*/
public function testGetRaven(): void
{
$sut = new SentryComponent();
$out = $sut->getRaven();
$this->assertInstanceOf(ClientInterface::class, $out);
}

/**
* @covers ::getRaven
* @uses \Skillshare\YiiSentry\SentryComponent::getComponent
* @uses \Skillshare\YiiSentry\SentryComponent::getUserContext
* @uses \Skillshare\YiiSentry\SentryComponent::registerRaven
*/
public function testGetRavenWithPreExisting(): void
{
$sut = new SentryComponent();
/** @var ClientInterface&MockInterface $raven */
$raven = Mockery::mock(ClientInterface::class);

$ref = new ReflectionProperty($sut, 'raven');
$ref->setAccessible(true);
$ref->setValue($sut, $raven);

$out = $sut->getRaven();
$this->assertInstanceOf(ClientInterface::class, $out);
$this->assertSame($raven, $out, 'Should return pre-established property');
}

/**
* @covers ::registerRaven
* @covers ::getUserContext
* @uses \Skillshare\YiiSentry\SentryComponent::getComponent
* @uses \Skillshare\YiiSentry\SentryComponent::getRaven
* @uses \Skillshare\YiiSentry\SentryComponent::getUserContext
*/
public function testRegisterRaven()
{
$dsn = 'http://tim:tam@bob.com/8675309';
$publicKey = 'tim';
$secretKey = 'tam';
$dsnHost = 'bob.com';
$projectId = 8675309;

$environment = 'dev';
$release = 'dev_release';
$callback = fn($i) => 1;

$sut = new SentryComponent();
$sut->options = [
'dsn' => $dsn,
'environment' => $environment,
'release' => $release,
'before_send' => $callback,
];

$out = $sut->getRaven();
$this->assertInstanceOf(ClientInterface::class, $out);
$options = $out->getOptions();
$this->assertSame($environment, $options->getEnvironment());
$this->assertSame($release, $options->getRelease());
$this->assertSame($callback, $options->getBeforeSendCallback());

$dsnObj = $options->getDsn(false);
$this->assertSame($dsnHost, $dsnObj->getHost());
$this->assertSame($publicKey, $dsnObj->getPublicKey());
$this->assertSame($secretKey, $dsnObj->getSecretKey());
$this->assertSame($projectId, $dsnObj->getProjectId());
}

/**
* @covers ::registerRaven
* @covers ::getUserContext
* @uses \Skillshare\YiiSentry\SentryComponent::getComponent
* @uses \Skillshare\YiiSentry\SentryComponent::getRaven
* @uses \Skillshare\YiiSentry\SentryComponent::getUserContext
*/
public function testRegisterRavenWithUserContext(): void
{
$userId = 37;
$userName = 'Dante';
$userInfo = ['id' => $userId, 'name' => strtoupper($userName)];

$sut = new SentryComponent();

/** @var IApplicationComponent&\Mockery\MockInterface $user */
$user = \Mockery::mock(IApplicationComponent::class);
$user->shouldReceive('getIsInitialized')->andReturnTrue();
$user->shouldReceive('getId')->andReturn($userId);
$user->shouldReceive('getName')->andReturn($userName);
$this->app->setComponent('user', $user);

$sut->getRaven()->captureMessage(
__METHOD__,
Severity::info(),
SentrySdk::getCurrentHub()->pushScope()
);
$scope = SentrySdk::getCurrentHub()->pushScope();
$eventUserContext = $scope->applyToEvent(new Event(), [])->getUserContext()->toArray();
$this->assertEquals($userInfo, $eventUserContext, 'Scope should have received the user information');
}

/**
* @covers ::registerRaven
* @covers ::getUserContext
* @uses \Skillshare\YiiSentry\SentryComponent::getComponent
* @uses \Skillshare\YiiSentry\SentryComponent::getRaven
* @uses \Skillshare\YiiSentry\SentryComponent::getUserContext
*/
public function testRegisterRavenWithGuestUserContext(): void
{
$userId = 37;
$userName = 'Dante';
$userInfo = [];

$sut = new SentryComponent();

/** @var IApplicationComponent&\Mockery\MockInterface $user */
$user = \Mockery::mock(IApplicationComponent::class);
$user->shouldReceive('getIsInitialized')->andReturnTrue();
$user->isGuest = true;
$user->shouldReceive('getId')->andReturn($userId)->never();
$user->shouldReceive('getName')->andReturn($userName)->never();
$this->app->setComponent('user', $user);

$sut->getRaven()->captureMessage(
__METHOD__,
Severity::info(),
SentrySdk::getCurrentHub()->pushScope()
);
$scope = SentrySdk::getCurrentHub()->pushScope();
$eventUserContext = $scope->applyToEvent(new Event(), [])->getUserContext()->toArray();
$this->assertEquals($userInfo, $eventUserContext, 'Scope should have received the user information');
}

/**
* @coversNothing
*/
public function testEverything()
{
$sut = new SentryComponent();
$sut->init();

$callback = fn() => 1;

$sut->options = [
'dsn' => 'http://tim:tam@bob.com/8675309',
'environment' => 'dev',
'release' => 'dev_release',
'before_send' => $callback,
];

$raven = $sut->getRaven();

$this->assertInstanceOf(Client::class, $raven);
$options = $raven->getOptions();
$this->assertEquals('dev', $options->getEnvironment());
$this->assertEquals('dev_release', $options->getRelease());
$this->assertSame($callback, $options->getBeforeSendCallback());
$dsn = $options->getDsn(false);
$this->assertEquals(8675309, $dsn->getProjectId());
$this->assertEquals('tim', $dsn->getPublicKey());
$this->assertEquals('tam', $dsn->getSecretKey());
$this->assertEquals('bob.com', $dsn->getHost());
}
}
Loading

0 comments on commit a739d8b

Please sign in to comment.