Amir Rossert
Amir Rossert

Reputation: 226

Boost Log multiple files with rotation

My application have several components and I want that each component will write logs to a separate file.

I wanted to use the "Text multi-file backend" but according to the documentation it does not support file rotation.

So my idea was to implement a log class and make an instant for each of the components and store them in a map, that way I can use the map to get the correct logger instance (according to the name) and log to the correct file.

I have done this but this is not working for me, I can see the same messages in all of the files (it seems that this is a global logger).

This is a draft of my code:

logger.h

struct LogInfo{
    std::string log_path;
    LogLevel log_level;
    long log_file_size;
    int log_file_amount;
};

LogLevel stringToLogLevel(const std::string &fileType);

class Logger {
public:
    Logger(const LogInfo &log_info, const std::string &component_name);
    void writeToLog(LogLevel log_level, const std::string &scope, const std::string &message);
private:
    void scanForFiles(const std::string &path, const std::string &component_name);

    std::string pid;
    std::string log_file_name;
    boost::log::sources::severity_logger<boost::log::trivial::severity_level> boost_severity_logger;
};

logger.cpp

using namespace boost::log;
using namespace std;
namespace fs = boost::filesystem;

LogLevel stringToLogLevel(const string &fileType) {
    if (fileType == "TRACE")
        return LOGLEVEL_TRACE;
    if (fileType == "DEBUG")
        return LOGLEVEL_DEBUG;
    if (fileType == "INFO")
        return LOGLEVEL_INFO;
    if (fileType == "WARNING")
        return LOGLEVEL_WARNING;
    if (fileType == "ERROR")
        return LOGLEVEL_ERROR;
    throw invalid_argument("Unknown file type");
}

trivial::severity_level logLevelToBoostLogLevel(const LogLevel log_level) {
    if (log_level == LOGLEVEL_TRACE)
        return trivial::trace;
    if (log_level == LOGLEVEL_DEBUG)
        return trivial::debug;
    if (log_level == LOGLEVEL_INFO)
        return trivial::info;
    if (log_level == LOGLEVEL_WARNING)
        return trivial::warning;
    if (log_level == LOGLEVEL_ERROR)
        return trivial::error;
    throw invalid_argument("Unknown log type");
}

Logger::Logger(const LogInfo &log_info, const string &component_name) {
    scanForFiles(log_info.log_path, component_name);
    stringstream s;
    s << log_info.log_path << component_name << "_%N.log";
    add_file_log(
        keywords::file_name = s.str(),
        keywords::rotation_size = log_info.log_file_size,
        keywords::max_size = log_info.log_file_amount * log_info.log_file_size,
        keywords::target = log_info.log_path,
        keywords::open_mode = std::ios::out | std::ios::app,
        keywords::auto_flush = true,
        keywords::format =
        expressions::format("[%1%] [%2%] [%3%] [%4%] %5%")
        % expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f")
        % expressions::attr<unsigned int>("ThreadID")
        % expressions::attr<string>("Scope")
        % trivial::severity
        % expressions::smessage
    );

    trivial::severity_level requested_level = logLevelToBoostLogLevel(log_info.log_level);
    core::get()->set_filter(
        trivial::severity >= requested_level
    );
    add_common_attributes();
}

void Logger::writeToLog(LogLevel log_level, const std::string &scope, const std::string &message) {
    BOOST_LOG_SCOPED_THREAD_ATTR("ThreadID", attributes::constant<unsigned int>(OS::getTid()));
    BOOST_LOG_SCOPED_THREAD_ATTR("Scope", attributes::constant<string>(scope));
    BOOST_LOG_SEV(this->boost_severity_logger, logLevelToBoostLogLevel(log_level))<< message.c_str();
}

Is it possible to achieve what I want?

Upvotes: 2

Views: 2363

Answers (2)

Keivan
Keivan

Reputation: 1781

To separate log files you can add filter to the added log file, in that way file rotation will work fine and each of your components will have their own log file:

logging::add_file_log(
        keywords::file_name = s.str(),
        keywords::rotation_size = log_info.log_file_size,
        keywords::max_size = log_info.log_file_amount * log_info.log_file_size,
        keywords::filter = expr::attr< std::string >([ATTRIBUTE/TAG]) == [ex. COMPONENT_NAME],
        keywords::target = log_info.log_path,
        keywords::open_mode = std::ios::out | std::ios::app,
        keywords::auto_flush = true,
        keywords::format =
        expressions::format("[%1%] [%2%] [%3%] [%4%] %5%")
        % expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f")
        % expressions::attr<unsigned int>("ThreadID")
        % expressions::attr<string>("Scope")
        % trivial::severity
        % expressions::smessage
    );

While logging the attribute should be set and so the message will be written on corresponding log file, for instance:

void Logger::writeToLog(LogLevel log_level, const std::string &scope, const std::string &message) {

...

BOOST_LOG_SCOPED_THREAD_TAG([ATT/TAG], [VALUE]); // Add Component name here

    BOOST_LOG_SEV(this->boost_severity_logger, logLevelToBoostLogLevel(log_level))<< message.c_str();
}

Upvotes: 1

Andrey Semashev
Andrey Semashev

Reputation: 10614

See this reply. Among other things, it describes how to use channels and filters to achieve what you want.

Upvotes: 1

Related Questions