Моя программа не поддерживает безопасное ведение журнала при использовании библиотеки boost

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

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

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

Если кто-нибудь найдет проблему, пожалуйста, помогите мне решить ее.

BoostLogger.h:

#pragma once
......
///////////////////////////////////////
//Defining Macros
///////////////////////////////////////
#define AddCommonAttr()         logging::add_common_attributes()
#define GetLoggingCore()        logging::core::get()
#define LoggingSeverity         logging::trivial::severity
#define AddFileLog              logging::add_file_log
#define ThreadValueType         logging::attributes::current_thread_id::value_type
#define Record                  logging::record
#define Extract                 logging::extract

#define ExprStream              expr::stream
#define ExprAttr                expr::attr
#define ExprFormatDateTime      expr::format_date_time
#define PosixTimeType           boost::posix_time::ptime
#define ExprMessage             expr::smessage

#define FileName                keywords::file_name
#define RotationSize            keywords::rotation_size
#define TimeBasedRotation       keywords::time_based_rotation
#define Format                  keywords::format
#define Target                  keywords::target
#define MaxSize                 keywords::max_size
#define MinFreeSpace            keywords::min_free_space
#define RotationAtTimeInterval  sinks::file::rotation_at_time_interval

#define Reset_Filter            reset_filter                                /*The reset_filter method removes the global logging filter.*/
#define Set_Filter              set_filter                                  /*The set_filter method sets the global logging filter to every log record that is processed.*/
#define SetFormatter            set_formatter
#define RecordView              logging::record_view
#define FormattingOstream       logging::formatting_ostream
#define SharedPtr               boost::shared_ptr
#define MakeShared              boost::make_shared
#define SinkFileBackend         sinks::text_file_backend
#define LockedBackend           locked_backend
#define SetFileCollector        set_file_collector
#define MakeCollector           sinks::file::make_collector
#define AddSink                 add_sink                                    /*The add_sink method adds a new sink. The sink is included into logging process immediately after being added and until being removed. No sink can be added more than once at the same time. If the sink is already registered, the call is ignored.*/
#define RemoveSink              remove_sink                                 /*The remove_sink method removes the sink from the output. The sink will not receive any log records after removal. The call has no effect if the sink is not registered.*/
#define RemoveAllSinks          remove_all_sinks                            /*The remove_all_sinks method removes all registered sinks from the output. The sinks will not receive any log records after removal.*/
#define Flush                   flush
#define ScanForFiles            scan_for_files
#define ScanAll                 sinks::file::scan_all
#define ScanMatching            sinks::file::scan_matching

#define SetExceptionHandler     set_exception_handler
#define ExceptionSuppressor     logging::make_exception_suppressor
#define MakeExceptionHandler    logging::make_exception_handler

typedef sinks::synchronous_sink < SinkFileBackend >     sink_type;

static src::logger lg;
#define WriteToLog              BOOST_LOG(lg)

/*Defining Macros for Writing log with Severity*/
//BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(my_logger, src::logger_mt)
//static src::severity_logger< logging::trivial::severity_level > slg;

#define LogTrace        BOOST_LOG_SEV(obj->slg, logging::trivial::trace)        
#define LogDebug        BOOST_LOG_SEV(obj->slg, logging::trivial::debug)        
#define LogInfo         BOOST_LOG_SEV(obj->slg, logging::trivial::info)     
#define LogWarning      BOOST_LOG_SEV(obj->slg, logging::trivial::warning)  
#define LogError        BOOST_LOG_SEV(obj->slg, logging::trivial::error)        
#define LogFatal        BOOST_LOG_SEV(obj->slg, logging::trivial::fatal)        
#define _1MB    (1 * 1024 * 1024)
#define _10MB   (10 * 1024 * 1024)
#define datefmt ("_%Y-%b-%d")
#define timefmt ("_%H-%M-%S")

using namespace std;
class CBoostLogger
{
private: 
    SharedPtr< SinkFileBackend > backend;
    SharedPtr< sink_type > sink;
public:
    src::severity_logger< logging::trivial::severity_level > slg;
    CBoostLogger(void);
    ~CBoostLogger(void);
    bool StartLogger(struct FileFormat *sff);
    bool StopLogger();
    bool SetFilter(short severitylevel);
    bool SetFormat(struct LogFormat *sle);

private:
    friend void Formatter(logging::record_view const& rec, logging::formatting_ostream& strm);
};
/*This Structure is used to set the formats for file*/
struct FileFormat
{
bool includedatetofile;
bool includetimetofile;
string filename;
string filelocation;
unsigned long rotationsize;
unsigned long maxsize;

FileFormat() :  includedatetofile(false),
                includetimetofile(false),
                filename("log")         ,
                filelocation("C:/Log")  ,
                rotationsize(_1MB)      ,
                maxsize(_10MB)          {};
};

