Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Extending library settings support

Boost , Chapter 1. Boost.Log v2 , Extending the library

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Если вы пишете свои собственные лесозаготовительные раковины или используете свои собственные типы в атрибутах, вы можете добавить поддержку этих компонентов в раздел настроек, предоставляемый библиотекой. Не делая этого, библиотека не будет знать о ваших типах и, таким образом, не сможет использовать их при анализе параметров.

Adding support for user-defined types to the formatter parser
#include <boost/log/utility/setup/formatter_parser.hpp>

Для того, чтобы добавить поддержку для определенных пользователем типов в форматист-парсер, необходимо зарегистрировать завод-форматер. Фабрика - это в основном объект, который происходит от интерфейса formatter_factory. Завод в основном реализует одиночный метод Создать_форматтер, который, когда его называют, будет строить формататор для конкретного значения атрибута.

Когда пользовательский тип поддерживает размещение в потоке с оператором<, и это поведение оператора подходит для ведения лесозаготовок, можно использовать простой общий завод форматирования, предоставляемый библиотекой из коробки. Например, предположим, что у нас есть следующий тип, определенный пользователем, который мы хотим использовать в качестве значения атрибута:

struct point
{
    float m_x, m_y;
    point() : m_x(0.0f), m_y(0.0f) {}
    point(float x, float y) : m_x(x), m_y(y) {}
};
template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, point const& p)
{
    strm << "(" << p.m_x << ", " << p.m_y << ")";
    return strm;
}

Затем, чтобы зарегистрировать этот тип с простой фабрикой форматирования, одного вызова register_simple_formatter_factory будет достаточно:

void init_factories()
{
    logging::register_simple_formatter_factory< point, char >("Coordinates");
}

[Note]Note

оператор< для значения атрибута сохраненного типа должен быть виден с точки зрения этого звонка.

Функция принимает сохраненный тип значения атрибута (point, в нашем случае) и целевой тип символов, используемый форматерами в качестве параметров шаблона. С точки зрения этого звонка, всякий раз, когда форматер-парсер сталкивается с ссылкой на атрибут «Координаты» в строке формата, он будет ссылаться на завод-форматизатор, который будет строить формататор, который называет наш оператор< для класса point.

[Tip]Tip

Как правило, неплохо зарегистрировать все заводы-форматеры на ранней стадии инициализации приложения, прежде чем любая другая инициализация библиотеки, например, чтение настраиваемых файлов.

Из описания парсера форматтера известно, что парсер поддерживает переход дополнительных параметров от строки формата к заводу-форматеру. Мы можем использовать эти параметры для настройки вывода, создаваемого форматером.

Например, давайте реализуем настраиваемое форматирование наших объектов point, чтобы следующая строка формата работала так, как ожидалось:

%TimeStamp% %Coordinates(format="{%0.3f; %0.3f}")% %Message%

Простой форматерный завод игнорирует все дополнительные параметры из строки формата, поэтому мы должны реализовать нашу собственную фабрику вместо этого. Пользовательские фабрики зарегистрированы с функцией register_formatter_factory, которая похожа на функцию register_simple_formatter_factory, но принимает указателей на фабрику вместо явных параметров шаблона.

// Custom point formatter
class point_formatter
{
public:
    typedef void result_type;
public:
    explicit point_formatter(std::string const& fmt) : m_format(fmt)
    {
    }
    void operator() (logging::formatting_ostream& strm, logging::value_ref< point > const& value) const
    {
        if (value)
        {
            point const& p = value.get();
            m_format % p.m_x % p.m_y;
            strm << m_format;
            m_format.clear();
        }
    }
private:
    mutable boost::format m_format;
};
// Custom point formatter factory
class point_formatter_factory :
    public logging::basic_formatter_factory< char, point >
{
public:
    formatter_type create_formatter(logging::attribute_name const& name, args_map const& args)
    {
        args_map::const_iterator it = args.find("format");
        if (it != args.end())
            return boost::phoenix::bind(point_formatter(it->second), expr::stream, expr::attr< point >(name));
        else
            return expr::stream << expr::attr< point >(name);
    }
};
void init_factories()
{
    logging::register_formatter_factory("Coordinates", boost::make_shared< point_formatter_factory >());
}

