Изготовленный на заказ HandlerWrapper с MonologBundle
Я использую Symfony 3.1 и пытаюсь настроить Monolog таким образом, чтобы запросы от Googlebot не регистрировались. Для этого я написал UserAgentProcessor
, который уже работает по назначению. На следующем шаге я попытался написать BotFilter, который выглядит следующим образом:
<?php
namespace AppBundleHandler;
use MonologHandlerHandlerWrapper;
class FilterBotsHandler extends HandlerWrapper
{
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
if (stripos($record['extra']['userAgent'], 'bot') !== false){
return false;
} else {
return $this->handler->isHandling($record);
}
}
}
Это было вдохновлено комментариями в абстрактном классе HandlerWrapper
(Посмотрите здесь).
Теперь я хочу добавить этот фильтр в мою YML-конфигурацию monolog. Я попытался добавить его к своим услугам, но это было невозможно, так как HandlerWrapper
требуется экземпляр обработчика для его конструктора. Я исследовал, как можно использовать фильтр без службы, но, насколько я могу видеть, пакет monolog принимает только встроенные типы и общий тип службы.
Теперь вопрос: Как я могу использовать фильтр в своей конфигурации?
3 ответа:
Я использую Symfony 3.1 и пытаюсь настроить Monolog таким образом, чтобы запросы от GoogleBot не регистрировались...
Быстрый способ предотвратить посещение вашего сайта роботами-поместить эти две строки в файл
/robots.txt
на вашем сервере. Создайте файлrobots.txt
в каталоге 'web' и вставьте следующее содержимое:User-agent: * Disallow: /
Это рекомендуемый вариант, когда вам нужно полностью избежать доступа , то есть ваши сайты больше не будут индексироваться поисковыми системами и другими ботами. Вам не нужно ничего настраивать/реализовывать в вашем приложении, чтобы достичь этого.
Теперь, если вам нужен бот для входа, но вы не хотите регистрироваться это в журналах. Вместо того, чтобы записывать файлы журналов где-то, некоторые обработчики используются для фильтрации или изменения записей журнала перед отправкой их другим обработчикам . По умолчанию в среде
prod
используется один мощный встроенный обработчик под названиемfingers_crossed
. Он сохраняет все сообщения журнала во время запроса, но передает их только второму обработчику, если одно из сообщений достигаетaction_level
:# app/config/config.yml monolog: handlers: filter_for_errors: type: fingers_crossed # if *one* log is error or higher, pass *all* to file_log action_level: error handler: file_log # now passed *all* logs, but only if one log is error or higher file_log: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log"
Таким образом, в вашем
prod.log
файле просто будут регистрироваться сообщения/запросы, содержащие некоторые ошибка, поэтому боты не имеют эффекта на этом уровне.Подробнее об этом http://symfony.com/doc/current/logging.html
То, что вы пытаетесь сделать, не рекомендуется, потому что обработчик будет зависеть от http-запроса вместо записей журнала, которые будут вне контекста, однако вы можете легко зарегистрировать свой собственный обработчик в Symfony:
Давайте создадим пользовательский обработчик класс:
namespace AppBundle\Monolog\Handler; use Monolog\Handler\AbstractHandler; class StopBotLogHandler extends AbstractHandler { public function isBotRequestDetected() { // here your code to detect Bot requests, return true or false // something like this: // return isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/bot|crawl|slurp|spider/i', $_SERVER['HTTP_USER_AGENT']); } /** * Checks whether the given record will be handled by this handler. * * This is mostly done for performance reasons, to avoid calling processors for nothing. * * Handlers should still check the record levels within handle(), returning false in isHandling() * is no guarantee that handle() will not be called, and isHandling() might not be called * for a given record. * * @param array $record Partial log record containing only a level key (e.g: array('level' => 100) for DEBUG level) * * @return bool */ public function isHandling(array $record) { return $this->isBotRequestDetected(); } /** * Handles a record. * * All records may be passed to this method, and the handler should discard * those that it does not want to handle. * * The return value of this function controls the bubbling process of the handler stack. * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * * @param array $record The record to handle * * @return bool true means that this handler handled the record, and that bubbling is not permitted. * false means the record was either not processed or that this handler allows bubbling. */ public function handle(array $record) { // do nothing, just returns true whether the request is detected as "bot", this will break the handlers loop. // else returns false and other handler will handle the record. return $this->isBotRequestDetected(); } }
Всякий раз, когда вы добавляете запись в регистратор, она пересекает стек обработчиков. Каждый обработчик решает, полностью ли он обработал запись, и если да, то на этом распространение записи заканчивается.
Важно: прочитайте phpdoc из методов
isHandling()
иhandle()
для получения более подробной информации.Далее зарегистрируем класс как сервис "без тегов":
# app/config/services.yml services: monolog.handler.stop_bot_log: class: AppBundle\Monolog\Handler\StopBotLogHandler public: false
Затем добавьте его обработчик в список
handlers
:# app/config/config_prod.yml monolog: handlers: # ... stopbotlog: type: service id: monolog.handler.stop_bot_log priority: 1
Примечание
type
собственность должно быть равноservice
,id
должно быть имя службы перед определением иpriority
должно быть больше, чем0
, чтобы гарантировать, что его обработчик будет выполнен перед любым другим обработчиком.Когда GoogleBot выполняет запрос к приложению веб-сайта, то
stopbotlog
обработчик останавливает все обработчики после него и не регистрирует никаких сообщений журнала.Помните, что это не рекомендуемый способ сделать это! В соответствии с вашими потребностями, реализация варианта 1 или 2 должно быть достаточно.
Если вы хотите игнорировать запросы бота для группы обработчиков, вы можете переопределить параметр контейнера
monolog.handler.group.class
и переопределить поведение группыhandler
:namespace AppBundle\Handler; use Monolog\Handler\GroupHandler; class NoBotGroupHandler extends GroupHandler { public function isBotRequestDetected() { // here your code to detect Bot requests, return true or false } public function handle(array $record) { if ($this->isBotRequestDetected()) { // ignore bot request for handlers list return false === $this->bubble; } return parent::handle($record); } }
В вашем
config_prod.yml
илиservices.yml
:parameters: monolog.handler.group.class: AppBundle\Handler\NoBotGroupHandler
Вот оно! Теперь вы можете остановить журналы ботов для списка пользовательских дескрипторов:
# config_prod.yml monolog: handlers: grouped: type: group members: [main, console, chromephp]
Наконец, если у вас есть трудности с анализом файлов журналов, я рекомендую использовать этот удивительный инструмент: https://github.com/EasyCorp/easy-log-handler
Это довольно грязный трюк, но если он вам действительно нужен, вы можете сделать его так.
Предположим, что вы хотите обернуть обработчик с типом
stream
:Добавьте конструктор в вас FilterBotsHandler :
public function __constructor($path, $level, $bubble, $permissions) { $this->handler = new Monolog\Handler\StreamHandler($path, $level, $bubble, $permissions); }
, а затем переопределите параметр
monolog.handler.stream.class
:parameters: monolog.handler.stream.class: AppBundle\Handler\FilterBotsHandler
Убедитесь, что этот параметр будет определен после того, как он был определен MonologBundle.
Вот и все. Должен работать.
Вы можете написать
CompilerPass
в вашемAppBundle
, который добавляет конфигуратор вmonolog
сервис. Такой конфигуратор может быть также слушателем событий запроса, который может динамически заменять все обработчики по запросу и обнаружению бота и передавать пустой массив обработчиков вLogger
, который может быть удержан при вызове конфигуратора.Другими словами конфигуратор добавлен к DI по
Di configurator-это единственный способ изменить ваши сервисы во время выполнения, который может быть применен в качестве уровня определения сервиса. Такое определение может быть присоединено или отсоединено, если это не требуется, и это действительно ничего не меняет в вашем приложении.CompilerPass
и добавлен кEventDispatcher
какListener
кKernel
событиям, которыеonRequest
проверяютUser-Agent
заголовок, ищущий бота, а затем очищаетMonolog\Logger
(передано в конфигураторе) все обработчики (или поставитьNullHandler
, Если пустой массив обработчиков не работает).