Skip to content

Commit

Permalink
Merge pull request #58 from aivis/master
Browse files Browse the repository at this point in the history
[FEATURE] Log filter
  • Loading branch information
mauricius authored Apr 12, 2022
2 parents 53ad362 + ce33d8f commit 473bd64
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 9 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php
declare(strict_types=1);
namespace App\Logging;
use Illuminate\Support\Str;
class UnderstandLogFilter
{
public function __invoke($level, $message, $context): bool
{
if ($level === 'warning' && Str::contains(strtolower($message), 'deprecated')) {
return true;
}
return false;
}
}
```
and then it can be configured in `understand-laravel.php`
```php
<?php
// ...
// config/understand-laravel.php
'log_filter' => \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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
21 changes: 21 additions & 0 deletions src/config/understand-laravel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
144 changes: 144 additions & 0 deletions tests/LogFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

use Understand\UnderstandLaravel5\Logger;
use Understand\UnderstandLaravel5\Handlers\CallbackHandler;
use Understand\UnderstandLaravel5\UnderstandLaravel5ServiceProvider;

class LogFilterTest extends Orchestra\Testbench\TestCase
{

/**
* Setup service provider
*
* @param object $app
* @return void
*/
protected function getPackageProviders($app)
{
return [UnderstandLaravel5ServiceProvider::class];
}

/**
* @return void
*/
public function testLogFilterAllowsDelivery()
{
$logsSent = 0;

$callback = function() use(&$logsSent)
{
$logsSent++;
};

$this->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);
}
}

0 comments on commit 473bd64

Please sign in to comment.