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

Utilities

Boost , Chapter 1. Boost.Log v2 , Detailed features description

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
#include <boost/log/utility/string_literal.hpp>

Струнные буквы используются в нескольких местах по всей библиотеке. Однако этот компонент может быть успешно использован за пределами библиотеки в коде пользователей. Он является только заголовком и не требует связывания с бинарной библиотекой. Буквы строк могут значительно улучшить производительность, если нет необходимости изменять сохраненные строки. Что также важно, так как струнные буквы не динамически распределяют память, легче поддерживать безопасность исключений при использовании струнных букв вместо обычных струн.

Функциональность реализована в шаблоне класса<basic_string_literal>, который параметризирован характером и чертами характера, похожими на<std::basic_string>. Предусмотрены также два удобных типа:<string_literal>и<wstring_literal>для узких и широких типов символов соответственно. Для того, чтобы облегчить конструкцию строки в общем коде, существует также шаблон<str_literal>функции, который принимает строку буквальную и возвращает экземпляр<basic_string_literal>для соответствующего типа символа.

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

[Note]Note

Этот компонент является устаревшим и будет удален в будущих выпусках.Boost.TypeIndexВ качестве замены.

#include <boost/log/utility/type_info_wrapper.hpp>

Языковая поддержка информации о типе времени выполнения имеет важное значение для библиотеки. Частично из-за ограничений, связанных с C++. Стандарт накладывает на эту функцию, отчасти из-за различий в реализации разных компиляторов возникла необходимость в легкой обертке вокруг класса<std::type_info>для заполнения пробелов. В таблице ниже кратко показаны различия между<std::type_info>и<type_info_wrapper>классами.

Table 1.6. Type information classes comparison

Особенность

<std::type_info>

<type_info_wrapper>

Разрешается по умолчанию

Нет

Да. По умолчанию построенная обертка находится в пустом состоянии.

Является копируемым

Нет

Да

Применяется

.

Нет

Да

Меняется

Нет

Да

Продолжительность жизни

Статический, до окончания подачи заявки

Динамический

Имеет пустое состояние

Нет

Да. В пустом состоянии обертка информации типа никогда не равна другим оберткам информации не пустого типа. Он равен другим оберткам с информацией пустого типа и может быть заказан с ними.

Поддерживает сравнение равенства

Да

Да

Поддерживает заказ

Да, частичный заказ с полным набором операторов сравнения. Семантика упорядочения аналогична методу<std::type_info::before>.

Поддерживает печатное представление типа

Да, с функцией<name>.

Да, с функцией<pretty_name>. Функция делает все возможное, чтобы напечатать имя типа в человекочитаемом виде. На некоторых платформах это лучше, чем<std::type_info::name>

.

Учитывая вышеперечисленные различия, использование типовых информационных объектов становится намного проще. Например, возможность копирования и заказа у обычных операторов позволяет использовать<type_info_wrapper>с контейнерами. Возможность построения и назначения по умолчанию позволяет использовать информацию типа в качестве обычного объекта и не прибегать к указателям, которые могут быть небезопасными.

#include <boost/log/utility/type_dispatch/type_dispatcher.hpp>

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

Каждый тип диспетчера поддерживает интерфейс<type_dispatcher>. Когда необходимо извлечь значение атрибута, этот интерфейс передается объекту значения атрибута, который затем пытается получить обратный вызов для фактического типа значения. Все обратные вызовы являются объектами шаблона класса [class_type_dispatcher_callback], инстанцированного на фактическом типе значения. Если диспетчер может потреблять значение запрашиваемого типа, он должен вернуть непустый объект обратного вызова. Когда (и если) получен соответствующий обратный вызов, объект стоимости атрибута должен только передать содержащуюся стоимость своему<operator ()>.

К счастью, нет необходимости писать тип диспетчеров с нуля. Библиотека предоставляет два типа диспетчеров, которые реализуют интерфейсы<type_dispatcher>и [class_type_dispatcher_callback] и инкапсулируют поиск в обратном вызове.

Static type dispatcher
#include <boost/log/utility/type_dispatch/static_type_dispatcher.hpp>

Диспетчеры статического типа используются, когда набор типов, которые необходимо поддерживать для извлечения, известен во время компиляции. Шаблон класса<static_type_dispatcher>параметризован с последовательностью типов MPL, которые необходимо поддерживать. Диспетчер наследует от интерфейса<type_dispatcher>, который предоставляет метод<get_callback>для получения объекта функции для вызова на сохраненное значение. Все, что вам нужно сделать, это предоставить объект функции посетителя диспетчеру в точке строительства и вызвать обратный вызов при отправке сохраненного значения:

// Base interface for the custom opaque value
struct my_value_base
{
    virtual ~my_value_base() {}
    virtual bool dispatch(logging::type_dispatcher& dispatcher) const = 0;
};
// A simple attribute value
template< typename T >
struct my_value :
    public my_value_base
{
    T m_value;
    explicit my_value(T const& value) : m_value(value) {}
    // The function passes the contained type into the dispatcher
    bool dispatch(logging::type_dispatcher& dispatcher) const
    {
        logging::type_dispatcher::callback< T > cb = dispatcher.get_callback< T >();
        if (cb)
        {
            cb(m_value);
            return true;
        }
        else
            return false;
    }
};
// Value visitor for the supported types
struct print_visitor
{
    typedef void result_type;
    // Implement visitation logic for all supported types
    void operator() (int const& value) const
    {
        std::cout << "Received int value = " << value << std::endl;
    }
    void operator() (double const& value) const
    {
        std::cout << "Received double value = " << value << std::endl;
    }
    void operator() (std::string const& value) const
    {
        std::cout << "Received string value = " << value << std::endl;
    }
};
// Prints the supplied value
bool print(my_value_base const& val)
{
    typedef boost::mpl::vector< int, double, std::string > types;
    print_visitor visitor;
    logging::static_type_dispatcher< types > disp(visitor);
    return val.dispatch(disp);
}

