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

FEATURE: Add advanced logging rules #23

Merged
merged 4 commits into from
Feb 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions Classes/Integration/NetlogixIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
namespace Netlogix\Sentry\Integration;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\ObjectManagement\CompileTimeObjectManager;
use Neos\Utility\Files;
use Netlogix\Sentry\ExceptionHandler\ExceptionRenderingOptionsResolver;
use Neos\Utility\PositionalArraySorter;
use Netlogix\Sentry\Scope\ScopeProvider;
use Sentry\Event;
use Sentry\EventHint;
Expand Down Expand Up @@ -54,10 +55,27 @@ public static function handleEvent(Event $event, EventHint $hint): ?Event
return $event;
}

if ($hint->exception instanceof Throwable
&& ($optionsResolver = Bootstrap::$staticObjectManager->get(ExceptionRenderingOptionsResolver::class)) !== null) {
$options = $optionsResolver->resolveRenderingOptionsForThrowable($hint->exception);
if (!($options['logException'] ?? true)) {
if (
$hint->exception instanceof Throwable &&
($configurationManager = Bootstrap::$staticObjectManager->get(ConfigurationManager::class)) !== null
) {
$rules = $configurationManager->getConfiguration(
ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules'
) ?? [];

$decision = true;

$positionalArraySorter = new PositionalArraySorter($rules);
$sortedRules = $positionalArraySorter->toArray();

foreach (array_keys($sortedRules) as $rule) {
$decision = Bootstrap::$staticObjectManager
->get($rule)
->decide($hint->exception, $decision);
}

if (!$decision) {
return null;
}
}
Expand Down
27 changes: 27 additions & 0 deletions Classes/LoggingRule/AllowListRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace Netlogix\Sentry\LoggingRule;

use Neos\Flow\Annotations as Flow;
use Throwable;

class AllowListRule implements LoggingRule
{
/**
* @Flow\InjectConfiguration(path="loggingRules.allowList")
* @var array
*/
protected $allowList = [];

public function decide(Throwable $throwable, bool $previousDecision): bool
{
foreach ($this->allowList as $allowedThrowableClassName) {
if ($throwable instanceof $allowedThrowableClassName) {
return true;
}
}

return $previousDecision;
}
}
27 changes: 27 additions & 0 deletions Classes/LoggingRule/DenyListRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace Netlogix\Sentry\LoggingRule;

use Neos\Flow\Annotations as Flow;
use Throwable;

class DenyListRule implements LoggingRule
{
/**
* @Flow\InjectConfiguration(path="loggingRules.denyList")
* @var array
*/
protected $denyList = [];

public function decide(Throwable $throwable, bool $previousDecision): bool
{
foreach ($this->denyList as $deniedThrowableClassName) {
if ($throwable instanceof $deniedThrowableClassName) {
return false;
}
}

return $previousDecision;
}
}
24 changes: 24 additions & 0 deletions Classes/LoggingRule/ExceptionHandlerRenderingGroupsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\LoggingRule;

use Neos\Flow\Annotations as Flow;
use Netlogix\Sentry\ExceptionHandler\ExceptionRenderingOptionsResolver;
use Throwable;

class ExceptionHandlerRenderingGroupsRule implements LoggingRule
{
/**
* @Flow\Inject
* @var ExceptionRenderingOptionsResolver
*/
protected $optionsResolver;

public function decide(Throwable $throwable, bool $previousDecision): bool
{
$options = $this->optionsResolver->resolveRenderingOptionsForThrowable($throwable);
return $options['logException'] ?? $previousDecision;
}
}
12 changes: 12 additions & 0 deletions Classes/LoggingRule/LoggingRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\LoggingRule;

use Throwable;

interface LoggingRule
{
public function decide(Throwable $throwable, bool $previousDecision): bool;
}
13 changes: 13 additions & 0 deletions Configuration/Settings.LoggingRules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Netlogix:
Sentry:
loggingRules:
rules:
'Netlogix\Sentry\LoggingRule\ExceptionHandlerRenderingGroupsRule': '10'
'Netlogix\Sentry\LoggingRule\AllowListRule': '20'
'Netlogix\Sentry\LoggingRule\DenyListRule': '30'

allowList: []

denyList:
- 'Neos\Flow\Security\Exception\InvalidHashException'
- 'Neos\Flow\Security\Exception\InvalidArgumentForHashGenerationException'
142 changes: 109 additions & 33 deletions Tests/Unit/Integration/NetlogixIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

namespace Netlogix\Sentry\Tests\Unit\Integration;

use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
use Neos\Flow\Tests\UnitTestCase;
use Netlogix\Sentry\Exception\Test;
use Netlogix\Sentry\ExceptionHandler\ExceptionRenderingOptionsResolver;
use Netlogix\Sentry\Integration\NetlogixIntegration;
use Netlogix\Sentry\LoggingRule\ExceptionHandlerRenderingGroupsRule;
use Netlogix\Sentry\Scope\ScopeProvider;
use Sentry\Event;
use Sentry\EventHint;
Expand Down Expand Up @@ -52,27 +54,39 @@ public function If_event_hint_does_not_contain_an_exception_it_is_not_filtered()

/**
* @test
* @dataProvider provideLogExceptionExpectations
* @dataProvider provideExceptionLoggingExpectations
*/
public function Event_is_logged_depending_on_logException(array $options, bool $isLogged): void
public function Event_is_logged_depending_on_loggingRules(bool $ruleResult, bool $isLogged): void
{
$objectManager = $this->getMockBuilder(ObjectManagerInterface::class)
->getMock();

$optionsResolver = new ExceptionRenderingOptionsResolver();
$optionsResolver->setOptions([
'renderingGroups' => [
'netlogixSentryTest' => [
'matchingExceptionClassNames' => [Test::class],
'options' => $options
]
]
]);
$configurationManager = $this->getMockBuilder(ConfigurationManager::class)
->disableOriginalConstructor()
->getMock();

$configurationManager
->method('getConfiguration')
->with( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules')
->willReturn([
'Netlogix\Sentry\LoggingRule\ExceptionHandlerRenderingGroupsRule' => '10'
]);

$exceptionHandlerRenderingGroupsRule = $this->getMockBuilder(ExceptionHandlerRenderingGroupsRule::class)
->disableOriginalConstructor()
->getMock();

$exceptionHandlerRenderingGroupsRule
->method('decide')
->willReturn($ruleResult);

$objectManager
->method('get')
->with(ExceptionRenderingOptionsResolver::class)
->willReturn($optionsResolver);
->will($this->returnValueMap([
[ConfigurationManager::class, $configurationManager],
[ExceptionHandlerRenderingGroupsRule::class, $exceptionHandlerRenderingGroupsRule]
]));

Bootstrap::$staticObjectManager = $objectManager;

Expand All @@ -88,24 +102,48 @@ public function Event_is_logged_depending_on_logException(array $options, bool $
}
}

public function provideLogExceptionExpectations(): iterable
/**
* @test
*/
public function Event_is_logged_when_no_rules_are_defined(): void
{
yield 'If logException is false, null is returned' => [
'options' => [
'logException' => false,
],
'isLogged' => false,
];
$objectManager = $this->getMockBuilder(ObjectManagerInterface::class)
->getMock();

yield 'If logException is true, the exception is logged' => [
'options' => [
'logException' => true,
],
'isLogged' => true,
$configurationManager = $this->getMockBuilder(ConfigurationManager::class)
->disableOriginalConstructor()
->getMock();

$configurationManager
->method('getConfiguration')
->with( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules')
->willReturn([]);

$objectManager
->method('get')
->with(ConfigurationManager::class)
->willReturn($configurationManager);

Bootstrap::$staticObjectManager = $objectManager;

$throwable = new Test('foo', 1612089648);

$event = Event::createEvent();
$hint = EventHint::fromArray(['exception' => $throwable]);

self::assertSame($event, NetlogixIntegration::handleEvent($event, $hint));
}

public function provideExceptionLoggingExpectations(): iterable
{
yield 'If ruleResult is false, null is returned' => [
'ruleResult' => false,
'isLogged' => false,
];

yield 'If logException is unset, the exception is logged' => [
'options' => [],
yield 'If ruleResult is true, the exception is logged' => [
'ruleResult' => true,
'isLogged' => true,
];
}
Expand Down Expand Up @@ -156,10 +194,23 @@ public function Event_is_enriched_with_ScopeProvider_data(): void
->method('collectUser')
->willReturn(['username' => 'lars']);

$configurationManager = $this->getMockBuilder(ConfigurationManager::class)
->disableOriginalConstructor()
->getMock();

$configurationManager
->method('getConfiguration')
->with( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules')
->willReturn([]);

$objectManager
->method('get')
->withConsecutive([ExceptionRenderingOptionsResolver::class], [ScopeProvider::class])
->willReturnOnConsecutiveCalls(new ExceptionRenderingOptionsResolver(), $scopeProvider);
->will($this->returnValueMap([
[ConfigurationManager::class, $configurationManager],
[ExceptionHandlerRenderingGroupsRule::class, new ExceptionRenderingOptionsResolver()],
[ScopeProvider::class, $scopeProvider],
]));

Bootstrap::$staticObjectManager = $objectManager;

Expand Down Expand Up @@ -216,10 +267,22 @@ public function Events_are_also_enriched_if_hint_does_not_contain_a_throwable():
->method('collectUser')
->willReturn([]);

$configurationManager = $this->getMockBuilder(ConfigurationManager::class)
->disableOriginalConstructor()
->getMock();

$configurationManager
->method('getConfiguration')
->with( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules')
->willReturn([]);

$objectManager
->method('get')
->with(ScopeProvider::class)
->willReturn($scopeProvider);
->will($this->returnValueMap([
[ConfigurationManager::class, $configurationManager],
[ScopeProvider::class, $scopeProvider],
]));

Bootstrap::$staticObjectManager = $objectManager;

Expand Down Expand Up @@ -251,10 +314,23 @@ public function ScopeProvider_receives_the_current_Exception(): void
->method('withThrowable')
->with($throwable);

$configurationManager = $this->getMockBuilder(ConfigurationManager::class)
->disableOriginalConstructor()
->getMock();

$configurationManager
->method('getConfiguration')
->with( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'Netlogix.Sentry.loggingRules.rules')
->willReturn([]);

$objectManager
->method('get')
->withConsecutive([ExceptionRenderingOptionsResolver::class], [ScopeProvider::class])
->willReturnOnConsecutiveCalls(new ExceptionRenderingOptionsResolver(), $scopeProvider);
->will($this->returnValueMap([
[ConfigurationManager::class, $configurationManager],
[ExceptionRenderingOptionsResolver::class, new ExceptionRenderingOptionsResolver()],
[ScopeProvider::class, $scopeProvider],
]));

Bootstrap::$staticObjectManager = $objectManager;

Expand Down
38 changes: 38 additions & 0 deletions Tests/Unit/LoggingRule/AllowListRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Netlogix\Sentry\Tests\Unit\LoggingRule;

use Neos\Flow\Tests\UnitTestCase;
use Netlogix\Sentry\Exception\Test;
use Netlogix\Sentry\LoggingRule\AllowListRule;

class AllowListRuleTest extends UnitTestCase
{
/**
* @test
*/
public function if_allow_list_contains_class_decision_should_be_true(): void
{
$allowListRule = $this->getAccessibleMock(AllowListRule::class, ['dummy']);
$allowListRule->_set('allowList', [
Test::class
]);

self::assertEquals(true, $allowListRule->decide(new Test(), false));
}

/**
* @test
*/
public function if_allow_list_not_contains_class_decision_should_be_equal_to_previous_decision(): void
{
$allowListRule = $this->getAccessibleMock(AllowListRule::class, ['dummy']);
$allowListRule->_set('allowList', []);

$previousDecision = false;

self::assertEquals($previousDecision, $allowListRule->decide(new Test(), $previousDecision));
}
}
Loading