Давайте пройдем через образец кода. Наш класс point_formatter_factory происходит от класса basic_formatter_factory базовый класс, предоставляемый библиотекой. Этот класс происходит от базового интерфейса formatter_factory и определяет несколько полезных типов, таких как formatter_type и args_map, которые мы используем. Единственное, что осталось сделать на нашей фабрике, это определить метод Create_formatter. Метод анализирует параметры из строки формата, которые передаются как аргумент args, который в основном std::map клавиш строк (параметрические имена) к строковым значениям (значения параметров). Мы стремимся к параметру format и ожидаем, что он будет содержать строку Boost.Format-совместимого формата для наших объектов point. Если параметр найден, мы создаем формататор, который вызывает point_formatter для значений атрибутов. В противном случае мы создаем формататор по умолчанию, который просто использует оператор<, как это делает простая фабрика. Обратите внимание, что мы используем аргумент name Create_formatter для идентификации атрибута, чтобы тот же завод мог использоваться для различных атрибутов.

point_formatter является нашим пользовательским форматером на основе Boost.Format. С помощью Boost.Phoenix и держатели места экспрессии мы можем построить форматер, который будет извлекать значение атрибута и передавать его вместе с целевым потоком на point_formatter функциональный объект. Обратите внимание, что форматер принимает значение атрибута, обернутое в value_ref обертку, которая может быть пустой, если значение не присутствует.

Наконец, призыв к register_formatter_factory создает фабрику и добавляет ее в библиотеку.

Вы можете найти полный код этого примера здесь.

Adding support for user-defined types to the filter parser
#include <boost/log/utility/setup/filter_parser.hpp>

Вы можете расширить фильтр-парсер таким же образом, как вы можете расширить форматтер-парсер - путем регистрации фильтров для ваших значений атрибутов в библиотеке. Однако, поскольку для описания фильтров требуется значительно более сложный синтаксис, фильтрующая фабрика обычно реализует несколько функций генератора.

Как и в случае с расширением отформатера, вы можете избежать орфографии завода фильтров и зарегистрировать простой завод, предоставляемый библиотекой:

void init_factories()
{
    logging::register_simple_filter_factory< point, char >("Coordinates");
}

Для этого тип пользователя должен выполнять эти требования:

  1. Поддержка чтения из потока ввода с оператором>.
  2. Поддержка полного набора операторов сравнения и заказа.

Естественно, все эти операторы должны быть видны с точки зрения register_simple_filter_factory. Обратите внимание, что в отличие от простой фабрики форматирования, фильтрующая фабрика требует, чтобы тип пользователя поддерживал чтение из потока. Это связано с тем, что фабрика фильтров должна будет изобразить аргументацию отношения фильтра из строки.

Но нам не сойдет с рук простая фильтровальная фабрика, потому что наш класс point не имеет разумной семантики заказа и поэтому мы не можем определить полный набор операторов. Вместо этого нам придется внедрить собственную фильтровальную фабрику. Фильтровые заводы происходят из интерфейса filter_factory. Этот базовый класс объявляет ряд виртуальных функций, которые будут называться для создания фильтров, в соответствии с выражением фильтра. Если некоторые функции не перекрываются заводом, соответствующие операции считаются не поддерживаемыми значением атрибута. Но прежде чем мы определим фильтрующую фабрику, мы должны улучшить наш класс point немного:

struct point
{
    float m_x, m_y;
    point() : m_x(0.0f), m_y(0.0f) {}
    point(float x, float y) : m_x(x), m_y(y) {}
};
bool operator== (point const& left, point const& right);
bool operator!= (point const& left, point const& right);
template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, point const& p);
template< typename CharT, typename TraitsT >
std::basic_istream< CharT, TraitsT >& operator>> (std::basic_istream< CharT, TraitsT >& strm, point& p);

Мы добавили операторов сравнения и ввода для класса point. Оператор вывода все еще используется форматерами и не требуется заводом-фильтром. Теперь мы можем определить и зарегистрировать завод фильтров:

// Custom point filter factory
class point_filter_factory :
    public logging::filter_factory< char >
{
public:
    logging::filter on_exists_test(logging::attribute_name const& name)
    {
        return expr::has_attr< point >(name);
    }
    logging::filter on_equality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) == boost::lexical_cast< point >(arg);
    }
    logging::filter on_inequality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) != boost::lexical_cast< point >(arg);
    }
};
void init_factories()
{
    logging::register_filter_factory("Coordinates", boost::make_shared< point_filter_factory >());
}