struct LogFormat
{
bool Set_LineID;
bool Set_Time;
bool Set_Severity; 
bool Set_ThreadID;
bool Set_Message;

LogFormat() :   Set_LineID(true)    ,
                Set_Time(true)      ,
                Set_Severity(true)  ,
                Set_ThreadID(true)  ,
                Set_Message(true)   {};

LogFormat(bool lineid, bool time, bool severity, bool threadid, bool message) 
    :   Set_LineID(lineid)      ,
        Set_Time(time)          ,
        Set_Severity(severity)  ,
        Set_ThreadID(threadid)  ,
        Set_Message(message)    {};
};

BoostLogger.cpp:

#pragma once
#include "BoostLogger.h"

////////////////////////////////////
//Global Declarations
////////////////////////////////////

bool SetLineID, SetTime, SetSeverity, SetThreadID, SetMessage ;

CBoostLogger::CBoostLogger(void)
{
    cout << "Calling CBoostLogger Constructor..." << endl;
    SetFilter(2);
    //GetLoggingCore()->SetExceptionHandler(MakeExceptionHandler<std::runtime_error,std::exception>(handler()));
    GetLoggingCore()->SetExceptionHandler(ExceptionSuppressor());
}

CBoostLogger::~CBoostLogger(void)
{
    GetLoggingCore() -> Reset_Filter();     
    GetLoggingCore() -> RemoveAllSinks();
}

bool CBoostLogger::StartLogger(struct FileFormat *sff )
{
    if(sff->includedatetofile)
        sff->filename += datefmt;
    if(sff->includetimetofile)
        sff->filename += timefmt;
    sff->filename += ".log";
    backend = MakeShared < SinkFileBackend >(
                FileName            =   sff->filename,                                                                                  /*< file name pattern >*/
                RotationSize        =   sff->rotationsize                                                                               /*< rotate files for every 1M >*/
                );  
    sink = MakeShared < sink_type > (backend);
    LogFormat sle;
    SetFormat(&sle);
    sink->LockedBackend()->SetFileCollector
        ( 
            MakeCollector
            ( 
                Target  =   sff->filelocation ,                 /*File Storage Location*/   
                MaxSize =   sff->maxsize                        /*Limit for folder : maxsize, where initially maxsize = 10M*/
            )
        );
    sink->LockedBackend()->ScanForFiles(ScanAll);
    GetLoggingCore()->AddSink(sink);

    AddCommonAttr();    
    BOOST_LOG_SEV(this->slg, logging::trivial::info) << "Logger Starts";
    return true;
}

/*This function used to remove the registered sink from core.*/
bool CBoostLogger::StopLogger()
{
    BOOST_LOG_SEV(this->slg, logging::trivial::info) << "Logger Stops";
    GetLoggingCore()->RemoveSink(sink);
    GetLoggingCore()->Flush();
    return true;
}

    /*This function is used to set filter level. */
bool CBoostLogger::SetFilter(short severitylevel)
{
    GetLoggingCore()->Set_Filter                
    (
        LoggingSeverity >= severitylevel
    );
    return true;
}

/*This function is used to set format for log. */
bool CBoostLogger::SetFormat(struct LogFormat *sle)
{
    SetLineID   = sle->Set_LineID;
    SetTime     = sle->Set_Time;
    SetSeverity = sle->Set_Severity;
    SetThreadID = sle->Set_ThreadID;
    SetMessage  = sle->Set_Message;
    sink->SetFormatter(&Formatter);
    return true;
}

