Boost.Log 更改频道名称未知的每个频道的过滤器

Boost.Log change filter per channel with unknown channel names

在 Boost.Log 我想根据频道设置过滤器。使用此 example 我使用 text_file_backend 实现。但是在我的程序中,频道名称是由用户作为输入参数给出的。所以我决定实现一种为频道设置严重性过滤器的方法。

commons.h

#ifndef COMMONS_H_
#define COMMONS_H_

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/sinks/syslog_backend.hpp>

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

#include <boost/thread/thread.hpp>


namespace logging = boost::log;
namespace src = boost::log::sources;
namespace sinks = boost::log::sinks;
namespace expr = boost::log::expressions;
namespace keywords = boost::log::keywords;

enum severity_levels
{
    normal,
    notification,
    warning,
    error,
    critical
};

// Define the attribute keywords
BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_levels)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)

typedef expr::channel_severity_filter_actor< std::string, severity_levels > 
min_severity_filter;
typedef src::severity_channel_logger_mt< severity_levels, std::string > 
logger_type_mt;

BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(test_lg, logger_type_mt)

typedef sinks::synchronous_sink< sinks::text_file_backend > File_sink;

#define ADD_LOG(severity, channel, msg, args...) add_log_message(__FILE__, __LINE__, severity, channel, boost::this_thread::get_id(), msg, args)

#define MY_GLOBAL_LOGGER(log_, channel, sv, file, line, thread) BOOST_LOG_CHANNEL_SEV( log_, channel, sv) \
<< boost::log::add_value("Line", line)      \
<< boost::log::add_value("File", file)       \
<< boost::log::add_value("Thread_id", thread)    


std::ostream& operator<< (std::ostream& strm, severity_levels level)
{
    static const char* strings[] =
    {
        "normal",
        "notification",
        "warning",
        "error",
        "critical"
    };

    if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
        strm << strings[level];
    else
        strm << static_cast< int >(level);

    return strm;
}

#endif

Logger.h

#ifndef LOGGER_H_
#define LOGGER_H_

#define BOOST_LOG_DYN_LINK 1

#include <string.h>
#include <ctime>
#include <chrono>

#include "commons.h"

class Logger
{
public:
    static min_severity_filter min_severity;
    static void init_logging()
    {
        logging::add_common_attributes();

        // Create a text file sink
        boost::shared_ptr< sinks::text_file_backend> backend(new sinks::text_file_backend());
        backend->set_file_name_pattern<std::string>("sample_%N.log");
        backend->set_rotation_size(2 * 1024 * 1024);

        boost::shared_ptr< File_sink > sink(new File_sink(backend));

        // Set up where the rotated files will be stored
        init_file_collecting <File_sink>(sink);
        sink->set_formatter(&file_log_formatter);
        sink->locked_backend()->scan_for_files();
        logging::core::get()->add_sink(sink);   
        logging::core::get()->set_filter(min_severity || severity >= normal);
    }

    template <typename T>
    static void init_file_collecting(boost::shared_ptr< T > sink)
    {
        sink->locked_backend()->set_file_collector(sinks::file::make_collector(
            keywords::target = "logs",                      /*< the target directory >*/
            keywords::max_size = 64 * 1024 * 1024,          /*< maximum total size of the stored files, in bytes >*/
            keywords::min_free_space = 100 * 1024 * 1024    /*< minimum free space on the drive, in bytes >*/
        ));
    }