See the complete code.

Dynamic type dispatcher
#include <boost/log/utility/type_dispatch/dynamic_type_dispatcher.hpp>

Если набор типов, которые должны поддерживаться, не доступен во время компиляции, класс<dynamic_type_dispatcher>должен помочь. Можно использовать метод<register_type>для добавления поддержки для конкретного типа. Пользователь должен передать объект функции вместе с типом, этот функтор будет вызываться при вызове посетителя для указанного типа. Учитывая, что<my_value>из образца кода для диспетчера статического типа не поврежден, код может быть переписан следующим образом:

// Visitor functions for the supported types
void on_int(int const& value)
{
    std::cout << "Received int value = " << value << std::endl;
}
void on_double(double const& value)
{
    std::cout << "Received double value = " << value << std::endl;
}
void on_string(std::string const& value)
{
    std::cout << "Received string value = " << value << std::endl;
}
logging::dynamic_type_dispatcher disp;
// The function initializes the dispatcher object
void init_disp()
{
    // Register type visitors
    disp.register_type< int >(&on_int);
    disp.register_type< double >(&on_double);
    disp.register_type< std::string >(&on_string);
}
// Prints the supplied value
bool print(my_value_base const& val)
{
    return val.dispatch(disp);
}

See the complete code.

Конечно, также поддерживаются сложные функциональные объекты, подобные тем, которые предоставляютсяBoost.Bind.

#include <boost/log/utility/type_dispatch/standard_types.hpp>
#include <boost/log/utility/type_dispatch/date_time_types.hpp>

Можно заметить, что при использовании диспетчеров типов и определения фильтров и формататоров может быть удобно иметь некоторые заранее определенные последовательности типов для обозначения часто используемых наборов типов. Библиотека предоставляет несколько таких наборов.

Table 1.7. Standard types (standard_types.hpp)

Последовательность типов

значение

<integral_types>

Все интегральные типы, включая<bool>, символы и 64-битные интегральные типы, если таковые имеются

<floating_point_types>

Типы плавающих точек

<numeric_types>

Включает<integral_types>и<floating_point_types>

<string_types>

Узкие и широкие струнные типы. В настоящее время включает только типы строк STL истроковые буквы.


Существует также ряд последовательностей типов, связанных со временем:

Table 1.8. Time-related types (date_time_types.hpp)

Последовательность типов

значение

<native_date_time_types>

Все типы, определенные в стандарте C/C++, которые имеют как дату, так и время

<boost_date_time_types>

Все типы, определенные вBoost.DateTime, которые имеют как дату, так и время

<date_time_types>

<native_date_types>

Все типы, определенные в стандарте C/C++, которые имеют часть даты. В настоящее время эквивалентно<native_date_time_types>

.

<boost_date_types>

<date_types>

Включает<native_date_types>и<boost_date_types>

<native_time_types>

Все типы, определенные в стандарте C/C++, которые имеют временную часть. В настоящее время эквивалентно<native_date_time_types>

.

<boost_time_types>

Все типы, определенные вBoost.DateTime, которые имеют временную часть. В настоящее время эквивалентно<boost_date_time_types>

.

<time_types>

Включает<native_time_types>и<boost_time_types>

native_time_duration_types

Все типы, определенные в стандарте C/C++, которые используются для представления длительности времени. В настоящее время только включает<double>, как результат тип<difftime>стандартной функции.

<boost_time_duration_types>

Все типы длительности времени, определенные вУскорение.Дата

<time_duration_types>

Включает<native_time_duration_types>и<boost_time_duration_types>

<boost_time_period_types>

Все типы длительности времени, определенные вУскорение.Дата

<time_period_types>

В настоящее время эквивалентно<boost_time_period_types>


#include <boost/log/utility/value_ref.hpp>

Шаблон класса<value_ref>является дополнительной оберткой, которая используется библиотекой для ссылки на сохраненные значения атрибутов. В определенной степени он разделяет характеристики компонентовBoost.OptionalиBoost.Variant.

Шаблон имеет два типа параметров. Первый – это упомянутый тип. Он также может быть указан как последовательность типаBoost.MPL, и в этом случае обертка<value_ref>может относиться к любому типу в последовательности. В этом случае метод<which>возвращает индекс указанного типа в последовательности. Второй параметр шаблона - это необязательный тип тега, который можно использовать для настройки поведения форматирования. Этот знак передается.<to_log>манипулятор, когда обертку помещают в<basic_formatting_ostream>поток, который используется библиотекой для форматирования записей. Например, посмотрите, как осуществляется извлечение значения атрибута:

void print_value_multiple_types(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;
    // Extract a reference to the stored value
    logging::value_ref< types > val = logging::extract< types >(attr);
    // Check the result
    if (val)
    {
        std::cout << "Extraction succeeded" << std::endl;
        switch (val.which())
        {
        case 0:
            std::cout << "int: " << val.get< int >() << std::endl;
            break;
        case 1:
            std::cout << "string: " << val.get< std::string >() << std::endl;
            break;
        }
    }
    else
        std::cout << "Extraction failed" << std::endl;
}