/*This function is used to set format for the log file.*/
void Formatter(RecordView const& rec, FormattingOstream& strm)
{
    if(SetLineID)   
    {
        strm << Extract < unsigned int >    ("LineID", rec) << "\t";    // Get the LineID attribute value and put it into the stream
    }
    if(SetTime) 
    {
        strm << Extract < PosixTimeType >   ("TimeStamp", rec) << "\t"; // Get the TimeStamp attribute value and put it into the stream
    }
    if(SetSeverity) 
    {
        strm << "[ " << rec[LoggingSeverity] << " ]\t";                 // Get the Severity attribute value and put it into the stream
    }
    if(SetThreadID) 
    {
        strm << Extract < ThreadValueType > ("ThreadID", rec )<<"\t";   // Get the ThreadID attribute value and put into the stream
    }
    if(SetMessage)  
    {
        strm << rec[ExprMessage];                                       // Finally, put the record message to the stream
    }
}

struct handler
{
  void operator()(const runtime_error &ex) const
  {
    std::cerr << "\nRuntime_error: " << ex.what() << '\n';
  }

  void operator()(const exception &ex) const
  {
    std::cerr << "Exception: " << ex.what() << '\n';
  }
};

Источник.cpp:

#include "BoostLogger.h"

void func_thread(std::string fn,string fl,int num)
{
    std::string buf = "";
    char str[20];
    buf += itoa(num, str, 10);
    fn += buf;

    CBoostLogger *obj = new CBoostLogger();
    FileFormat formatobj;
    formatobj.filename = fn;
    formatobj.filelocation = fl;
    formatobj.includedatetofile = true;
    formatobj.includetimetofile = true;
    obj->StartLogger(&formatobj);

    for(int i=0;i<10000;i++)
    {
        LogTrace    << "Trace message new " << fn;
        BOOST_LOG_SEV(obj->slg,logging::trivial::trace) << "Test";

        LogDebug    << "Debug Message new"  << fn;
        LogInfo     << "Info  message" << fn;
        LogWarning  << "Warning  message new" << fn;
        LogError    << "An error  message new" << fn;
        LogFatal    << "A fatal  message new" << fn;
    }   

    LogFormat sle(true,false,false,false,true);
    obj->SetFormat(&sle);   

    for(int i=0;i<10000;i++)
    {
        LogTrace        << "Trace message new " << fn;
        LogDebug        << "Debug Message new"  << fn;
        LogInfo     << "Info  message" << fn;
        LogWarning  << "Warning  message new" << fn;
        LogError        << "An error  message new" << fn;
        LogFatal        << "A fatal  message new" << fn;
    }   
    obj->StopLogger();
    delete obj;
}

int main()
{
    //This following code makes problem.
    boost::thread *thread1 = new boost::thread(&func_thread,"Thread_","C:/BoostLog",1);
    boost::thread *thread2 = new boost::thread(&func_thread,"Thread_","C:/BoostLog",2);
    thread1->join();
    thread2->join();

    /*
    //This following is not making that problem.
    boost::thread_group t_groups;
    for(int i=1;i<=5;i++)
    {
        t_groups.create_thread(boost::bind(&func_thread,"Thread","C:/BoostLog",i));
        t_groups.join_all();
    }

    boost::thread_group tgroup;
    boost::thread *threads;
    for(int i=0;i<20;i++)
    {
        threads=new boost::thread(&func_thread,"Thread","C:/BoostLog",i);
        tgroup.add_thread(threads);
        std::cout << "\nThread "<<i<<" is created whose id is : "<<threads->get_id();
        threads->join();
    }   
    */

    return 0;
}   

Если вам нужна дополнительная информация по этому поводу, пожалуйста, спросите меня.

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

И еще одно, если возможно, потоки должны выполняться одновременно.

Спасибо.


person Arun kumar Kalaiarasan    schedule 29.02.2016    source источник
comment
Эта ссылка может вам помочь.   -  person Some programmer dude    schedule 29.02.2016
comment
Какая? Как они могут столкнуться? Если есть два файла, создайте два экземпляра — никаких «коллизий».   -  person Martin James    schedule 29.02.2016
comment
Минимальный пример, пожалуйста.   -  person SergeyA    schedule 29.02.2016


Ответы (4)


Для столкновения используйте

BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
или
BOOST_LOG_SCOPED_THREAD_TAG("Tag", tag_)

Для получения более подробной информации вы можете проверить это ниже здесь:

Boost.log: Как предотвратить дублирование вывода во все добавленные потоки при использовании функции add_file_log()?
By, Thiyagu

person Thiyaga    schedule 12.09.2016
comment
Да, у меня такой же ответ на другой вопрос. Не повторяйте ответ. - person Arun kumar Kalaiarasan; 12.09.2016