    static void file_log_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
    {
        // Get the LineID attribute value and put it into the stream
        strm << logging::extract< unsigned int >("LineID", rec) << ": ";

        // TimeStamp
        strm << "[";

        strm << logging::extract<boost::posix_time::ptime>("TimeStamp", rec);
        strm << "]";

        // thread id
        strm << "[" << logging::extract< boost::thread::id >("Thread_id", rec) << "] ";
        strm << "[" << rec[channel] << "] ";
        strm << "[";
        strm << logging::extract< int >("Line", rec) << ", ";
        logging::value_ref< std::string > fullpath = logging::extract< std::string >("File", rec);
        strm << boost::filesystem::path(fullpath.get()).filename().string() << "] ";

        // The same for the severity level.
        // The simplified syntax is possible if attribute keywords are used.
        strm << "<" << rec[severity] << "> ";

        // Finally, put the record message to the stream
        strm << rec[expr::smessage];
    }

    static void set_channel_filter(std::string channel, severity_levels min_level)
    {
        min_severity[channel] = min_level;
        logging::core::get()->set_filter(min_severity);
    }

    static void add_log_message(const char* file, int line, severity_levels severity,
                                    std::string channel, boost::thread::id thread_id,
                                    const char* message, ...)
    {
        char buffer[256];
        va_list ap;
        va_start(ap, message);
        vsnprintf(buffer, 256, message, ap);
        MY_GLOBAL_LOGGER(test_lg::get(), channel, severity, file, line, thread_id) << buffer;
        va_end(ap);
    }
};

#endif
min_severity_filter Logger::min_severity = expr::channel_severity_filter(channel, severity);

在程序的第一个调用 init_logging() 将所有通道的过滤器设置为正常。

问题是当我用一些输入(例如 "CHANNEL_1",警告)调用 set_channel_filter() 时,我希望只为 "CHANNEL_1" 设置过滤器,但过滤是为所有可能的通道设置的. (例如 "CHANNEL_2, etc). When I add for example "OTHER_CHANNEL" 手动到 set_channel_filter() 它适用于它。我想让 c++ 映射像数据结构一样保存每个通道的所有严重性过滤器。并且任何时候用户调用以添加一个带有过滤器的新频道或现有频道,它只会更改特定频道的过滤,而不是所有频道。

main.cpp

int main()
{
    Logger::init_logging();

    Logger::ADD_LOG(severity_levels::normal, "NETWORK", "a warning message with id %d", 34);
    Logger::ADD_LOG(severity_levels::notification, "NETWORK", "a warning message with id %d", 65);

    Logger::ADD_LOG(severity_levels::notification, "GENERAL", "a notification message with id %d", 12);
    Logger::ADD_LOG(severity_levels::warning, "GENERAL", "a warning message with id %d", 13);

    // Logs in GENERAL category must have severity level of warning or higher in-order to record.
    Logger::set_channel_filter("GENERAL", severity_levels::warning);

    Logger::ADD_LOG(severity_levels::notification, "GENERAL", "a notification message with id %d", 14);
    for (int i = 0; i < 2; i++)
    {
        Logger::ADD_LOG(severity_levels::warning, "GENERAL", "a warning message with id %d", 15);
    }

    Logger::ADD_LOG(severity_levels::normal, "NETWORK", "a warning message with id %d", 34); // Expected to sent to file. but filtered!!
    Logger::ADD_LOG(severity_levels::notification, "NETWORK", "a warning message with id %d", 65); //Also filtered !!
}

问题是最初,在 init_logging 中,您按如下方式设置过滤器:

logging::core::get()->set_filter(min_severity || severity >= normal);

此时,min_severity 为空,因为您尚未添加任何 channels/severity 阈值。默认情况下,当在 channel/severity 映射中找不到通道时,min_severity 将 return false,这意味着您在此处设置的过滤器实际上是 severity >= normal

稍后,当您调用 set_channel_filter 时,您将第一个条目添加到 min_severity 映射中,以便它适用于 "GENERAL" 频道,但不适用于任何其他频道。但是,您这次设置了不同的过滤器:

logging::core::get()->set_filter(min_severity);

这一次,如果 min_severity returns false 日志记录被丢弃,这就是 "NETWORK" 通道中最后两条记录所发生的情况。您需要每次都设置相同的过滤器表达式才能获得一致的行为。