Skip to content
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
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);
}
}