Я использую MonologBundle
в моем проекте Symfony 2.8
для управления сообщениями журнала. С помощью разных Handlers
нет проблем писать записи в файл и отправлять их по электронной почте одновременно.
Я хотел бы уменьшить количество сообщений, получаемых по почте. Я уже использую DeduplicationHandler
и обработчик FingersCrossed
для фильтрации по уровню ошибок и для предотвращения дублирования сообщений. Это работает отлично, но этого недостаточно.
Например, я хотел бы уменьшить количество сообщений о ошибках PageNotFound
. Конечно, я хочу получать уведомление, если /existingPage
не найден, но мне не интересны сообщения о /.well-known/...
Другим примером являются сообщения об ошибках стороннего компонента парсера CSV. Есть несколько известных и безобидных ошибок, которые мне не интересны, но, конечно, важны другие ошибки.
Эти ошибки / сообщения генерируются сторонним кодом, я не могу влиять на источник. Я мог полностью игнорировать эти сообщения, но этого я не хочу.
Я ищу решение для фильтрации сообщений по содержанию. Как это можно сделать в Монологе?
Я уже пытался решить это с помощью HandlerWrapper
и обсудил этот вопрос в другом вопросе : идея заключалась в том, что HandlerWrapper
действует как фильтр. HandlerWrapper
вызывается HandlerWrapper
, он проверяет содержимое сообщения и решает, как его обрабатывать или нет (например, отбросить все сообщения, включая текст «./well-known/»). Если сообщения проходят, HandlerWrapper
должен просто передать его своему вложенному / завернутому обработчику. В противном случае сообщение пропускается без дальнейшей обработки.
Однако эта идея не сработала, и ответы на другой вопрос указывают на то, что HandlerWrapper
не подходит для этой проблемы.
Таким образом, новый / актуальный вопрос: как создать фильтр для сообщений Монолога, который позволяет мне контролировать, какое конкретное сообщение должно быть процессом или нет?
Я не уверен, почему использование HandlerWrapper – неправильный способ сделать это.
У меня была такая же проблема, и я понял способ переноса обработчика для фильтрации определенных записей.
В этом ответе я описываю два способа решения этого, более сложный и простой.
Первое, что я сделал, – создать новый класс, который расширяет 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 в качестве своего класса. Если это так, я добавляю новое определение в контейнер, который украшает оригинальный обработчик моим CustomHandler.
Замечание : сначала я попытался обернуть все обработчики (за исключением CustomHandler, конечно :)), но из-за некоторых обработчиков, реализующих другие интерфейсы (например, ConsoleHandler с использованием EventSubscriberInterface ), это не сработало и привело к проблемам, которые я не хотел решить в какой-то хакерский путь.
Не забудьте добавить этот компилятор в контейнер в классе AppBundle
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, что и мы, сложным способом, затем определяем наши обработчики в config:
app/config(_prod|_dev).yml
monolog: handlers: graylog: type: gelf publisher: id: my.publisher level: debug formatter: my.formatter
Украсьте обработчик в своих службах.yml своим собственным CustomHandler
services.yml
CustomHandler: class: CustomHandler decorates: monolog.handler.graylog arguments: ['@CustomHandler.inner']
Для свойства decorates вы должны использовать формат monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG
, в этом случае это был graylog.
… вот и все
Хотя оба способа работают, я использовал первый, так как у нас есть несколько проектов symfony, где мне это нужно, и оформление всех обработчиков вручную – это просто не то, что я хотел.
Надеюсь, это поможет (хотя я довольно поздно для ответа :))