Skip to content

Commit 473bd64

Browse files
authored
Merge pull request #58 from aivis/master
[FEATURE] Log filter
2 parents 53ad362 + ce33d8f commit 473bd64

File tree

4 files changed

+231
-9
lines changed

4 files changed

+231
-9
lines changed

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,56 @@ Laravel's (`>= 5.0, < 5.1`) exception logger doesn't use event dispatcher (https
117117
php artisan vendor:publish --provider="Understand\UnderstandLaravel5\UnderstandLaravel5ServiceProvider"
118118
```
119119

120+
### Log Filter
121+
To filter out specific log types a custom log filter can be provided.
122+
123+
**Example filter class**
124+
```php
125+
// app/Logging/UnderstandLogFilter.php
126+
<?php
127+
128+
declare(strict_types=1);
129+
130+
namespace App\Logging;
131+
132+
use Illuminate\Support\Str;
133+
134+
class UnderstandLogFilter
135+
{
136+
public function __invoke($level, $message, $context): bool
137+
{
138+
if ($level === 'warning' && Str::contains(strtolower($message), 'deprecated')) {
139+
return true;
140+
}
141+
142+
return false;
143+
}
144+
}
145+
```
146+
and then it can be configured in `understand-laravel.php`
147+
```php
148+
<?php
149+
// ...
150+
// config/understand-laravel.php
151+
'log_filter' => \App\Logging\UnderstandLogFilter::class,
152+
```
153+
154+
The `log_filter` config value must be a callable type:
155+
- https://www.php.net/manual/en/function.is-callable.php
156+
or a callable dependency from the service container:
157+
- https://laravel.com/docs/9.x/container#the-make-method
158+
159+
The suggested way would be to create an invokable class since it's hard to serialise anonymous functions (Laravel config cache):
160+
- https://www.php.net/manual/en/language.oop5.magic.php#object.invoke
161+
162+
The log filter interface must be as follows: `$callable($level, $message, $context)`.
163+
The result of the filter must be a boolean value:
164+
- `TRUE`, the log should be ignored and NOT delivered to Understand.io
165+
- `FALSE`, the log should be delivered to Understand.io
166+
167+
The `ignored_logs` config value has higher precedence than `log_filter`.
168+
169+
120170
### Requirements
121171
##### UTF-8
122172
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.

src/Understand/UnderstandLaravel5/UnderstandLaravel5ServiceProvider.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -434,22 +434,29 @@ protected function handleEvent($level, $message, $context)
434434
}
435435
}
436436

437-
/**
438-
* @param $level
439-
* @param $message
440-
* @param $context
441-
* @return bool
442-
*/
443437
protected function shouldIgnoreEvent($level, $message, $context)
444438
{
445439
$ignoredEventTypes = (array)$this->app['config']->get('understand-laravel.ignored_logs');
440+
$logFilter = $this->app['config']->get('understand-laravel.log_filter');
441+
442+
// check if the log should be ignored by its level (info, warning, etc.)
443+
if ($ignoredEventTypes)
444+
{
445+
return in_array($level, $ignoredEventTypes, true);
446+
}
446447

447-
if ( ! $ignoredEventTypes)
448+
// check if a custom filter is set and whether the log should be ignored
449+
// true - the log should be ignored
450+
// false - the log should be delivered to Understand
451+
if ($logFilter)
448452
{
449-
return false;
453+
$factory = is_callable($logFilter) ? $logFilter : $this->app->make($logFilter);
454+
455+
return (bool)$factory($level, $message, $context);
450456
}
451457

452-
return in_array($level, $ignoredEventTypes, true);
458+
// by default logs are not ignored
459+
return false;
453460
}
454461

455462
/**

src/config/understand-laravel.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,27 @@
6666
//'emergency',
6767
],
6868

69+
/**
70+
* Log filter.
71+
*
72+
* The configuration value (filter) must be a callable type:
73+
* - https://www.php.net/manual/en/function.is-callable.php
74+
* or a callable dependency from the service container:
75+
* - https://laravel.com/docs/9.x/container#the-make-method
76+
*
77+
* The suggested way would be to create an invokable class since it's hard to serialise anonymous functions (Laravel config cache):
78+
* - https://www.php.net/manual/en/language.oop5.magic.php#object.invoke
79+
*
80+
* The log (callable) filter interface is as follows: `$callable($level, $message, $context)`.
81+
*
82+
* The result of the filter must be a boolean value:
83+
* - TRUE, the log should be ignored and NOT delivered to Understand.io
84+
* - FALSE, the log should be delivered to Understand.io
85+
*
86+
* The `ignored_logs` config value has higher precedence than `log_filter`.
87+
*/
88+
'log_filter' => null,
89+
6990
/**
7091
* Field names which values should not be sent to Understand.io
7192
* It applies to POST and GET request parameters

tests/LogFilterTest.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
use Understand\UnderstandLaravel5\Logger;
4+
use Understand\UnderstandLaravel5\Handlers\CallbackHandler;
5+
use Understand\UnderstandLaravel5\UnderstandLaravel5ServiceProvider;
6+
7+
class LogFilterTest extends Orchestra\Testbench\TestCase
8+
{
9+
10+
/**
11+
* Setup service provider
12+
*
13+
* @param object $app
14+
* @return void
15+
*/
16+
protected function getPackageProviders($app)
17+
{
18+
return [UnderstandLaravel5ServiceProvider::class];
19+
}
20+
21+
/**
22+
* @return void
23+
*/
24+
public function testLogFilterAllowsDelivery()
25+
{
26+
$logsSent = 0;
27+
28+
$callback = function() use(&$logsSent)
29+
{
30+
$logsSent++;
31+
};
32+
33+
$this->app['config']->set('understand-laravel.log_filter', function() {
34+
// FALSE, logs should not be filtered
35+
return false;
36+
});
37+
38+
$handler = new CallbackHandler($callback);
39+
$this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler);
40+
41+
// trigger error
42+
$this->app['Psr\Log\LoggerInterface']->error('test');
43+
$this->app['Psr\Log\LoggerInterface']->warning('test2');
44+
45+
$this->assertEquals(2, $logsSent);
46+
}
47+
48+
/**
49+
* @return void
50+
*/
51+
public function testServiceContainerDependency()
52+
{
53+
$logsSent = 0;
54+
55+
$callback = function() use(&$logsSent)
56+
{
57+
$logsSent++;
58+
};
59+
60+
$dependencyName = 'service-container-dependency';
61+
$dependencyCalled = false;
62+
63+
$this->app->bind($dependencyName, function() use(&$dependencyCalled) {
64+
return function() use(&$dependencyCalled) {
65+
$dependencyCalled = true;
66+
// FALSE, logs should not be filtered
67+
return false;
68+
};
69+
});
70+
71+
$this->app['config']->set('understand-laravel.log_filter', $dependencyName);
72+
73+
$handler = new CallbackHandler($callback);
74+
$this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler);
75+
76+
// trigger error
77+
$this->app['Psr\Log\LoggerInterface']->error('test');
78+
79+
$this->assertTrue($dependencyCalled);
80+
$this->assertEquals(1, $logsSent);
81+
}
82+
83+
/**
84+
* @return void
85+
*/
86+
public function testLogFilterFiltersOneLog()
87+
{
88+
$logsSent = 0;
89+
90+
$callback = function() use(&$logsSent)
91+
{
92+
$logsSent++;
93+
};
94+
95+
$this->app['config']->set('understand-laravel.log_filter', function($level, $message, $context) {
96+
if ($message === 'test2') {
97+
// TRUE, log should be filtered
98+
return true;
99+
}
100+
101+
// FALSE, logs should not be filtered
102+
return false;
103+
});
104+
105+
$handler = new CallbackHandler($callback);
106+
$this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler);
107+
108+
// trigger error
109+
$this->app['Psr\Log\LoggerInterface']->error('test');
110+
$this->app['Psr\Log\LoggerInterface']->warning('test2');
111+
112+
$this->assertEquals(1, $logsSent);
113+
}
114+
115+
/**
116+
* @return void
117+
*/
118+
public function testLogFilterReceivesAllData()
119+
{
120+
$logsSent = 0;
121+
122+
$callback = function() use(&$logsSent)
123+
{
124+
$logsSent++;
125+
};
126+
127+
$this->app['config']->set('understand-laravel.log_filter', function($level, $message, $context) {
128+
$this->assertEquals('error', $level);
129+
$this->assertEquals('test', $message);
130+
$this->assertEquals(['context' => 'value'], $context);
131+
132+
// FALSE, logs should not be filtered
133+
return false;
134+
});
135+
136+
$handler = new CallbackHandler($callback);
137+
$this->app['understand.logger'] = new Logger($this->app['understand.fieldProvider'], $handler);
138+
139+
// trigger error
140+
$this->app['Psr\Log\LoggerInterface']->error('test', ['context' => 'value']);
141+
142+
$this->assertEquals(1, $logsSent);
143+
}
144+
}

0 commit comments

Comments
 (0)