См. полный код.

Обертка<value_ref>также поддерживает применение объекта функции посетителя к указанному объекту. Это можно сделать, назвав один из следующих методов:

  • <apply_visitor>. Этот метод должен использоваться только на действительной (не пустой) ссылке. Метод возвращает посетителю результат.
  • <apply_visitor_optional>. Метод проверяет, является ли ссылка действительной и применяет посетителя к указанному значению, если это так. Метод возвращает результат посетителя, завернутый в<boost::optional>, который будет заполнен только в том случае, если ссылка действительна.
  • <apply_visitor_or_default>Если ссылка действительна, метод применяет посетителя к указанному значению и возвращает его результат. В противном случае метод возвращает значение по умолчанию, переданное в качестве второго аргумента.
[Note]Note

Независимо от используемого метода, объект функции посетителядолженопределить типдеф<result_type>. Полиморфные посетители не поддерживаются, так как это слишком усложняет интерфейс<value_ref>. Это требование также исключает использование бесплатных функций и функций лямбды C++11 в качестве посетителей. Пожалуйста, используйтеBoost.Bindили аналогичные обертки в таких случаях.

Вот пример применения посетителя:

struct hash_visitor
{
    typedef std::size_t result_type;
    result_type operator() (int val) const
    {
        std::size_t h = val;
        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }
    result_type operator() (std::string const& val) const
    {
        std::size_t h = 0;
        for (std::string::const_iterator it = val.begin(), end = val.end(); it != end; ++it)
            h += *it;
        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }
};
void hash_value(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;
    // Extract the stored value
    logging::value_ref< types > val = logging::extract< types >(attr);
    // Check the result
    if (val)
        std::cout << "Extraction succeeded, hash value: " << val.apply_visitor(hash_visitor()) << std::endl;
    else
        std::cout << "Extraction failed" << std::endl;
}

#include <boost/log/utility/record_ordering.hpp>

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

Abstract record ordering

Класс<abstract_ordering>позволяет применять быстрый непрозрачный порядок. Результат этого упорядочивания не является стабильным между различными прогонами приложения и в целом не может быть предсказан до применения предиката, однако он обеспечивает наилучшую производительность. Класс<abstract_ordering>является шаблоном, который специализируется на опциональной функции предиката, которая сможет сравнивать указатели<const void*>. По умолчанию используется эквивалент<std::less>.

// A set of unique records
std::set< logging::record_view, logging::abstract_ordering< > > m_Records;

Этот тип заказа может быть полезен, если конкретный порядок записей журнала не важен, но тем не менее требуется некоторый порядок.

Attribute value based ordering

Этот вид упорядочения реализован с классом<attribute_value_ordering>и основан на значениях атрибутов, прикрепленных к записи. Предикат будет искать значение атрибута с заданным именем в обеих записях и пытаться сравнить значения атрибута.

// Ordering type definition
typedef logging::attribute_value_ordering<
    int     // attribute value type
> ordering;
// Records organized into a queue based on the "Severity" attribute value
std::priority_queue<
    logging::record_view,
    std::vector< logging::record_view >,
    ordering
> m_Records(ordering("Severity"));

Как и<abstract_ordering>,<attribute_value_ordering>также принимает второй необязательный параметр шаблона, который должен быть предикатом для сравнения значений атрибутов<int>s в примере выше. По умолчанию используется эквивалент<std::less>.

Вы также можете использовать функцию генератора<make_attr_ordering>для автоматического создания экземпляра<attribute_value_ordering>на основе имени значения атрибута и функции заказа. Это может быть полезно, если функция упорядочения имеет нетривиальный тип, как те, которые предоставляетBoost.Bind.

#include <boost/log/utility/exception_handler.hpp>

Библиотека предоставляет крючки для обработки исключений в разных местах. Инструменты, определенные в этом заголовке, обеспечивают простой способ реализации функциональных объектов, подходящих для таких крючков.

Обработчик исключений — это объект функции, который не принимает аргументов. Результат обработчика исключений игнорируется и, следовательно, обычно должен быть<void>. Обработчики исключений вызываются библиотекой из разделов<catch>, поэтому для того, чтобы вновь получить объект исключения, он должен его перебросить. Заголовок определяет функтор шаблона<exception_handler>, который делает именно это, а затем перенаправляет объект исключения в унарный функциональный объект, определяемый пользователем. Функция<make_exception_handler>может быть использована для упрощения конструкции обработчика. Все ожидаемые типы исключений должны быть четко указаны в вызове, в том порядке, в котором они появятся в разделах<catch>(т.е. от наиболее конкретных до наиболее общих).

struct my_handler
{
    typedef void result_type;
    void operator() (std::runtime_error const& e) const
    {
        std::cout << "std::runtime_error: " << e.what() << std::endl;
    }
    void operator() (std::logic_error const& e) const
    {
        std::cout << "std::logic_error: " << e.what() << std::endl;
        throw;
    }
};
void init_exception_handler()
{
    // Setup a global exception handler that will call my_handler::operator()
    // for the specified exception types
    logging::core::get()->set_exception_handler(logging::make_exception_handler<
        std::runtime_error,
        std::logic_error
    >(my_handler()));
}

Как вы можете видеть, вы можете либо подавить исключение, вернувшись обычно из<operator()>в функторе обработчика, определяемом пользователем, либо отбросить исключение, и в этом случае оно будет распространяться дальше. Если окажется, что обработчик исключений вызывается для типа исключения, который не может быть пойман любым из указанных типов, исключение будет распространяться без какой-либо обработки. Для того, чтобы ловить такие ситуации, существует класс<nothrow_exception_handler>. Он вызывает определяемый пользователем функтор без аргументов, если он не может определить тип исключения.

