Вы можете расширить библиотеку, разработав свои собственные источники и, если на то пошло, способы сбора данных журнала. В принципе, у вас есть два варианта, как начать: вы можете либо разработать новую функцию регистратора, либо разработать совершенно новый тип источника. Первый подход хорош, если все, что вам нужно, это настроить функциональность существующих регистраторов. Второй подход обоснован, если весь механизм сбора бревен предоставленными лесозаготовителями не подходит для ваших нужд.
Каждый регистратор, предоставляемый библиотекой, состоит из ряда функций, которые можно комбинировать друг с другом. Каждая функция отвечает за один и независимый аспект функциональности регистратора. Например, регистраторы, которые предоставляют возможность назначать уровни тяжести для записей журналирования, включают функцию severity. Вы можете реализовать свою функцию и использовать ее вместе с теми, которые предоставляет библиотека.
Функция регистратора должна соответствовать этим основным требованиям:
Функция регистрации должна быть шаблоном класса. Он должен иметь по крайней мере один тип параметров шаблона (назовем его BaseT).
Функция должна публично выводиться из параметра шаблона BaseT.
Функция должна быть по умолчанию конструктивной и копируемой.
Функция должна быть конструируемой с одним аргументом шаблонного типа. Функция может не использовать сам этот аргумент, но она должна передать этот аргумент конструктору BaseT.
Эти требования позволяют составлять регистратор из ряда признаков, полученных друг от друга. Корневым классом иерархии функций будет экземпляр шаблона класса basic_logger. Этот класс реализует большинство основных функциональных возможностей регистраторов, таких как хранение специфических атрибутов регистратора и предоставление интерфейса для форматирования сообщений журнала. Композиция иерархии выполняется шаблоном класса basic_composite_logger, который инстанцируется на последовательности признаков (не волнуйтесь, это будет показано в примере через несколько мгновений). Конструктор с шаблонным аргументом позволяет инициализировать функции с названными параметрами, используя библиотеку Boost.Parameter.
Функция регистрации может также содержать внутренние данные. В этом случае для поддержания безопасности резьбы для регистратора функция должна следовать этим дополнительным рекомендациям:
Обычно нет необходимости вводить в каждую функцию мутекс или другой механизм синхронизации. Кроме того, рекомендуется не делать этого, потому что одна и та же функция может использоваться как в нитей-безопасных, так и не нитей-безопасных лесозаготовителей. Вместо этого функции должны использовать резьбовую модель регистратора как примитивную синхронизацию, аналогичную тому, как они будут использовать mutex. Модель резьбы доступна через метод get_threading_model, определенный в шаблоне класса basic_logger.
Общественные методы, которые в конечном итоге называют эти методы, реализуются
basic_composite_logger
шаблон класса. Эти реализации делают необходимые блокировки
_unlocked
метод базовых особенностей.
.
The public methods that eventually call these methods are implemented
by the basic_composite_logger
class template. These implementations do the necessary locking
and then pass control to the corresponding _unlocked
method of the base features.
[ORIG_END] -->
Требования безопасности резьбы для этих методов выражены типами блокировки. Эти типы доступны в виде типдефов в каждой функции и шаблона класса basic_logger. Если функция показывает защищенную функцию foo_unlocked, она также выдаст тип foo_lock, который будет выражать требования к блокировке foo_unlocked. Соответствующий метод foo в шаблоне класса basic_composite_logger будет использовать этот типдеф для блокировки модели резьбы перед вызовом foo_unlocked.
Конструкторы функций не нуждаются в блокировке, и поэтому для них нет необходимости в типах блокировок.
Функция может реализовать конструктор копий. Аргумент конструктора уже запирается общим замком, когда конструктор вызывается. Естественно, ожидается, что функция перенаправит вызов конструктора копий в класс BaseT.
Функция не должна реализовывать оператора назначения. Задание будет автоматически предоставлено экземпляром класса basic_composite_logger. Однако функция может предоставлять метод swap_unlocked, который будет менять содержимое этой функции и аргумент метода, и вызывать аналогичный метод в классе BaseT. Автоматически генерируемый оператор присваивания будет использовать этот метод вместе с конструктором копий.
Чтобы проиллюстрировать все эти длинные рекомендации, давайте реализуем простую функцию регистратора. Предположим, мы хотим, чтобы наш регистратор мог пометить отдельные записи журнала. Другими словами, регистратор должен временно добавить атрибут к своему набору атрибутов, испустить запись регистрации, а затем автоматически удалить атрибут. Несколько похожая функциональность может быть достигнута с помощью расширенных атрибутов, хотя синтаксис может усложнить обертывание его в аккуратный макрос:
// We want something equivalent to this{BOOST_LOG_SCOPED_LOGGER_TAG(logger,"Tag","[GUI]");BOOST_LOG(logger)<<"The user has confirmed his choice";}
Давайте объявим нашу функцию регистратора:
template<typenameBaseT>classrecord_tagger_feature:publicBaseT{public:// Let's import some types that we will need. These imports should be public,// in order to allow other features that may derive from record_tagger to do the same.typedeftypenameBaseT::char_typechar_type;typedeftypenameBaseT::threading_modelthreading_model;public:// Default constructor. Initializes m_Tag to an invalid value.record_tagger_feature();// Copy constructor. Initializes m_Tag to a value, equivalent to that.m_Tag.record_tagger_feature(record_tagger_featureconst&that);// Forwarding constructor with named parameterstemplate<typenameArgsT>record_tagger_feature(ArgsTconst&args);// The method will require locking, so we have to define locking requirements for it.// We use the strictest_lock trait in order to choose the most restricting lock type.typedeftypenamelogging::strictest_lock<boost::lock_guard<threading_model>,typenameBaseT::open_record_lock,typenameBaseT::add_attribute_lock,typenameBaseT::remove_attribute_lock>::typeopen_record_lock;protected:// Lock-less implementation of operationstemplate<typenameArgsT>logging::recordopen_record_unlocked(ArgsTconst&args);};// A convenience metafunction to specify the feature// in the list of features of the final logger laterstructrecord_tagger:publicboost::mpl::quote1<record_tagger_feature>{};
функция должна быть получена из других функций или класса basic_logger
Вы можете видеть, что мы используем шаблон strictest_lock, чтобы определить типы блокировок, которые удовлетворяют требованиям безопасности потока базового класса для методов, которые должны быть вызваны из соответствующих методов record_tagger_feature. Определение open_record_lock показывает, что реализация open_record_unlocked для функции record_tagger_feature требует эксклюзивной блокировки (которой является lock_guard) для регистратора, но также учитывает требования к блокировке open_record_unlocked, add_attribute_unlocked способов базового класса, поскольку для этого придется их вызывать. Сгенерированный метод open_record конечного класса регистратора будет использовать этот типдеф для автоматического получения соответствующего типа блокировки перед пересылкой на методы open_record_unlocked.
На самом деле, в этом конкретном примере не было необходимости использовать черту strictest_lock, потому что все наши методы требуют исключительной блокировки, которая уже является самой строгой. Тем не менее, этот шаблон может пригодиться, если вы используете общую блокировку.
Реализация публичного интерфейса становится довольно тривиальной:
Теперь, поскольку вся блокировка извлекается в общедоступный интерфейс, у нас есть большая часть нашей логики функций, которая будет реализована в защищенной части интерфейса. Чтобы установить значение тега в регистраторе, нам нужно будет ввести новое ключевое слово Boost.Parameter. Следуя рекомендациям из этой библиотечной документации, лучше ввести ключевое слово в специальное пространство имен:
Открытие нового альбома может выглядеть примерно так:
template<typenameBaseT>template<typenameArgsT>logging::recordrecord_tagger_feature<BaseT>::open_record_unlocked(ArgsTconst&args){// Extract the named argument from the parameters packstd::stringtag_value=args[my_keywords::tag|std::string()];logging::attribute_set&attrs=BaseT::attributes();logging::attribute_set::iteratortag=attrs.end();if(!tag_value.empty()){// Add the tag as a new attributestd::pair<logging::attribute_set::iterator,bool>res=BaseT::add_attribute_unlocked("Tag",attrs::constant<std::string>(tag_value));if(res.second)tag=res.first;}// In any case, after opening a record remove the tag from the attributesBOOST_SCOPE_EXIT_TPL((&tag)(&attrs)){if(tag!=attrs.end())attrs.erase(tag);}BOOST_SCOPE_EXIT_END// Forward the call to the base featurereturnBaseT::open_record_unlocked(args);}
Здесь мы добавляем новый атрибут со значением тега, если он указан в вызове open_record. Когда запись журнала открывается, все значения атрибутов приобретаются и блокируются после записи, поэтому мы удаляем тег из набора атрибутов с блоком Boost.ScopeExit.
Хорошо, у нас есть функция, и пришло время ввести ее в лесоруб. Предположим, что мы хотим объединить его со стандартным уровнем жесткости. Нет проблем:
template<typenameLevelT=int>classmy_logger:publicsrc::basic_composite_logger<char,my_logger<LevelT>,src::single_thread_model,src::features<src::severity<LevelT>,record_tagger>>{// The following line will automatically generate forwarding constructors that// will call to the corresponding constructors of the base classBOOST_LOG_FORWARD_LOGGER_MEMBERS_TEMPLATE(my_logger)};
тип символа для лесоруба
окончательный тип лесозаготовителя
регистратор не выполняет синхронизацию потоков; используйте multi_thread_model для объявления регистратора
список функций, которые мы хотим объединить
Как видите, создание лесозаготовителя – довольно простая процедура. Макро BOOST_LOG_FORWARD_LOGGER_MEMBERS_TEMPLATE, которое вы видите здесь, просто для удобства: оно разворачивается в конструктор по умолчанию, конструктор копий, оператор назначения и ряд конструкторов для поддержки названных аргументов. Для нешаблонных лесорубов существует аналогичная BOOST_LOG_FORWARD_LOGGER_MEMBERS macro.
Предполагая, что мы определили уровни тяжести, как это:
enumseverity_level{normal,warning,error};
теперь мы можем использовать наш регистратор следующим образом:
voidmanual_logging(){my_logger<severity_level>logger;logging::recordrec=logger.open_record((keywords::severity=normal,my_keywords::tag="GUI"));if(rec){logging::record_ostreamstrm(rec);strm<<"The user has confirmed his choice";strm.flush();logger.push_record(boost::move(rec));}}
Вся эта вербальность обычно не требуется. Можно определить специальный макрос, чтобы сделать код более лаконичным:
#defineLOG_WITH_TAG(lg,sev,tg)\BOOST_LOG_WITH_PARAMS((lg),(keywords::severity=(sev))(my_keywords::tag=(tg)))voidlogging_function(){my_logger<severity_level>logger;LOG_WITH_TAG(logger,normal,"GUI")<<"The user has confirmed his choice";}
В общем, вы можете внедрять новые источники журналирования так, как вам нравится, библиотека не предписывает никаких требований к проектированию источников журналов. Тем не менее, есть некоторые заметки о том, как источники журналов должны взаимодействовать с ядром журналирования.
Всякий раз, когда источник регистрации готов испустить запись журнала, он должен называть open_record в ядре. В этот призыв должны быть переданы специфические атрибуты источника. Во время этого вызова ядро выделяет ресурсы для записи и выполняет фильтрацию.
Если вызов open_record вернул действительную запись журнала, то запись прошла фильтрацию и считается открытой. Запись может быть позже подтверждена источником путем последующего вызова push_record или удалена путем ее уничтожения.
Если вызов open_record вернул недействительную (пустую) запись журнала, это означает, что запись не была открыта (скорее всего, из-за отказа фильтрации). В этом случае ядро регистрации не содержит никаких ресурсов, связанных с записью, и поэтому источник не должен вызывать push_record для этой конкретной попытки регистрации.
Впоследствии источник может открыть более одной записи. Открытые журнальные записи существуют независимо друг от друга.
Статья Writing your own sources раздела Chapter 1. Boost.Log v2 Extending the library может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.