![]() |
![]() ![]() ![]() ![]() |
![]() |
Extending the libraryBoost , Chapter 1. Boost.Log v2 , Chapter 1. Boost.Log v2
|
||||||||||||||||||||
![]() | Tip |
|---|---|
Выбирая любой из требований синхронизации потока, вы фактически разрешаете или запрещаете использовать определенныефронтенды раковиныс вашим бэкэндом. |
Несколько требований могут быть объединены вfrontend_requirementstype сCombin_requirementsmetafunction:
typedef sinks::combine_requirements< sinks::synchronized_feeding, sinks::formatted_records, sinks::flushing >::type frontend_requirements;
Следует отметить, чтосинхронизированное_кормлениеиодновременное_кормлениене следует сочетать вместе, поскольку это сделало бы требование синхронизации двусмысленным.синхронное кормлениеявляется более строгим требованием, чемодновременное кормление, поэтому всякий раз, когда бэкэнд требует одновременного кормления, он также способен синхронизировать кормление.
Метафункцияимеет_требованиеможет быть использована для тестирования для конкретного требования вfrontend_требованияtypedef.
В качестве примера использования классаbasic_sink_backendдавайте реализуем простой сборщик статистической информации backend. Предположим, что у нас есть сетевой сервер, и мы хотим отслеживать, сколько входящих соединений активны и сколько данных было отправлено или получено. Собранная информация должна быть записана в CSV-файл каждую минуту. Определение backend может выглядеть примерно так:
// 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; public: // The constructor initializes the internal data explicit stat_collector(const char* file_name); // 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(); };
Нам придется хранить внутренние данные, поэтому давайте потребуем frontend для синхронизации звонков в backend. | |
Кроме того, обеспечивается поддержка промывки |
Как видите, публичный интерфейс бэкэнда довольно прост. Толькопотребляютиметоды смываназываются фронтендами. Функция«потребление»называется каждый раз, когда запись журнала проходит фильтрацию в интерфейсе. Запись, как было сказано ранее, содержит набор значений атрибутов и строку сообщения. Поскольку у нас нет необходимости в записи сообщения, мы пока будем игнорировать его. Но из других атрибутов мы можем извлечь статистические данные для накопления и записи в файл. Для этого мы можем использоватьатрибут ключевые словаизначение посещения.
BOOST_LOG_ATTRIBUTE_KEYWORD(sent, "Sent", unsigned int) BOOST_LOG_ATTRIBUTE_KEYWORD(received, "Received", unsigned int) // The function consumes the log records that come from the frontend void stat_collector::consume(logging::record_view const& rec) { // Accumulate statistical readings if (rec.attribute_values().count("Connected")) ++m_active_connections; else if (rec.attribute_values().count("Disconnected")) --m_active_connections; else { namespace phoenix = boost::phoenix; logging::visit(sent, rec, phoenix::ref(m_sent_bytes) += phoenix::placeholders::_1); logging::visit(received, rec, phoenix::ref(m_received_bytes) += phoenix::placeholders::_1); } ++m_collected_count; // Check if it's time to write the accumulated data to the file boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); if (now - m_last_store_time >= boost::posix_time::minutes(1)) { write_data(); m_last_store_time = now; } } // The function writes the collected data to the file void stat_collector::write_data() { m_csv_file << m_active_connections << ',' << m_sent_bytes << ',' << m_received_bytes << std::endl; reset_accumulators(); } // The function resets statistical accumulators to initial values void stat_collector::reset_accumulators() { m_sent_bytes = m_received_bytes = 0; m_collected_count = 0; }
Обратите внимание, что мы использовалиBoost.Phoenixдля автоматического генерирования объектов функции посетителя для значений атрибутов.
Последним битом реализации является методflush. Он используется для смывания всех буферизованных данных во внешнее хранилище, которое в нашем случае является файлом. Метод может быть реализован следующим образом:
// The function flushes the file void stat_collector::flush() { // Store any data that may have been collected since the list write to the file if (m_collected_count > 0) { write_data(); m_last_store_time = boost::posix_time::microsec_clock::universal_time(); } m_csv_file.flush(); }
Вы можете найти полный код этого примераздесь.
В качестве примера форматирования раковины бэкэнд, давайте реализуем раковину, которая будет отображать текстовые уведомления для каждой записи журнала, переданной ему.
![]() | Tip |
|---|---|
Приложения реального мира, вероятно, будут использовать некоторые API-инструменты GUI для отображения уведомлений, но программирование GUI выходит за рамки этой документации. Для отображения уведомлений мы используем внешнюю программу, которая делает именно это. В этом примере мы используем |
Определение бэкэнда очень похоже на то, что мы видели в предыдущем разделе:
// The backend starts an external application to display notifications class app_launcher : public sinks::basic_formatted_sink_backend< char,sinks::synchronized_feeding
> { public: // The function consumes the log records that come from the frontend void consume(logging::record_view const& rec, string_type const& command_line); };
Тип целевого персонажа | |
Чтобы не создавать слишком много экземпляров приложений, мы требуем, чтобы записи обрабатывались последовательно. |
Первое, что следует отметить, это то, чтоapp_launcherbackend происходит отbasic_formatted_sink_backend, а неbasic_sink_backend. Этот базовый класс принимает тип символа в дополнение к требованиям. Указанный тип символа определяет тип целевой строки, которую форматировщик будет составлять в интерфейсе, и он обычно соответствует базовому API, используемому бэкэндом для обработки записей. Следует отметить, что тип символа, который требуется бэкэнду, не связан с типами символов значений атрибутов строки, включая текст сообщения. Форматтер позаботится о преобразовании кода символов, когда это необходимо.
Вторым заметным отличием от предыдущих примеров является то, чтопотребительметод принимает дополнительный параметр строки помимо записи журнала. Это результат форматирования. Типstring_typeопределяется базовым классомbasic_formatted_sink_backendи соответствует запрашиваемому типу символов.
В этом примере нам не нужно промывать буферы, поэтому мы не указали требованиепромывкии опустили методпромывкив бэкэнде. Хотя нам не нужна никакая синхронизация в нашем бэкэнде, мы указалитребование синхронизированного_питания, чтобы мы не порождали несколько экземпляровуведомления-отправкипрограммы и не вызывали «вихтовую бомбу».
Теперьпотребляют.Реализация тривиальна:
// The function consumes the log records that come from the frontend void app_launcher::consume(logging::record_view const& rec, string_type const& command_line) { std::system(command_line.c_str()); }
Таким образом, отформатированная строка, как ожидается, будет командной строкой для запуска приложения. Точное название приложения и аргументы должны быть определены форматером. Этот подход добавляет гибкости, потому что бэкэнд может использоваться для различных целей, а обновление командной строки так же просто, как обновление форматтера.
Раковина может быть сконфигурирована со следующим кодом:
BOOST_LOG_ATTRIBUTE_KEYWORD(process_name, "ProcessName", std::string) BOOST_LOG_ATTRIBUTE_KEYWORD(caption, "Caption", std::string) // Custom severity level formatting function std::string severity_level_as_urgency( logging::value_ref< logging::trivial::severity_level, logging::trivial::tag::severity > const& level) { if (!level || level.get() == logging::trivial::info) return "normal"; logging::trivial::severity_level lvl = level.get(); if (lvl < logging::trivial::info) return "low"; else return "critical"; } // The function initializes the logging library void init_logging() { boost::shared_ptr< logging::core > core = logging::core::get(); typedef sinks::synchronous_sink< app_launcher > sink_t; boost::shared_ptr< sink_t > sink(new sink_t()); const std::pair< const char*, const char* > shell_decorations[] = { std::pair< const char*, const char* >("\"", "\\\""), std::pair< const char*, const char* >("$", "\\$"), std::pair< const char*, const char* >("!", "\\!") }; // Make the formatter generate the command line for notify-send sink->set_formatter ( expr::stream << "notify-send -t 2000 -u " << boost::phoenix::bind(&severity_level_as_urgency, logging::trivial::severity.or_none()) << expr::if_(expr::has_attr(process_name)) [ expr::stream << " -a '" << process_name << "'" ] << expr::if_(expr::has_attr(caption)) [ expr::stream << " \"" << expr::char_decor(shell_decorations)[ expr::stream << caption ] << "\"" ] << " \"" << expr::char_decor(shell_decorations)[ expr::stream << expr::message ] << "\"" ); core->add_sink(sink); // Add attributes that we will use core->add_global_attribute("ProcessName", attrs::current_process_name()); }
Самая интересная часть - установка раковины.синхронный_sinkfrontend (как и любой другой frontend) обнаружит, чтоapp_launcherbackend требует форматирования и включения соответствующей функциональности. Способset_formatterстановится доступным и может быть использован для установки выражения форматирования, которое составляет командную строку для запускауведомления-отправкипрограммы. Мы использовалиатрибутные ключевые словадля идентификации конкретных значений атрибутов в форматировщике. Обратите внимание, что значения атрибутов строки должны быть предварительно обработаны так, чтобы специальные символы, интерпретируемые оболочкой, ускользали в командной строке. Мы достигаем этого с помощьюchar_decorдекоратора с нашей собственной картой замены. После настройки раковины мы также добавляем к ядру атрибуттекущего процесса, чтобы нам не приходилось добавлять его к каждой записи.
После всего этого мы можем, наконец, отобразить некоторые уведомления:
void test_notifications() { BOOST_LOG_TRIVIAL(debug) << "Hello, it's a simple notification"; BOOST_LOG_TRIVIAL(info) << logging::add_value(caption, "Caption text") << "And this notification has caption as well"; }
Статья Extending the library раздела Chapter 1. Boost.Log v2 Chapter 1. Boost.Log v2 может быть полезна для разработчиков на c++ и boost.
:: Главная :: Chapter 1. Boost.Log v2 ::
реклама |