struct my_handler_nothrow
{
    typedef void result_type;
    void operator() (std::runtime_error const& e) const
    {
        std::cout << "std::runtime_error: " << e.what() << std::endl;
    }
    void operator() (std::logic_error const& e) const
    {
        std::cout << "std::logic_error: " << e.what() << std::endl;
        throw;
    }
    void operator() () const
    {
        std::cout << "unknown exception" << std::endl;
    }
};
void init_exception_handler_nothrow()
{
    // Setup a global exception handler that will call my_handler::operator()
    // for the specified exception types. Note the std::nothrow argument that
    // specifies that all other exceptions should also be passed to the functor.
    logging::core::get()->set_exception_handler(logging::make_exception_handler<
        std::runtime_error,
        std::logic_error
    >(my_handler_nothrow(), std::nothrow));
}

Иногда бывает удобно полностью подавить все исключения на определенном библиотечном уровне. Функция<make_exception_suppressor>создает обработчика исключений, который просто ничего не делает при поимке исключения. Например, таким образом мы можем отключить все исключения из библиотеки регистрации:

void init_logging()
{
    boost::shared_ptr< logging::core > core = logging::core::get();
    // Disable all exceptions
    core->set_exception_handler(logging::make_exception_suppressor());
}

Библиотека предоставляет ряд потоковых манипуляторов, которые могут быть полезны в некоторых контекстах.

#include <boost/log/utility/manipulators/to_log.hpp>

Функция<to_log>создает манипулятор потока, который просто выводит принятое значение в поток. По умолчанию его поведение эквивалентно простому размещению значения в потоке. Однако пользователь может перегрузить<operator<<>для принятого значения, чтобы переопределить поведение форматирования, когда значения отформатированы для целей регистрации. Это обычно желательно, когда обычный<operator<<>используется для других задач (таких как сериализация), и его поведение не подходит для регистрации и не может быть легко изменено. Например:

std::ostream& operator<<
(
    std::ostream& strm,
    logging::to_log_manip< int > const& manip
)
{
    strm << std::setw(4) << std::setfill('0') << std::hex << manip.get() << std::dec;
    return strm;
}
void test_manip()
{
    std::cout << "Regular output: " << 1010 << std::endl;
    std::cout << "Log output: " << logging::to_log(1010) << std::endl;
}

Второе заявление о потоковой передаче в функции<test_manip>будет ссылаться на нашего оператора ввода потока, который определяет специальные правила форматирования.

Также можно определить различные правила форматирования для разных контекстов значений. Библиотека использует эту функцию, чтобы разрешить различное форматирование для разных значений атрибутов, даже если тип сохраненного значения одинаков. Для этого необходимо указать явный аргумент шаблона<to_log>, тип тега, который будет встроен в тип манипулятора и, таким образом, позволит определить различные операторы вставки:

struct tag_A;
struct tag_B;
std::ostream& operator<<
(
    std::ostream& strm,
    logging::to_log_manip< int, tag_A > const& manip
)
{
    strm << "A[" << manip.get() << "]";
    return strm;
}
std::ostream& operator<<
(
    std::ostream& strm,
    logging::to_log_manip< int, tag_B > const& manip
)
{
    strm << "B[" << manip.get() << "]";
    return strm;
}
void test_manip_with_tag()
{
    std::cout << "Regular output: " << 1010 << std::endl;
    std::cout << "Log output A: " << logging::to_log< tag_A >(1010) << std::endl;
    std::cout << "Log output B: " << logging::to_log< tag_B >(1010) << std::endl;
}

См. полный код.

[Note]Note

Библиотека использует<basic_formatting_ostream>тип потока для форматирования записей, поэтому при настройке правил форматирования значений атрибутов<operator<<>необходимо использовать<basic_formatting_ostream>вместо<std::ostream>.

#include <boost/log/utility/manipulators/add_value.hpp>

Функция<add_value>создает манипулятор, который прикрепляет значение атрибута к записи журнала. Этот манипулятор может использоваться только в потоковых выражениях с типом потока<basic_record_ostream>(что имеет место при форматировании сообщения записи журнала). Поскольку текст сообщения отформатирован только после фильтрации, значения атрибутов, прилагаемые к этому манипулятору, не влияют на фильтрацию и могут использоваться только в форматировщиках и поглотителях.

В дополнение к самому значению, манипулятор также требует, чтобы имя атрибута было предоставлено. Например:

// Creates a log record with attribute value "MyAttr" of type int attached
BOOST_LOG(lg) << logging::add_value("MyAttr", 10) << "Hello world!";
#include <boost/log/utility/manipulators/dump.hpp>

Функция<dump>создает манипулятор, который выводит двоичное содержимое смежных областей памяти. Это может быть полезно для регистрации некоторых двоичных данных низкого уровня, таких как закодированные сетевые пакеты или записи двоичного файла. Использование довольно простое:

void on_receive(std::vector< unsigned char > const& packet)
{
    // Outputs something like "Packet received: 00 01 02 0a 0b 0c"
    BOOST_LOG(lg) << "Packet received: " << logging::dump(packet.data(), packet.size());
}

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

void on_receive(std::vector< unsigned char > const& packet)
{
    // Outputs something like "Packet received: 00 01 02 03 04 05 06 07 and 67 bytes more"
    BOOST_LOG(lg) << "Packet received: " << logging::dump(packet.data(), packet.size(), 8);
}

