From a3e0988592d1f09eac9bbe6f4797c5cd9a75e948 Mon Sep 17 00:00:00 2001 From: Artur Moczulski Date: Wed, 28 Jun 2017 13:01:01 +0000 Subject: [PATCH] github-171: exception sampling and tests; moved error sampling to a separate function as well --- README.md | 20 ++++++ src/Config.php | 117 ++++++++++++++++++++++++++++++------ tests/ConfigTest.php | 60 ++++++++++++++++++ tests/RollbarLoggerTest.php | 20 ++++++ 4 files changed, 200 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a78fe130..846e577a 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,13 @@ Default: `'production'` Default: empty array, meaning all errors are reported. +
exception_sample_rates +
+
Associative array mapping exception classes to sample rates. Sample rates are ratio out of 1, e.g. 0 is "never report", 1 is "always report", and 0.1 is "report 10% of the time". Sampling is done on a per-exception basis. It also respects class inheritance meaning if Exception is at 1.0 then ExceptionSublcass is also at 1.0, unless explicitly configured otherwise. If ExceptionSubclass is set to 0.5, but Exception is at 1.0 then Exception and all its' subclasses run at 1.0, except for ExceptionSubclass and its' subclasses which run at 0.5. Names of exception classes should NOT be followed with additional `\` for global namespace, i.e. Rollbar\SampleException and NOT \Rollbar\SampleException. + +Default: empty array, meaning all exceptions are reported. +
+
fluent_host
Either an `IPv4`, `IPv6`, or a `unix socket`. @@ -579,6 +586,19 @@ $config['error_sample_rates'] = array( ?> ``` +Example use of exception_sample_rates: + +```php + 0.1, +); +?> +``` + Example use of person_fn: ```php diff --git a/src/Config.php b/src/Config.php index 1bab2c8e..a85f0c39 100644 --- a/src/Config.php +++ b/src/Config.php @@ -56,6 +56,7 @@ class Config */ private $checkIgnore; private $error_sample_rates = array(); + private $exception_sample_rates = array(); private $mt_randmax; private $included_errno = ROLLBAR_INCLUDED_ERRNO_BITMASK; @@ -77,6 +78,10 @@ public function __construct(array $configArray) if (isset($configArray['error_sample_rates'])) { $this->error_sample_rates = $configArray['error_sample_rates']; } + + if (isset($configArray['exception_sample_rates'])) { + $this->exception_sample_rates = $configArray['exception_sample_rates']; + } $levels = array(E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR); @@ -407,6 +412,7 @@ public function checkIgnored($payload, $accessToken, $toLog, $isUncaught) if ($this->shouldSuppress()) { return true; } + if (isset($this->checkIgnore)) { try { if (call_user_func($this->checkIgnore, $isUncaught, $toLog, $payload)) { @@ -417,38 +423,115 @@ public function checkIgnored($payload, $accessToken, $toLog, $isUncaught) $this->checkIgnore = null; } } + if ($this->levelTooLow($payload)) { return true; } + if (!is_null($this->filter)) { return $this->filter->shouldSend($payload, $accessToken); } if ($toLog instanceof ErrorWrapper) { - $errno = $toLog->errorLevel; + + return $this->shouldIgnoreError($toLog); + + } + + if ($toLog instanceof \Exception) { + + return $this->shouldIgnoreException($toLog); + + } + + return false; + } + + /** + * Check if the error should be ignored due to `included_errno` config, + * `use_error_reporting` config or `error_sample_rates` config. + * + * @param \Rollbar\ErrorWrapper $toLog + * + * @return bool + */ + protected function shouldIgnoreError(ErrorWrapper $toLog) + { + $errno = $toLog->errorLevel; - if ($this->included_errno != -1 && ($errno & $this->included_errno) != $errno) { - // ignore - return true; - } + if ($this->included_errno != -1 && ($errno & $this->included_errno) != $errno) { + // ignore + return true; + } - if ($this->use_error_reporting && ($errno & error_reporting()) != $errno) { - // ignore due to error_reporting level - return true; - } + if ($this->use_error_reporting && ($errno & error_reporting()) != $errno) { + // ignore due to error_reporting level + return true; + } - if (isset($this->error_sample_rates[$errno])) { - // get a float in the range [0, 1) - // mt_rand() is inclusive, so add 1 to mt_randmax - $float_rand = mt_rand() / ($this->mt_randmax + 1); - if ($float_rand > $this->error_sample_rates[$errno]) { - // skip - return true; - } + if (isset($this->error_sample_rates[$errno])) { + // get a float in the range [0, 1) + // mt_rand() is inclusive, so add 1 to mt_randmax + $float_rand = mt_rand() / ($this->mt_randmax + 1); + if ($float_rand > $this->error_sample_rates[$errno]) { + // skip + return true; } } + + return false; + } + + /** + * Check if the exception should be ignored due to configured exception + * sample rates. + * + * @param \Exception $toLog + * + * @return bool + */ + protected function shouldIgnoreException(\Exception $toLog) + { + // get a float in the range [0, 1) + // mt_rand() is inclusive, so add 1 to mt_randmax + $floatRand = mt_rand() / ($this->mt_randmax + 1); + if ($floatRand > $this->exceptionSampleRate($toLog)) { + // skip + return true; + } + return false; } + + /** + * Calculate what's the chance of logging this exception according to + * exception sampling. + * + * @param \Exception $toLog + * + * @return float + */ + public function exceptionSampleRate(\Exception $toLog) + { + $sampleRate = 1.0; + + $exceptionClasses = array(); + + $class = get_class($toLog); + while ($class) { + $exceptionClasses []= $class; + $class = get_parent_class($class); + } + $exceptionClasses = array_reverse($exceptionClasses); + + foreach ($exceptionClasses as $exceptionClass) { + if (isset($this->exception_sample_rates["$exceptionClass"])) { + $sampleRate = $this->exception_sample_rates["$exceptionClass"]; + } + } + + return $sampleRate; + } /** * @param Payload $payload diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index ea470cd3..a61cb79f 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -9,6 +9,18 @@ use Rollbar\Payload\Payload; use Rollbar\RollbarLogger; +class MidExceptionSampleRate extends \Exception {} + +class SilentExceptionSampleRate extends MidExceptionSampleRate {} + +class FiftyFityExceptionSampleRate extends MidExceptionSampleRate {} + +class FiftyFityChildExceptionSampleRate extends FiftyFityExceptionSampleRate {} + +class QuarterExceptionSampleRate extends FiftyFityExceptionSampleRate {} + +class VerboseExceptionSampleRate extends MidExceptionSampleRate {} + class ConfigTest extends \PHPUnit_Framework_TestCase { private $error; @@ -438,4 +450,52 @@ public function useErrorReportingProvider() ) ); } + + /** + * @dataProvider providerExceptionSampleRate + */ + public function testExceptionSampleRate($exception, $expected) + { + $config = new Config(array( + "access_token" => "ad865e76e7fb496fab096ac07b1dbabb", + "environment" => "testing-php", + "exception_sample_rates" => array( + get_class($exception) => $expected + ) + )); + + $sampleRate = $config->exceptionSampleRate($exception); + + $this->assertEquals($expected, $sampleRate); + } + + public function providerExceptionSampleRate() + { + return array( + array( + new \Exception, + 1.0 + ), + array( + new SilentExceptionSampleRate, + 0.0 + ), + array( + new FiftyFityExceptionSampleRate, + 0.5 + ), + array( + new FiftyFityChildExceptionSampleRate, + 0.5 + ), + array( + new QuarterExceptionSampleRate, + 0.25 + ), + array( + new VerboseExceptionSampleRate, + 1.0 + ), + ); + } } diff --git a/tests/RollbarLoggerTest.php b/tests/RollbarLoggerTest.php index 2df50f28..8be1c617 100644 --- a/tests/RollbarLoggerTest.php +++ b/tests/RollbarLoggerTest.php @@ -3,6 +3,8 @@ use Rollbar\Payload\Level; use Rollbar\Payload\Payload; +class SilentExceptionSampleRate extends \Exception {} + class RollbarLoggerTest extends \PHPUnit_Framework_TestCase { @@ -66,6 +68,24 @@ public function testErrorSampleRates() $this->assertEquals(0, $response->getStatus()); } + public function testExceptionSampleRates() + { + $l = new RollbarLogger(array( + "access_token" => "ad865e76e7fb496fab096ac07b1dbabb", + "environment" => "testing-php", + "exception_sample_rates" => array( + 'Rollbar\SilentExceptionSampleRate' => 0.0 + ) + )); + $response = $l->log(Level::ERROR, new SilentExceptionSampleRate); + + $this->assertEquals(0, $response->getStatus()); + + $response = $l->log(Level::ERROR, new \Exception); + + $this->assertEquals(200, $response->getStatus()); + } + public function testIncludedErrNo() { $l = new RollbarLogger(array(