Я бы рекомендовал изучить boost::scoped_lock блокировку области документация Если вы просто хотите сделать каждую функцию потокобезопасной, используйте блокировку области действия с переменной-членом мьютекса. Однако, если вы хотите синхронизировать свои потоки и их выполнение, загляните в boost::condition_variable

person Samer Tufail    schedule 29.02.2016
comment
Спасибо. Но блокировку здесь использовать не следует. Потому что потоки должны работать одновременно. - person Arun kumar Kalaiarasan; 29.02.2016

Я не уверен, что вы подразумеваете под «столкновением двух потоков», но первая непосредственная проблема с вашим кодом заключается в том, что вы используете не потокобезопасные регистраторы src::logger и src::severity_logger. Поточно-ориентированными аналогами являются src::logger_mt и src::severity_logger_mt (см. здесь, например).

Если вы хотите разделить журналы, созданные разными потоками, в разные файлы, попробуйте использовать text_multifile_backend с генератором имени файла на основе атрибута "ThreadID", который вы добавляете с помощью вызова logging::add_common_attributes() при инициализации. код. На связанной странице есть пример.

Кстати, вы не должны вызывать logging::add_common_attributes() каждый раз, когда вызывается CBoostLogger::StartLogger, что происходит в каждом потоке. Атрибуты нужно добавлять только один раз.

Альтернативное решение, поскольку вы создаете CBoostLogger для каждого потока, состоит в том, чтобы настроить фильтр для добавления стока CBoostLogger::StartLogger. Фильтр должен пропускать записи с идентификатором текущего потока и отбрасывать остальные. Вы можете сделать это следующим образом:

bool CBoostLogger::StartLogger(struct FileFormat *sff )
{
    // ...
    sink = MakeShared < sink_type > (backend);

    // Get the current thread id
    typedef logging::attributes::current_thread_id::value_type thread_id;
    thread_id id = logging::attributes::current_thread_id().get_value().extract_or_throw< thread_id >();

    // Set the filter
    sink->set_filter(expr::attr< thread_id >("ThreadID") == id);

    // ...
}

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

person Andrey Semashev    schedule 29.02.2016
comment
Я говорю «столкновение» в том смысле, что пока я создаю два потока и запускаю их, оба потока создают свои собственные файлы, и сообщения, созданные каждым потоком, записываются в оба файла. - person Arun kumar Kalaiarasan; 29.02.2016
comment
О, я вижу это сейчас. Вы создаете отдельный экземпляр CBoostLogger для каждого потока. В таком случае есть другое решение. Вам нужно только добавить фильтр к приемнику каждого потока, который пропускает записи журнала только из этого потока. - person Andrey Semashev; 29.02.2016
comment
Хорошо. Можете ли вы объяснить, как добавить фильтр в сток для каждого потока? - person Arun kumar Kalaiarasan; 29.02.2016
comment
Большое спасибо г-н Андрей. Моя проблема решена с регистратором. Еще раз спасибо. - person Arun kumar Kalaiarasan; 01.03.2016
comment
Можно ли включить в эту программу обработку исключений? - person Arun kumar Kalaiarasan; 01.03.2016
comment
Нет, стоки не привязаны к потокам или логгерам. Вся маршрутизация записей журнала выполняется исключительно путем фильтрации. - person Andrey Semashev; 01.03.2016
comment
Хорошо . Здесь я использую synchronised_sink, чтобы создаваемые здесь потоки были внутренне синхронизированы. Я прав? Поскольку запись lineID в каждый файл не является непрерывной, и когда я устанавливаю фильтр и формат для одного потока, это также применяется к другим потокам. Есть ли другой способ создать журнал для каждого потока без использования синхронизации и блокировки? - person Arun kumar Kalaiarasan; 01.03.2016
comment
LineID, как и любые другие атрибуты, не имеет отношения к приемникам. Они не являются смежными в файлах журналов, поскольку атрибут добавляется глобально и подсчитывает записи журнала из всех потоков. Вы можете подсчитывать записи для каждого потока, если вы добавите атрибут в набор для конкретного потока, а не в глобальный. - person Andrey Semashev; 01.03.2016
comment
synchronous_sink выполняет внутреннюю блокировку при обработке записи. Это не должно вас беспокоить, потому что в соответствии с вашим фильтром каждый приемник обрабатывает записи только из одного потока, и поэтому конфликтов не возникает. - person Andrey Semashev; 01.03.2016
comment
Вам следует прочитать обзор дизайна (boost .org/doc/libs/1_60_0/libs/log/doc/html/log/design.html), чтобы лучше понять, как работает библиотека. - person Andrey Semashev; 01.03.2016
comment
Спасибо за ответ. Но как я могу установить атрибут LineID как специфичный для потока, а не как глобальный? - person Arun kumar Kalaiarasan; 01.03.2016
comment
Вы можете сделать это, вызвав add_thread_attribute в ядре регистрации. boost.org/doc/libs/1_60_0/libs/log/doc/html/log/ - person Andrey Semashev; 01.03.2016
comment
Извините, что спрашиваю вас. Можете ли вы объяснить мне пример кода. Потому что я не могу этого понять. - person Arun kumar Kalaiarasan; 01.03.2016
comment
Нет проблем, я использовал add_thread_attribute в программе. Спасибо за вашу помощь. - person Arun kumar Kalaiarasan; 01.03.2016
comment
Как установить форматтер для бэкэнда вместо настройки для приемника? Потому что я не знаю, как использовать backend-›set_formatter. Можете ли вы помочь мне найти эти. Спасибо. - person Arun kumar Kalaiarasan; 02.03.2016
comment
Хай... У меня есть еще одно сомнение. Можем ли мы отформатировать серверную часть для определенного потока? - person Arun kumar Kalaiarasan; 02.03.2016
comment
Знаете ли вы, как определить потокобезопасный регистратор? Могу ли я использовать глобальный регистратор и его экземпляр? - person Arun kumar Kalaiarasan; 02.03.2016
comment
Спасибо... @Андрей Семашев - person Arun kumar Kalaiarasan; 16.03.2016

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