Существует еще один манипулятор под названием<dump_elements>для печати двоичного представления небайтовых элементов массива. Специальный манипулятор для этого случая необходим, потому что единицы аргумента размера<dump>могут сбивать с толку (в байтах или в элементах?). Поэтому<dump>не будет компилироваться при использовании для небайтовых входных данных.<dump_elements>принимает одни и те же аргументы, а аргументы, связанные с размером, всегда определяют количество элементов для обработки.

void process(std::vector< double > const& matrix)
{
    // Note that dump_elements accepts the number of elements in the matrix, not its size in bytes
    BOOST_LOG(lg) << "Matrix dump: " << logging::dump_elements(matrix.data(), matrix.size());
}
[Tip]Tip

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

#include <boost/log/utility/ipc/object_name.hpp>

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

Имя объекта может быть построено из идентификатора строки UTF-8 и области. Портативный идентификатор может содержать следующие символы:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 . _ -
[Note]Note

Набор символов соответствуетPOSIX Portable Filename Character Set.

Использование других символов может привести к непортативному системному поведению.

Сфера охвата определяется перечислением<object_name::scope>:

  • <global>- Название имеет глобальный охват; любой процесс в системе имеет потенциал для открытия ресурса, идентифицированного по названию. В некоторых системах эта область может быть недоступна или требовать повышенных привилегий.
  • <user>- Название ограничено процессами, выполняемыми под текущим пользователем.
  • <session>- Название ограничено процессами, выполняемыми в текущем сеансе входа в систему.
  • <process_group>- Название ограничено процессами, выполняемыми в текущей группе процессов. В настоящее время в Windows все процессы, выполняемые в текущей сессии, считаются членами одной и той же группы процессов. Это может измениться в будущем.

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

[Warning]Warning

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

#include <boost/log/utility/ipc/reliable_message_queue.hpp>

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

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

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

  • <block_on_overflow>- Блокируйте нить до тех пор, пока не будет достаточно места для очереди сообщения или операция не будет прервана вызовом<stop_local>.
  • <fail_on_overflow>- Возврат кода ошибки из операции отправки. Код ошибки<operation_result::no_space>.
  • <throw_on_overflow>- Выбросить исключение из операции отправки. Исключение составляет<capacity_limit_reached>.

Политика носит объектно-локальный характер, т.е. разные авторы и читатели могут иметь разную политику переполнения. Вот пример записи в очередь сообщений:

int main()
{
    typedef logging::ipc::reliable_message_queue queue_t;
    // Create a message_queue_type object that is associated with the interprocess
    // message queue named "ipc_message_queue".
    queue_t queue
    (
        keywords::name = logging::ipc::object_name(logging::ipc::object_name::user, "ipc_message_queue"),
        keywords::open_mode = logging::open_mode::open_or_create,  1
        keywords::capacity = 256,                                  2
        keywords::block_size = 1024,                               3
        keywords::overflow_policy = queue_t::fail_on_overflow      4
    );
    // Send a message through the queue
    std::string message = "Hello, Viewer!";
    queue_t::operation_result result = queue.send(message.data(), message.size());
    // See if the message was sent
    switch (result)
    {
    case queue_t::operation_result::succeeded:
        std::cout << "Message sent successfully" << std::endl;
        break;
    case queue_t::operation_result::no_space:
        std::cout << "Message could not be sent because the queue is full" << std::endl;
        break;
    case queue_t::operation_result::aborted:
        // This can happen is overflow_policy is block_on_overflow
        std::cout << "Message sending operation has been interrupted" << std::endl;
        break;
    }
    return 0;
}

1

Создайте очередь, если еще не созданы

2

Если нужно создать очередь, выделите 256 блоков.

3

1 киб для каждого сообщения

4

Если очередь заполнена, верните ошибку автору.

См. полный код.

Как правило, очередь используется для отправки записей журнала в другой процесс. Таким образом, вместо того, чтобы использовать очередь для написания непосредственно, можно использовать специальный фон раковины для этого. См.<text_ipc_message_queue_backend>документацию.

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

int main()
{
    try
    {
        typedef logging::ipc::reliable_message_queue queue_t;
        // Create a message_queue_type object that is associated with the interprocess
        // message queue named "ipc_message_queue".
        queue_t queue
        (
            keywords::name = logging::ipc::object_name(logging::ipc::object_name::user, "ipc_message_queue"),
            keywords::open_mode = logging::open_mode::open_or_create,
            keywords::capacity = 256,
            keywords::block_size = 1024,
            keywords::overflow_policy = queue_t::block_on_overflow
        );
        std::cout << "Viewer process running..." << std::endl;
        // Keep reading log messages from the associated message queue and print them on the console.
        // queue.receive() will block if the queue is empty.
        std::string message;
        while (queue.receive(message) == queue_t::succeeded)
        {
            std::cout << message << std::endl;
            // Clear the buffer for the next message
            message.clear();
        }
    }
    catch (std::exception& e)
    {
        std::cout << "Failure: " << e.what() << std::endl;
    }
    return 0;
}

См. полный код.

[Note]Note

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

Заблокированный читатель или автор может быть разблокирован по телефону<stop_local>. После того, как этот метод называется, все нити, заблокированные на этом конкретном объекте, высвобождаются и возвращаются<operation_result::aborted>. Другие случаи очереди (в текущих или других процессах) не затрагиваются. Чтобы восстановить нормальное функционирование экземпляра очереди после вызова<stop_local>, пользователь должен вызвать<reset_local>.

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