Назвав функцию register_filter_factory, всякий раз, когда фильтр-парсер сталкивается с атрибутом «Координаты», упомянутым в фильтре, он будет использовать объект point_filter_factory для построения соответствующего фильтра. Например, в случае следующего фильтра

%Coordinates% = "(10, 10)"

метод on_equality_relation будет называться с name аргументом "Координаты" и arg быть "(10, 10)".

[Note]Note

Цитаты вокруг скобки необходимы, потому что фильтр-парсер должен интерпретировать координаты точки как одну строку. Кроме того, круглые скобки уже используются для группирования субвыражений фильтра. Всякий раз, когда необходимо передать несколько параметров к соотношению (как в данном случае - ряд компонентов point класс), параметры должны быть закодированы в цитируемую строку. Строка может включать в себя C-стиль побега последовательности, которые будут разворачиваться при разборке.

Сконструированный фильтр будет использовать соответствующие операторы сравнения для класса point. Заказ операций, таких как «>» или «<=», не будет поддерживаться для атрибутов, называемых «Координаты», и именно так мы этого хотим, потому что класс point не поддерживает их. Полный пример доступен здесь.

Библиотека позволяет не только добавлять поддержку для новых типов, но и связывать с ними новые отношения. Например, мы можем создать новое отношение «is_in_rectangle», которое даст положительный результат, если координаты вписываются в прямоугольник с двумя точками. Фильтр может выглядеть так:

%Coordinates% is_in_rectangle "{(10, 10) - (20, 20)}"

Во-первых, давайте определим наш класс прямоугольника:

struct rectangle
{
    point m_top_left, m_bottom_right;
};
template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, rectangle const& r);
template< typename CharT, typename TraitsT >
std::basic_istream< CharT, TraitsT >& operator>> (std::basic_istream< CharT, TraitsT >& strm, rectangle& r);

Как было сказано, прямоугольник описывается двумя точками - верхними левыми и нижними правыми углами прямоугольника. Теперь давайте расширим нашу фильтрующую фабрику с помощью метода on_custom_relation:

// The function checks if the point is inside the rectangle
bool is_in_rectangle(logging::value_ref< point > const& p, rectangle const& r)
{
    if (p)
    {
        return p->m_x >= r.m_top_left.m_x && p->m_x <= r.m_bottom_right.m_x &&
               p->m_y >= r.m_top_left.m_y && p->m_y <= r.m_bottom_right.m_y;
    }
    return false;
}
// Custom point filter factory
class point_filter_factory :
    public logging::filter_factory< char >
{
public:
    logging::filter on_exists_test(logging::attribute_name const& name)
    {
        return expr::has_attr< point >(name);
    }
    logging::filter on_equality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) == boost::lexical_cast< point >(arg);
    }
    logging::filter on_inequality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) != boost::lexical_cast< point >(arg);
    }
    logging::filter on_custom_relation(logging::attribute_name const& name, string_type const& rel, string_type const& arg)
    {
        if (rel == "is_in_rectangle")
        {
            return boost::phoenix::bind(&is_in_rectangle, expr::attr< point >(name), boost::lexical_cast< rectangle >(arg));
        }
        throw std::runtime_error("Unsupported filter relation: " + rel);
    }
};
void init_factories()
{
    logging::register_filter_factory("Coordinates", boost::make_shared< point_filter_factory >());
}

Метод on_custom_relation называется реляционным именем (в нашем случае строка «is_in_rectangle») и аргументом правой руки для отношения (описание прямоугольника). Все, что нам нужно сделать, это построить фильтр, который реализован нашей функцией is_in_rectangle. Мы используем bind от Boost.Phoenix для композиции фильтра от функции и Atribute placeholders. Вы можете найти полный код этого примера здесь.

Adding support for user-defined sinks
#include <boost/log/utility/setup/from_settings.hpp>
#include <boost/log/utility/setup/from_stream.hpp>

Библиотека предоставляет механизм расширения поддержки для раковин, аналогичных форматировщику и фильтрующим парсерам. Для того, чтобы иметь возможность упоминать пользовательские раковины в файле настроек, пользователь должен зарегистрировать раковинную фабрику, которая по существу содержит метод Create_sink, который получает подраздел постановления и возвращает указателю к инициализируемой раковине. Завод зарегистрирован для определенного места назначения (см. описание файла settings), поэтому всякий раз, когда раковина с указанным пунктом назначения упоминается в файле настроек, завод получает название.

