diff --git a/README.md b/README.md index 2bd7819..571a9d0 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,56 @@ Laravel's (`>= 5.0, < 5.1`) exception logger doesn't use event dispatcher (https php artisan vendor:publish --provider="Understand\UnderstandLaravel5\UnderstandLaravel5ServiceProvider" ``` +### Log Filter +To filter out specific log types a custom log filter can be provided. + +**Example filter class** +```php +// app/Logging/UnderstandLogFilter.php + \App\Logging\UnderstandLogFilter::class, +``` + +The `log_filter` config value must be a callable type: +- https://www.php.net/manual/en/function.is-callable.php + or a callable dependency from the service container: +- https://laravel.com/docs/9.x/container#the-make-method + +The suggested way would be to create an invokable class since it's hard to serialise anonymous functions (Laravel config cache): +- https://www.php.net/manual/en/language.oop5.magic.php#object.invoke + +The log filter interface must be as follows: `$callable($level, $message, $context)`. +The result of the filter must be a boolean value: +- `TRUE`, the log should be ignored and NOT delivered to Understand.io +- `FALSE`, the log should be delivered to Understand.io + +The `ignored_logs` config value has higher precedence than `log_filter`. + + ### Requirements ##### UTF-8 This package uses the json_encode function, which only supports UTF-8 data, and you should therefore ensure that all of your data is correctly encoded. In the event that your log data contains non UTF-8 strings, then the json_encode function will not be able to serialize the data. diff --git a/src/Understand/UnderstandLaravel5/UnderstandLaravel5ServiceProvider.php b/src/Understand/UnderstandLaravel5/UnderstandLaravel5ServiceProvider.php index 6b948f1..7b0fffe 100644 --- a/src/Understand/UnderstandLaravel5/UnderstandLaravel5ServiceProvider.php +++ b/src/Understand/UnderstandLaravel5/UnderstandLaravel5ServiceProvider.php @@ -434,22 +434,29 @@ protected function handleEvent($level, $message, $context) } } - /** - * @param $level - * @param $message - * @param $context - * @return bool - */ protected function shouldIgnoreEvent($level, $message, $context) { $ignoredEventTypes = (array)$this->app['config']->get('understand-laravel.ignored_logs'); + $logFilter = $this->app['config']->get('understand-laravel.log_filter'); + + // check if the log should be ignored by its level (info, warning, etc.) + if ($ignoredEventTypes) + { + return in_array($level, $ignoredEventTypes, true); + } - if ( ! $ignoredEventTypes) + // check if a custom filter is set and whether the log should be ignored + // true - the log should be ignored + // false - the log should be delivered to Understand + if ($logFilter) { - return false; + $factory = is_callable($logFilter) ? $logFilter : $this->app->make($logFilter); + + return (bool)$factory($level, $message, $context); } - return in_array($level, $ignoredEventTypes, true); + // by default logs are not ignored + return false; } /** diff --git a/src/config/understand-laravel.php b/src/config/understand-laravel.php index aa082d1..984e022 100644 --- a/src/config/understand-laravel.php +++ b/src/config/understand-laravel.php @@ -66,6 +66,27 @@ //'emergency', ], + /** + * Log filter. + * + * The configuration value (filter) must be a callable type: + * - https://www.php.net/manual/en/function.is-callable.php + * or a callable dependency from the service container: + * - https://laravel.com/docs/9.x/container#the-make-method + * + * The suggested way would be to create an invokable class since it's hard to serialise anonymous functions (Laravel config cache): + * - https://www.php.net/manual/en/language.oop5.magic.php#object.invoke + * + * The log (callable) filter interface is as follows: `$callable($level, $message, $context)`. + * + * The result of the filter must be a boolean value: + * - TRUE, the log should be ignored and NOT delivered to Understand.io + * - FALSE, the log should be delivered to Understand.io + * + * The `ignored_logs` config value has higher precedence than `log_filter`. + */ + 'log_filter' => null, + /** * Field names which values should not be sent to Understand.io * It applies to POST and GET request parameters diff --git a/tests/LogFilterTest.php b/tests/LogFilterTest.php new file mode 100644 index 0000000..abcb323 --- /dev/null +++ b/tests/LogFilterTest.php @@ -0,0 +1,144 @@ +app['config']->set('understand-laravel.log_filter', function() { + // FALSE, logs should not be filtered + return false; + }); + + $handler = new CallbackHandler($callback); + $this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler); + + // trigger error + $this->app['Psr\Log\LoggerInterface']->error('test'); + $this->app['Psr\Log\LoggerInterface']->warning('test2'); + + $this->assertEquals(2, $logsSent); + } + + /** + * @return void + */ + public function testServiceContainerDependency() + { + $logsSent = 0; + + $callback = function() use(&$logsSent) + { + $logsSent++; + }; + + $dependencyName = 'service-container-dependency'; + $dependencyCalled = false; + + $this->app->bind($dependencyName, function() use(&$dependencyCalled) { + return function() use(&$dependencyCalled) { + $dependencyCalled = true; + // FALSE, logs should not be filtered + return false; + }; + }); + + $this->app['config']->set('understand-laravel.log_filter', $dependencyName); + + $handler = new CallbackHandler($callback); + $this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler); + + // trigger error + $this->app['Psr\Log\LoggerInterface']->error('test'); + + $this->assertTrue($dependencyCalled); + $this->assertEquals(1, $logsSent); + } + + /** + * @return void + */ + public function testLogFilterFiltersOneLog() + { + $logsSent = 0; + + $callback = function() use(&$logsSent) + { + $logsSent++; + }; + + $this->app['config']->set('understand-laravel.log_filter', function($level, $message, $context) { + if ($message === 'test2') { + // TRUE, log should be filtered + return true; + } + + // FALSE, logs should not be filtered + return false; + }); + + $handler = new CallbackHandler($callback); + $this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler); + + // trigger error + $this->app['Psr\Log\LoggerInterface']->error('test'); + $this->app['Psr\Log\LoggerInterface']->warning('test2'); + + $this->assertEquals(1, $logsSent); + } + + /** + * @return void + */ + public function testLogFilterReceivesAllData() + { + $logsSent = 0; + + $callback = function() use(&$logsSent) + { + $logsSent++; + }; + + $this->app['config']->set('understand-laravel.log_filter', function($level, $message, $context) { + $this->assertEquals('error', $level); + $this->assertEquals('test', $message); + $this->assertEquals(['context' => 'value'], $context); + + // FALSE, logs should not be filtered + return false; + }); + + $handler = new CallbackHandler($callback); + $this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler); + + // trigger error + $this->app['Psr\Log\LoggerInterface']->error('test', ['context' => 'value']); + + $this->assertEquals(1, $logsSent); + } +} \ No newline at end of file