Для некоторых функций, описанных в этом разделе, потребуется отдельный библиотечный двоичный файл с именем, основанным на строке «boost_log_setup». Этот бинар зависит от основной библиотеки.

#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

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

Регистрация на консоли приложения - это самый простой способ увидеть библиотеку журналов в действии. Для этого можно инициализировать библиотеку одним вызовом функции, например:

int main(int, char*[])
{
    // Initialize logging to std::clog
    logging::add_console_log();
    // Here we go, we can write logs right away
    src::logger lg;
    BOOST_LOG(lg) << "Hello world!";
    return 0;
}

Довольно просто, не так ли? Существует также функция<wadd_console_log>для широкохарактерной консоли. Если вы хотите поместить журналы в какой-то другой стандартный поток, вы можете передать поток функции<add_console_log>в качестве аргумента. Например, возможность регистрации в<std::cout>вместо<std::clog>будет выглядеть следующим образом:

logging::add_console_log(std::cout);

Важно то, что вы можете дополнительно управлять раковиной консоли, если сохраните<shared_ptr>до раковины, которую эта функция возвращает. Это позволяет настроить такие вещи, как фильтр, формататор и автоматический флаг.

int main(int, char*[])
{
    // Initialize logging to std::clog
    boost::shared_ptr<
        sinks::synchronous_sink< sinks::text_ostream_backend >
    > sink = logging::add_console_log();
    sink->set_filter(expr::attr< int >("Severity") >= 3);
    sink->locked_backend()->auto_flush(true);
    // Here we go, we can write logs right away
    src::logger lg;
    BOOST_LOG(lg) << "Hello world!";
    return 0;
}

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

int main(int, char*[])
{
    // Initialize logging to the "test.log" file
    logging::add_file_log("test.log");
    // Here we go, we can write logs right away
    src::logger lg;
    BOOST_LOG(lg) << "Hello world!";
    return 0;
}

Функции<add_console_log>и<add_file_log>не конфликтуют и могут свободно комбинироваться, поэтому можно настроить вход в консоль и пару файлов, включая фильтрацию и форматирование, примерно в 10 строках кода.

Наконец, есть функция<add_common_attributes>, которая регистрирует два часто используемых атрибута: «LineID» и «TimeStamp». Первый считает запись журнала сделанной и имеет значение атрибута<unsignedint>. Последнее, как следует из его названия, предоставляет текущее время для каждой записи журнала в виде<boost::posix_time::ptime>(см.Boost.DateTime). Эти два атрибута зарегистрированы во всем мире, поэтому они будут доступны во всех потоках и регистраторах. Это делает окончательную версию нашего образца кода примерно такой:

int main(int, char*[])
{
    // Initialize sinks
    logging::add_console_log()->set_filter(expr::attr< int >("Severity") >= 4);
    logging::formatter formatter =
        expr::stream
            << expr::attr< unsigned int >("LineID") << ": "
            << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << " *"
            << expr::attr< int >("Severity") << "* "
            << expr::message;
    logging::add_file_log("complete.log")->set_formatter(formatter);
    boost::shared_ptr<
        sinks::synchronous_sink< sinks::text_ostream_backend >
    > sink = logging::add_file_log("essential.log");
    sink->set_formatter(formatter);
    sink->set_filter(expr::attr< int >("Severity") >= 1);
    // Register common attributes
    logging::add_common_attributes();
    // Here we go, we can write logs
    src::logger lg;
    BOOST_LOG(lg) << "Hello world!";
    return 0;
}
#include <boost/log/utility/setup/filter_parser.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>

Парсеры фильтров и форматеров позволяют создавать фильтры и формататоры из описательной строки. Функция<parse_filter>отвечает за распознавание фильтров и<parse_formatter>— за распознавание формататоров.

В случае фильтров струна образуется из последовательности выражений состояний, связанных с булевыми операциями. Поддерживаются две операции: соединение (обозначенное как «&» или «и») и разъединение (« |» или «или»). Каждое условие само по себе может быть либо одним условием, либо подфильтром, взятым в круглые скобки. Каждое условие можно свести на нет с помощью ключевого слова «!» или «нет». Условие, если оно не является подфильтром, обычно состоит из имени атрибута, заключенного в символах % («%»), ключевом слове отношения и операнде. Отношение и операнд могут быть опущены, и в этом случае условие считается требованием присутствия атрибута (с любым типом).

filter:
    condition { op condition }
op:
    &
    and
    |
    or
condition:
    !condition
    not condition
    (filter)
    %attribute_name%
    %attribute_name% relation operand
relation:
    >
    <
    =
    !=
    >=
    <=
    begins_with
    ends_with
    contains
    matches

Ниже приведены некоторые примеры фильтров:

Table 1.9. Examples of filters

струна фильтра

Описание

<%Severity%>

Фильтр возвращает<true>, если в записи журнала находится значение атрибута с именем «Severity».

<%Severity%> 3>

Фильтр возвращает<true>, если найдено значение атрибута с именем «Severity» и оно больше 3. Значение атрибута должно быть одного изинтегральных типов

.

<%Ratio%> 0.0& %Ratio%<= 0.5)>

<%Tag%contains "net"or %Tag%contains "io"and not%StatFlow%>

Фильтр возвращает<true>, если найдено значение атрибута с именем «Tag» и содержит слова «net» или «io» и если значение атрибута «StatFlow» не найдено. Значение атрибута "Tag" должно быть одного изтипов строк, тип значения атрибута "StatFlow" не рассматривается.


