如何在 Symfony 中对 Monolog 消息进行高级过滤?
How to do advanced filtering of Monolog messages in Symfony?
我在 Symfony 2.8
项目中使用 MonologBundle
来管理日志消息。使用不同的 Handlers
将日志写入文件并同时通过电子邮件发送是没有问题的。
我想减少通过邮件收到的消息数量。我已经使用 DeduplicationHandler
和 FingersCrossed
处理程序按错误级别过滤并避免重复消息。这工作正常但还不够。
例如我想减少关于 PageNotFound
错误的邮件数量。如果找不到 /existingPage
,我当然希望得到通知,但我对有关 /.well-known/...
文件的消息不感兴趣。
另一个示例是有关第三方 CSV 解析器组件中错误的消息。有几个已知且无害的错误我不感兴趣,但当然其他错误也很重要。
这些errors/messages是第三方代码生成的,我无法影响源码。我只能完全忽略这些消息,但这不是我想要的。
我正在寻找一种按内容过滤消息的解决方案。如何在 Monolog 中做到这一点?
我已经尝试使用 HandlerWrapper
解决这个问题并在 another question 中讨论了这个问题:想法是,HandlerWrapper
充当过滤器。 HandlerWrapper
由 Monolog 调用,它检查消息内容并决定是否应该处理它(例如丢弃所有消息,包括文本“./well-known/”)。如果消息通过,HandlerWrapper
应该简单地将它交给它的 nested/wrapped 处理程序。否则将跳过消息而不作进一步处理。
但是这个想法没有奏效,另一个问题的答案表明,HandlerWrapper
不是解决这个问题的正确方法。
所以 new/actual 问题是:如何为 Monolog 消息创建过滤器,让我控制特定消息是否应该被处理?
我不确定为什么使用 HandlerWrapper 是错误的方法。
我遇到了同样的问题,并想出了一种方法来包装处理程序以过滤某些记录。
在这个回答中,我描述了两种解决方法,一种更复杂,一种更简单。
(或多或少)复杂的方式
我做的第一件事是创建一个新的 class 来扩展 HandlerWrapper 并添加一些我可以过滤记录的逻辑:
use Monolog\Handler\HandlerWrapper;
class CustomHandler extends HandlerWrapper
{
public function isHandling(array $record)
{
if ($this->shouldFilter($record)) {
return false;
}
return $this->handler->isHandling($record);
}
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
return $this->handler->handle($record);
}
public function handleBatch(array $records)
{
foreach ($records as $record) {
$this->handle($record);
}
}
private function shouldFilter(array $record)
{
return mt_rand(0, 1) === 1;; // add logic here
}
}
然后我创建了一个服务定义和一个 CompilerPass,我可以在其中包装 GroupHandler
services.yml
CustomHandler:
class: CustomHandler
abstract: true
arguments: ['']
use Monolog\Handler\GroupHandler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class CustomMonologHandlerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition(CustomHandler::class)) {
return;
}
$definitions = $container->getDefinitions();
foreach ($definitions as $serviceId => $definition) {
if (!$this->isValidDefinition($definition)) {
continue;
}
$cacheId = $serviceId . '.wrapper';
$container
->setDefinition($cacheId, new ChildDefinition(CustomHandler::class))
->replaceArgument(0, new Reference($cacheId . '.inner'))
->setDecoratedService($serviceId);
}
}
private function isValidDefinition(Definition $definition): bool
{
return GroupHandler::class === $definition->getClass();
}
}
如您所见,我检查了此处的所有定义并找到了将 GroupHandler 设置为 class 的定义。如果是这种情况,我向容器添加一个新定义,用我的 CustomHandler 装饰原始处理程序。
旁注:起初我尝试包装所有处理程序(当然 CustomHandler 除外 :))但是由于一些处理程序实现了其他接口(比如ConsoleHandler 使用 EventSubscriberInterface)这不起作用并导致我不想以某种 hacky 方式解决的问题。
不要忘记将此编译器传递添加到 AppBundle 中的容器 class
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CustomMonologHandlerPass());
}
}
现在一切就绪,您必须对处理程序进行分组才能使其正常工作:
app/config(_prod|_dev).yml
monolog:
handlers:
my_group:
type: group
members: [ 'graylog' ]
graylog:
type: gelf
publisher:
id: my.publisher
level: debug
formatter: my.formatter
简单的方法
我们使用与复杂方式相同的 CustomHandler,然后在配置中定义我们的处理程序:
app/config(_prod|_dev).yml
monolog:
handlers:
graylog:
type: gelf
publisher:
id: my.publisher
level: debug
formatter: my.formatter
用您自己的 CustomHandler
修饰 services.yml 中的处理程序
services.yml
CustomHandler:
class: CustomHandler
decorates: monolog.handler.graylog
arguments: ['@CustomHandler.inner']
对于 decorates 属性 你必须使用格式 monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG
,在本例中是 graylog。
...就是这样
总结
虽然这两种方法都有效,但我使用了第一种,因为我们有几个 symfony 项目,我需要它并装饰所有处理程序
手动不是我想要的。
我希望这对您有所帮助(尽管我来晚了:))
我在 Symfony 2.8
项目中使用 MonologBundle
来管理日志消息。使用不同的 Handlers
将日志写入文件并同时通过电子邮件发送是没有问题的。
我想减少通过邮件收到的消息数量。我已经使用 DeduplicationHandler
和 FingersCrossed
处理程序按错误级别过滤并避免重复消息。这工作正常但还不够。
例如我想减少关于 PageNotFound
错误的邮件数量。如果找不到 /existingPage
,我当然希望得到通知,但我对有关 /.well-known/...
文件的消息不感兴趣。
另一个示例是有关第三方 CSV 解析器组件中错误的消息。有几个已知且无害的错误我不感兴趣,但当然其他错误也很重要。
这些errors/messages是第三方代码生成的,我无法影响源码。我只能完全忽略这些消息,但这不是我想要的。
我正在寻找一种按内容过滤消息的解决方案。如何在 Monolog 中做到这一点?
我已经尝试使用 HandlerWrapper
解决这个问题并在 another question 中讨论了这个问题:想法是,HandlerWrapper
充当过滤器。 HandlerWrapper
由 Monolog 调用,它检查消息内容并决定是否应该处理它(例如丢弃所有消息,包括文本“./well-known/”)。如果消息通过,HandlerWrapper
应该简单地将它交给它的 nested/wrapped 处理程序。否则将跳过消息而不作进一步处理。
但是这个想法没有奏效,另一个问题的答案表明,HandlerWrapper
不是解决这个问题的正确方法。
所以 new/actual 问题是:如何为 Monolog 消息创建过滤器,让我控制特定消息是否应该被处理?
我不确定为什么使用 HandlerWrapper 是错误的方法。
我遇到了同样的问题,并想出了一种方法来包装处理程序以过滤某些记录。
在这个回答中,我描述了两种解决方法,一种更复杂,一种更简单。
(或多或少)复杂的方式
我做的第一件事是创建一个新的 class 来扩展 HandlerWrapper 并添加一些我可以过滤记录的逻辑:
use Monolog\Handler\HandlerWrapper;
class CustomHandler extends HandlerWrapper
{
public function isHandling(array $record)
{
if ($this->shouldFilter($record)) {
return false;
}
return $this->handler->isHandling($record);
}
public function handle(array $record)
{
if (!$this->isHandling($record)) {
return false;
}
return $this->handler->handle($record);
}
public function handleBatch(array $records)
{
foreach ($records as $record) {
$this->handle($record);
}
}
private function shouldFilter(array $record)
{
return mt_rand(0, 1) === 1;; // add logic here
}
}
然后我创建了一个服务定义和一个 CompilerPass,我可以在其中包装 GroupHandler
services.yml
CustomHandler:
class: CustomHandler
abstract: true
arguments: ['']
use Monolog\Handler\GroupHandler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class CustomMonologHandlerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition(CustomHandler::class)) {
return;
}
$definitions = $container->getDefinitions();
foreach ($definitions as $serviceId => $definition) {
if (!$this->isValidDefinition($definition)) {
continue;
}
$cacheId = $serviceId . '.wrapper';
$container
->setDefinition($cacheId, new ChildDefinition(CustomHandler::class))
->replaceArgument(0, new Reference($cacheId . '.inner'))
->setDecoratedService($serviceId);
}
}
private function isValidDefinition(Definition $definition): bool
{
return GroupHandler::class === $definition->getClass();
}
}
如您所见,我检查了此处的所有定义并找到了将 GroupHandler 设置为 class 的定义。如果是这种情况,我向容器添加一个新定义,用我的 CustomHandler 装饰原始处理程序。
旁注:起初我尝试包装所有处理程序(当然 CustomHandler 除外 :))但是由于一些处理程序实现了其他接口(比如ConsoleHandler 使用 EventSubscriberInterface)这不起作用并导致我不想以某种 hacky 方式解决的问题。
不要忘记将此编译器传递添加到 AppBundle 中的容器 class
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CustomMonologHandlerPass());
}
}
现在一切就绪,您必须对处理程序进行分组才能使其正常工作:
app/config(_prod|_dev).yml
monolog:
handlers:
my_group:
type: group
members: [ 'graylog' ]
graylog:
type: gelf
publisher:
id: my.publisher
level: debug
formatter: my.formatter
简单的方法
我们使用与复杂方式相同的 CustomHandler,然后在配置中定义我们的处理程序:
app/config(_prod|_dev).yml
monolog:
handlers:
graylog:
type: gelf
publisher:
id: my.publisher
level: debug
formatter: my.formatter
用您自己的 CustomHandler
修饰 services.yml 中的处理程序services.yml
CustomHandler:
class: CustomHandler
decorates: monolog.handler.graylog
arguments: ['@CustomHandler.inner']
对于 decorates 属性 你必须使用格式 monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG
,在本例中是 graylog。
...就是这样
总结
虽然这两种方法都有效,但我使用了第一种,因为我们有几个 symfony 项目,我需要它并装饰所有处理程序 手动不是我想要的。
我希望这对您有所帮助(尽管我来晚了:))