![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
Extending library settings supportBoost , Chapter 1. Boost.Log v2 , Extending the library
|
![]() | Note |
---|---|
|
Функция принимает сохраненный тип значения атрибута (point
, в нашем случае) и целевой тип символов, используемый форматерами в качестве параметров шаблона. С точки зрения этого звонка, всякий раз, когда форматер-парсер сталкивается с ссылкой на атрибут «Координаты» в строке формата, он будет ссылаться на завод-форматизатор, который будет строить формататор, который называет наш оператор<
для класса point
.
![]() | 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
создает фабрику и добавляет ее в библиотеку.
Вы можете найти полный код этого примера здесь.
#include <boost/log/utility/setup/filter_parser.hpp
>
Вы можете расширить фильтр-парсер таким же образом, как вы можете расширить форматтер-парсер - путем регистрации фильтров для ваших значений атрибутов в библиотеке. Однако, поскольку для описания фильтров требуется значительно более сложный синтаксис, фильтрующая фабрика обычно реализует несколько функций генератора.
Как и в случае с расширением отформатера, вы можете избежать орфографии завода фильтров и зарегистрировать простой завод, предоставляемый библиотекой:
void init_factories() { logging::register_simple_filter_factory< point, char >("Coordinates"); }
Для этого тип пользователя должен выполнять эти требования:
оператором>
.Естественно, все эти операторы должны быть видны с точки зрения 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 |
---|---|
Цитаты вокруг скобки необходимы, потому что фильтр-парсер должен интерпретировать координаты точки как одну строку. Кроме того, круглые скобки уже используются для группирования субвыражений фильтра. Всякий раз, когда необходимо передать несколько параметров к соотношению (как в данном случае - ряд компонентов |
Сконструированный фильтр будет использовать соответствующие операторы сравнения для класса 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. Вы можете найти полный код этого примера здесь.
#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 |
---|---|
Поскольку параметр "Destination" используется для определения завода по производству раковин, этот параметр зарезервирован и не может использоваться заводами по производству раковин для своих целей. |
Теперь, когда завод зарегистрирован, мы можем использовать его при инициализации из файлов или настроек. Например, это то, как файл настроек может выглядеть:
[Sinks.MyStat] Destination=StatCollector FileName=stat.csv WriteInterval=30
Полный код примера в этом разделе можно найти здесь.
Статья Extending library settings support раздела Chapter 1. Boost.Log v2 Extending the library может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: Extending the library ::
реклама |