Синтаксис строки форматера еще проще и в значительной степени напоминаетBoost. Форматсинтаксис строк. Струна интерпретируется как шаблон, который может содержать имена атрибутов, прилагаемые знаками % («%»). Соответствующие значения атрибутов заменят эти заполнители при применении форматтера. Заполнитель «%Message%» будет заменен текстом записи журнала. Например, следующая строка форматтера:

[%TimeStamp%] *%Severity%* %Message%

Записи журналов будут выглядеть так:

[2008-07-05 13:44:23] *0* Hello world
[Note]Note

Предыдущие выпуски библиотеки также поддерживали заполнитель «%_%» для текста сообщения. Этот заполнитель теперь обесценен, хотя он все еще работает для обратной совместимости. Его поддержка будет удалена в будущих выпусках.

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

[Note]Note

Парсированные формататоры и фильтры, как правило, менее оптимальны, чем эквивалентные, написанные в коде с выражениями шаблонов. Это связано с двумя причинами: (*) программист обычно знает больше о типах значений атрибутов, которые могут быть задействованы в форматировании или фильтрации, и (*) компилятор имеет больше шансов оптимизировать формататор или фильтр, если он известен во время компиляции. Поэтому, когда производительность имеет значение, рекомендуется избегать разрозненных фильтров и форматировщиков.

#include <boost/log/utility/setup/settings.hpp>
#include <boost/log/utility/setup/from_settings.hpp>

Заголовки определяют компоненты для инициализации библиотеки из контейнера настроек. Контейнер настроек представляет собой набор именованных параметров, разделенных на разделы. Контейнер реализован с шаблоном класса<basic_settings>. Существует несколько ограничений на то, как параметры хранятся в контейнере:

  • Каждый параметр должен находиться в разделе. Не может быть параметров, не относящихся к разделу.
  • Параметры должны иметь уникальные имена в разделе, к которому они относятся. Параметры из разных секций могут иметь одинаковое название.
  • Секции могут гнездиться. При чтении из файла или доступе из кода имена разделов могут выражать произвольную иерархию, разделяя имена родительского и детского разделов с «.» (например, «[Parent.Child.ChildChild]»).
  • Секции должны иметь имена, уникальные в пределах прилагаемого раздела (или глобального охвата, если раздел находится на верхнем уровне).

Таким образом, контейнер настроек представляет собой многоуровневый ассоциативный контейнер со строковыми ключами и значениями. В некотором отношении он похож наBoost.PropertyTree, и фактически он поддерживает строительство из<boost::ptree>. Поддерживаемые параметры описаны ниже.

[Tip]Tip

В таблицах ниже тип<CharT>обозначает тип символа, который используется с контейнером настроек.

Table 1.10. Section "Core". Logging core settings.

Параметр

Формат

Описание

Фильтр

Строка фильтра, как описаноздесь

Глобальный фильтр для установки в ядро. Если не указано, глобальный фильтр не установлен.

Инвалидность

«истинный» или «ложный»

Если<true>, то получается вызов<set_logging_enabled(false)>на ядро. По умолчанию принимается значение<false>.


Настройки поглотителей разделены на отдельные подразделы в рамках общего раздела верхнего уровня «Поглотители» - по одному подразделу для каждой раковины. Имена подраздела обозначают имя раковины, определенное пользователем. Например, «Мой файл».

[Note]Note

Предыдущие версии библиотеки также поддерживали разделы верхнего уровня, начиная с префикса «Sink:» для описания параметров раковины. Этот синтаксис теперь обесценен, хотя он все еще работает при разборе файла настроек для обратной совместимости. Парсер автоматически поместит эти разделы под секцией верхнего уровня «Sinks» в результирующий контейнер настроек. Поддержка этого синтаксиса будет удалена в будущих выпусках.

Table 1.11. Sections under the "Sinks" section. Common sink settings.

Параметр

Формат

Описание

Направление

Цель поглотить, см. описание

Фильтр

Строка фильтра, как описаноздесь

Удельный фильтр. Если не указано, то фильтр не установлен.

«истинный» или «ложный»

Если<true>, то будет использоватьсяасинхронный фронтенд раковины. В противном случае будет использоватьсясинхронный фронтенд раковины. По умолчанию принимается значение<false>. В однопоточной сборке этот параметр не используется, так как всегда используетсяразблокированный фронтенд раковины.


Помимо общих настроек, которые поддерживают все раковины, некоторые бэкэнды раковины также принимают ряд конкретных параметров. Эти параметры должны быть указаны в том же разделе.

Table 1.12. "Console" sink settings

Параметр

Формат

Описание

Формат

Формат строки, как описаноздесь

Формататор записи журнала, который будет использоваться раковиной. Если не указано, используется форматтер по умолчанию.

Автофлеш

«истинный» или «ложный»

Включает или отключает функцию автоматического смыва бэкэнда. Если не указано, принимается значение по умолчанию<false>.


Table 1.13. "TextFile" sink settings

Параметр

Формат

Описание

Файловое имя

Файловое имя

Образец имени файла для бэкэнда раковины. Этот параметр является обязательным.

Формат

Формат строки, как описаноздесь

Формататор записи журнала, который будет использоваться раковиной. Если не указано, используется форматтер по умолчанию.

Автофлеш

«истинный» или «ложный»

Включает или отключает функцию автоматического смыва бэкэнда. Если не указано, принимается значение по умолчанию<false>.

Размер вращения

Неподписанное целое число

Размер файла в байтах, на котором будет выполняться вращение файла. Если не указано, то ротация на основе размера не производится.