Основное требование

Насколько я понимаю, основное требование состоит в том, что время регистрации события должно быть фиксированным, и чем короче, тем лучше. Многие «многопоточные» варианты ведения журнала, которые я видел, не работают (насколько я могу судить, log4cpp, boost mt loggers).

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

Идеал

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

Apache log4cpp

Apache log4cpp приближается к этому — у него есть AsyncAppender. Я говорю близко; в то время как у него есть внутренний поток, вызывающий любые другие подключенные приложения, передний конец не может быть разделен между группой потоков без того, чтобы все они боролись за один и тот же мьютекс. Но, по крайней мере, запись в файл не связана с ведением журнала. Это имеет большое значение для обеспечения того, чтобы поток не блокировался журналом.

Log4cpp

Log4cpp имеет BufferingAppender (не упоминается в документах, но есть в коде). У него есть очередь, но нет потока диспетчера, такого как AsyncAppender в log4cxx. Это можно использовать в качестве шаблона для создания чего-то лучшего.

Журнал повышения

Это отвратительная, ужасная, большая уродливая куча труднопостижимого и не полностью задокументированного кода. Я еще не смог точно понять, что он на самом деле может сделать с потоками и моим идеалом, но я думаю, что его asynchronous_sink выполняет эту работу. Как и Apache log4cxx, внешний интерфейс ведения журнала также использует мьютекс, чтобы сделать его потокобезопасным.

Нулевой MQ

Проблема, которую я вижу с Log4cxx и журналом Boost, заключается в том, что не так много места для гибкости архитектуры журнала. Чего я действительно хочу, так это шаблонов, которые вы можете создавать с помощью ZeroMQ.

Я все больше убеждаюсь, что мне нужно написать свою собственную библиотеку ведения журналов. ZeroMQ имеет несколько очень хороших шаблонов (в частности, PUB/SUB), и использование их для отправки сообщений журнала из потоков в центральный поток журналирования кажется хорошей идеей, особенно с учетом архитектурной гибкости ZeroMQ.

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

Я могу попытаться втиснуть его в log4cpp и буквально использовать его в качестве транспорта между категориями/аппендерами для каждого потока и центральным аппендером, который выполняет фактический вывод. Хотя это будет немного сложно. ZeroMQ переносит байты, а не объекты. Поэтому мне, возможно, придется сериализовать события log4cpp... Затем возникает вопрос о том, какую сериализацию использовать и т. д. и т. д., и, прежде чем я это узнаю, он станет неприятным большим неэффективным монстром.

person bazza    schedule 20.04.2016