Например, давайте зарегистрируем stat_collector раковину, которую мы описали перед в библиотеке. Во-первых, давайте вспомним определение раковины:

// The backend collects statistical information about network activity of the application
class stat_collector :
    public sinks::basic_sink_backend<
        sinks::combine_requirements<
            sinks::synchronized_feeding,
            sinks::flushing
        >::type
    >
{
private:
    // The file to write the collected information to
    std::ofstream m_csv_file;
    // Here goes the data collected so far:
    // Active connections
    unsigned int m_active_connections;
    // Sent bytes
    unsigned int m_sent_bytes;
    // Received bytes
    unsigned int m_received_bytes;
    // The number of collected records since the last write to the file
    unsigned int m_collected_count;
    // The time when the collected data has been written to the file last time
    boost::posix_time::ptime m_last_store_time;
    // The collected data writing interval
    boost::posix_time::time_duration m_write_interval;
public:
    // The constructor initializes the internal data
    stat_collector(const char* file_name, boost::posix_time::time_duration write_interval);
    // The function consumes the log records that come from the frontend
    void consume(logging::record_view const& rec);
    // The function flushes the file
    void flush();
private:
    // The function resets statistical accumulators to initial values
    void reset_accumulators();
    // The function writes the collected data to the file
    void write_data();
};

По сравнению с более ранним определением мы добавили параметр write_interval, чтобы мы могли установить интервал статистической информации в файле настроек. Реализация раковины остается практически такой же, как и раньше. Теперь мы должны определить завод:

// Factory for the stat_collector sink
class stat_collector_factory :
    public logging::sink_factory< char >
{
public:
    // Creates the sink with the provided parameters
    boost::shared_ptr< sinks::sink > create_sink(settings_section const& settings)
    {
        // Read sink parameters
        std::string file_name;
        if (boost::optional< std::string > param = settings["FileName"])
            file_name = param.get();
        else
            throw std::runtime_error("No target file name specified in settings");
        boost::posix_time::time_duration write_interval = boost::posix_time::minutes(1);
        if (boost::optional< std::string > param = settings["WriteInterval"])
        {
            unsigned int sec = boost::lexical_cast< unsigned int >(param.get());
            write_interval = boost::posix_time::seconds(sec);
        }
        // Create the sink
        boost::shared_ptr< stat_collector > backend = boost::make_shared< stat_collector >(file_name.c_str(), write_interval);
        boost::shared_ptr< sinks::synchronous_sink< stat_collector > > sink = boost::make_shared< sinks::synchronous_sink< stat_collector > >(backend);
        if (boost::optional< std::string > param = settings["Filter"])
        {
            sink->set_filter(logging::parse_filter(param.get()));
        }
        return sink;
    }
};
void init_factories()
{
    logging::register_sink_factory("StatCollector", boost::make_shared< stat_collector_factory >());
}

Как вы можете видеть, мы читаем параметры из настроек и просто создаем нашу раковину с ними в результате метода Создать_sink. Как правило, пользователи могут свободно называть параметры своих раковин так, как им нравится, до тех пор, пока соблюдается формат файла settings. Тем не менее, это хорошая идея, чтобы следовать шаблону, установленному библиотекой и повторно использовать имена параметров с тем же значением. То есть, должно быть очевидно, что параметр «Filter» означает то же самое как для библиотечной раковины «TextFile», так и для раковины «StatCollector».

После определения завода мы должны зарегистрировать его только с register_sink_factory call. Первым аргументом является новое значение параметра «назначение» в настройках. Всякий раз, когда библиотека находит описание раковины с пунктом назначения «StatCollector», наша фабрика будет использоваться для создания раковины. Также возможно перенаправить библиотечные типы назначения с заводами, определенными пользователем, однако впоследствии восстановить фабрики по умолчанию невозможно.

[Note]Note

Поскольку параметр "Destination" используется для определения завода по производству раковин, этот параметр зарезервирован и не может использоваться заводами по производству раковин для своих целей.

Теперь, когда завод зарегистрирован, мы можем использовать его при инициализации из файлов или настроек. Например, это то, как файл настроек может выглядеть:

[Sinks.MyStat]
Destination=StatCollector
FileName=stat.csv
WriteInterval=30

Полный код примера в этом разделе можно найти здесь.


PrevUpHomeNext

Статья Extending library settings support раздела Chapter 1. Boost.Log v2 Extending the library может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Extending the library ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 18:59:50/0.010993957519531/0