Вращение Интервал

Неподписанное целое число

Интервал времени в секундах, на который будет выполняться вращение файла. См. также параметр RotationTimePoint и примечание ниже.

Строка формата точки времени, см. ниже

Точка времени или предикат, который определяет, в какой момент времени выполнить вращение файла журнала. См. также параметр RotationInterval и примечание ниже.

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

MaxSize

Неподписанное целое число

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

MinFreeSpace

Неподписанное целое число

Минимальное свободное пространство в целевой директории, в байтах, на котором будет удален самый старый файл. Если не указано, не будет выполняться очистка файлов на основе пространства.

MaxFiles

Неподписанное целое число

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

Сканирование файлов

«Все» или «Матч»


[Warning]Warning

Исправитель текстового файла используетBoost.Filesystemвнутренне, что может вызвать проблемы при прекращении процесса. См.здесьдля более подробной информации.

Вращение на основе времени может быть настроено с одним из двух параметров: RotationInterval или RotationTimePoint. Для данной раковины следует указывать не более одного из этих параметров. Если ничего не указано, ротация на основе времени не будет выполняться.

Параметр RotationTimePoint должен иметь один из следующих форматов, согласно обозначению форматаBoost.DateTime:

  • "%H:%M:%S". В этом случае ротация файлов будет осуществляться ежедневно, в указанное время. Например, «12:00:00».
  • "%a%H:%M:%S" или "%A%H:%M:%S". Ротация файлов происходит каждую неделю, в будний день, указанный в длинной или короткой форме, в указанное время. Например, «Суббота 09:00:00».
  • "%d%H:%M:%S". Ротация файлов происходит каждый месяц, в указанный день месяца, в указанное время. Например, "01 23:30:00".

Table 1.14. "Syslog" sink settings

Параметр

Формат

Описание

Формат

Формат строки, как описаноздесь

Формататор записи журнала, который будет использоваться раковиной. Если не указано, используется форматтер по умолчанию.

Местный адрес

IP-адрес

Локальный адрес для инициирования подключения к серверу syslog. Если не указано, будет использоваться локальный адрес по умолчанию.

Целевой адрес

IP-адрес

Удаленный адрес сервера syslog. Если не указано, будет использован местный адрес.


Table 1.15. "SimpleEventLog" sink settings

Параметр

Формат

Описание

Формат

Формат строки, как описаноздесь

Формататор записи журнала, который будет использоваться раковиной. Если не указано, используется форматтер по умолчанию.

LogName

Струна

Имя журнала для записи событий. Если не указано, будет использоваться имя журнала по умолчанию.

Струна

Источник журнала для написания событий. Если не указано, будет использоваться источник по умолчанию.

Регистрация

«Никогда», «По требованию» или «Принудительно»

Режим регистрации источника журнала в реестре Windows см.<registration_mode>. Если не указано, регистрация по требованию будет выполнена.


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

void init_logging()
{
    logging::settings setts;
    setts["Core"]["Filter"] = "%Severity% >= warning";
    setts["Core"]["DisableLogging"] = false;
    // Subsections can be referred to with a single path
    setts["Sinks.Console"]["Destination"] = "Console";
    setts["Sinks.Console"]["Filter"] = "%Severity% >= fatal";
    setts["Sinks.Console"]["AutoFlush"] = true;
    // ...as well as the individual parameters
    setts["Sinks.File.Destination"] = "TextFile";
    setts["Sinks.File.FileName"] = "MyApp_%3N.log";
    setts["Sinks.File.AutoFlush"] = true;
    setts["Sinks.File.RotationSize"] = 10 * 1024 * 1024; // 10 MiB
    logging::init_from_settings(setts);
}

Считыватель настроек также позволяет расширяться для поддержки пользовательских типов раковины. СмотретьРасширение разделадля получения дополнительной информации.

#include <boost/log/utility/setup/from_stream.hpp>

Поддержка конфигурационных файлов является часто запрашиваемой функцией библиотеки. И несмотря на то, что в конечном итоге отсутствует удобный и гибкий формат настроек библиотеки, библиотека обеспечивает предварительную поддержку этой функции. Функция реализована с помощью простой функции<init_from_stream>, которая принимает поток ввода STL и считывает из него настройки библиотеки. Затем функция переходит на настройки чтения к функции<init_from_settings>, описаннойвыше. Следовательно, имена параметров и их значение одинаковы для функции<init_from_settings>.

Формат настроек довольно прост и широко используется. Ниже приведено описание синтаксиса и параметров.

# Comments are allowed. Comment line begins with the '#' character
# and spans until the end of the line.
# Logging core settings section. May be omitted if no parameters specified within it.
[Core]
DisableLogging=false
Filter="%Severity% > 3"
# Sink settings sections
[Sinks.MySink1]
# Sink destination type
Destination=Console
# Sink-specific filter. Optional, by default no filter is applied.
Filter="%Target% contains \"MySink1\""
# Formatter string. Optional, by default only log record message text is written.
Format="<%TimeStamp%> - %Message%"
# The flag shows whether the sink should be asynchronous
Asynchronous=false
# Enables automatic stream flush after each log record.
AutoFlush=true

Вот пример использования:

int main(int, char*[])
{
    // Read logging settings from a file
    std::ifstream file("settings.ini");
    logging::init_from_stream(file);
    return 0;
}

PrevUpHomeNext

Статья Utilities раздела Chapter 1. Boost.Log v2 Detailed features description может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Detailed features description ::


реклама


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

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