Изготовленный на заказ 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 8

3 ответа:

Я использую Symfony 3.1 и пытаюсь настроить Monolog таким образом, чтобы запросы от GoogleBot не регистрировались...


  1. Быстрый способ предотвратить посещение вашего сайта роботами-поместить эти две строки в файл /robots.txt на вашем сервере. Создайте файл robots.txt в каталоге 'web' и вставьте следующее содержимое:

    User-agent: *
    Disallow: /
    

    Https://support.google.com/webmasters/answer/6062608?hl=en&visit_id=1-636097099675465769-3677253464&rd=1

    Это рекомендуемый вариант, когда вам нужно полностью избежать доступа , то есть ваши сайты больше не будут индексироваться поисковыми системами и другими ботами. Вам не нужно ничего настраивать/реализовывать в вашем приложении, чтобы достичь этого.


  1. Теперь, если вам нужен бот для входа, но вы не хотите регистрироваться это в журналах. Вместо того, чтобы записывать файлы журналов где-то, некоторые обработчики используются для фильтрации или изменения записей журнала перед отправкой их другим обработчикам . По умолчанию в среде 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


  1. То, что вы пытаетесь сделать, не рекомендуется, потому что обработчик будет зависеть от 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 по CompilerPass и добавлен к EventDispatcher как Listener к Kernel событиям, которые onRequest проверяют User-Agent заголовок, ищущий бота, а затем очищает Monolog\Logger (передано в конфигураторе) все обработчики (или поставить NullHandler, Если пустой массив обработчиков не работает).

Di configurator-это единственный способ изменить ваши сервисы во время выполнения, который может быть применен в качестве уровня определения сервиса. Такое определение может быть присоединено или отсоединено, если это не требуется, и это действительно ничего не меняет в вашем приложении.