c++ - boost 日志 : How to prevent the output will be duplicated to all added streams when it uses the add_file_log() function?

标签 c++ c++11 boost boost-log boost-logging

我使用 add_file_log() 函数来初始化一个日志接收器,它将日志记录存储到一个文本文件中。当我定义多个接收器时,我观察到:

  • 为每个接收器创建一个文件。
  • 输出被复制到所有文件。

这是我的记录器:

class logger
{
public:
  logger(const logger&) =delete;
  logger(logger&&) =delete;
  logger& operator=(const logger&) =delete;
  logger& operator=(logger&&) =delete;

  static logger& get_instance(
    const std::string& file,
    bool console
  )
  {
      boost::log::register_simple_formatter_factory<
                                                    boost::log::trivial::severity_level,
                                                    char
                                                   >("Severity");

      std::string the_format = "[%TimeStamp%] (%LineID%) [%Severity%]: %Message%";

      if(!file.empty()) {
        boost::log::add_file_log(
          boost::log::keywords::file_name = file + "_%N.log",
          boost::log::keywords::rotation_size = 10 * 1024 * 1024,
          boost::log::keywords::time_based_rotation =
            boost::log::sinks::file::rotation_at_time_point(0, 0, 0),
          boost::log::keywords::auto_flush = true,
          //boost::log::keywords::open_mode = (std::ios::out | std::ios::app),
          boost::log::keywords::format = the_format
        );
      }

      boost::log::add_common_attributes();
      static logger instance{ the_format, console };
      return instance;
  }

  void log(
    const std::string& msg
  )
  {
      BOOST_LOG_SEV ( m_log_, boost::log::trivial::info ) << msg;
  }

private:
  boost::log::sources::severity_logger<
                                       boost::log::trivial::severity_level
                                      > m_log_;

  logger(
    const std::string& format,
    bool console
  )
  {
      if(console) {
        boost::log::add_console_log(
          std::clog,
          boost::log::keywords::format = format
        );
      }
  }
}; // logger

这是我的 main() 函数:

void test(
  const std::string& file
)
{
  logger& lg1 = logger::get_instance( file, false );
  lg1.log( "Hello" );
  lg1.log( "World" );
  lg1.log( "Bye" );
} // test

int main()
{
  unsigned char result = EXIT_SUCCESS;

  try
  {
    std::string file1 = "a.txt",
                file2 = "b.txt";
    logger& lg = logger::get_instance( file1, false );

    for(int i = 1; i<=10; i++) {
       lg.log( std::to_string(i) );
       if(i == 5) {
         test( file2 );
       }
    }
  }
  catch ( std::exception& e )
  {
    std::cerr << "Error: " << e.what() << std::endl;
    result = EXIT_FAILURE;
  }
  return result;
}

运行示例后,文件包含:

a.txt_0.log

[2016-Aug-31 11:49:48.584353] (1) [info]: 1
[2016-Aug-31 11:49:48.585376] (2) [info]: 2
[2016-Aug-31 11:49:48.585418] (3) [info]: 3
[2016-Aug-31 11:49:48.585442] (4) [info]: 4
[2016-Aug-31 11:49:48.585462] (5) [info]: 5
[2016-Aug-31 11:49:48.585505] (6) [info]: Hello  <-- 
[2016-Aug-31 11:49:48.585610] (7) [info]: World  <-- Generated by second logger
[2016-Aug-31 11:49:48.585672] (8) [info]: Bye    <--
[2016-Aug-31 11:49:48.585709] (9) [info]: 6
[2016-Aug-31 11:49:48.585744] (10) [info]: 7
[2016-Aug-31 11:49:48.585777] (11) [info]: 8
[2016-Aug-31 11:49:48.585813] (12) [info]: 9
[2016-Aug-31 11:49:48.585842] (13) [info]: 10

b.txt_0.log

[2016-Aug-31 11:49:48.585505] (6) [info]: Hello
[2016-Aug-31 11:49:48.585610] (7) [info]: World
[2016-Aug-31 11:49:48.585672] (8) [info]: Bye
[2016-Aug-31 11:49:48.585709] (9) [info]: 6    <--
[2016-Aug-31 11:49:48.585744] (10) [info]: 7   <--
[2016-Aug-31 11:49:48.585777] (11) [info]: 8   <-- Generated by the first logger
[2016-Aug-31 11:49:48.585813] (12) [info]: 9   <--
[2016-Aug-31 11:49:48.585842] (13) [info]: 10  <--

如何防止这种行为?我希望每个文件只存储由其关联的记录器生成的信息。

最佳答案

您似乎对 Boost.Log 的工作原理有误解。

。源获取数据(例如字符串)并用它创建一个条目。然后将条目提供给核心,后者将其分派(dispatch)给所有接收器。然后接收器可以过滤、格式化条目并将其输出到他们想要的任何地方,例如 stdout 或文件。

来源 的一个示例是您正在使用的 severity_logger。您可能习惯于使用术语“记录器”而不是“源”,但“记录器”并不是很准确,因为记录是一个多阶段过程。

您通常不必创建多个(“记录器”)。相反,您可以添加多个全局接收器。在您的情况下,每个文件都需要一个过滤接收器。

                                  +--------------+
                            +---> | console sink | ----> stdout
                            |     +--------------+
                            |
+--------+      +------+    |     +--------------+
| source | ---> | core | ---+---> | file sink    | ----> log1.txt
+--------+      +------+    |     +--------------+
                            |
                            |     +--------------+
                            +---> | file sink    | ----> log2.txt
                                  +--------------+

现在,您可以有多个源,每个源都有自己的线程模型、属性、字符类型等,但它们仍然会全部生成条目并将它们提供给核心。在您的情况下,它不会很有用。

让我们把标题移开:

#include <string>
#include <fstream>
#include <boost/log/sinks.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>

namespace bl = boost::log;

让我们开始吧:

BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string);

日志条目具有属性,每次记录内容时都可以设置这些属性。这些属性通常用于格式化(例如 "[%TimeStamp%] [%Message%]"),但我们将添加一个新属性以允许区分不同的文件。我将该属性称为“标签”。

using logger_type = bl::sources::severity_logger<bl::trivial::severity_level>;
static logger_type g_logger;

const std::string g_format = "[%TimeStamp%] (%LineID%) [%Severity%] [%Tag%]: %Message%";

现在,在这个例子中,实际的 boost 记录器是一个全局对象 (g_logger)。您可能希望限制其范围并将其传递给您自己的 logger 对象。我还将格式设置为全局常量。 YMMV.

这是 logger 类:

class logger
{
public:
    logger(std::string file)
        : tag_(file)
    {
        using backend_type = bl::sinks::text_file_backend;
        using sink_type = bl::sinks::synchronous_sink<backend_type>;
        namespace kw = bl::keywords;

        auto backend = boost::make_shared<backend_type>(
            kw::file_name = file + "_%N.log",
            kw::rotation_size = 10 * 1024 * 1024,
            kw::time_based_rotation = bl::sinks::file::rotation_at_time_point(0, 0, 0),
            kw::auto_flush = true);

        auto sink = boost::make_shared<sink_type>(backend);
        sink->set_formatter(bl::parse_formatter(g_format));
        sink->set_filter(tag_attr == tag_);

        bl::core::get()->add_sink(sink);
    }

    void log(const std::string& s)
    {
        BOOST_LOG_SCOPED_THREAD_TAG("Tag", tag_);
        BOOST_LOG_SEV(g_logger, bl::trivial::info) << s;
    }

private:
    const std::string tag_;
};

我已经使用文件名作为标签,但它可以是任何其他名称,只要它是唯一的即可。每个日志条目都会将此标记作为属性,将在接收器过滤器中使用。

首先,创建一个 text_file_backend 并将其提供给一个新的接收器,然后将其添加到核心中。这实际上是调用 add_file_log() 时发生的情况,它只是一个辅助函数。我重复使用了您在示例中使用的相同参数(文件名模式、旋转等)

有趣的是这一行:

sink->set_filter(tag_attr == tag_);

这里,tag_attr 在上面被定义为一个属性关键字。关键字在 Boost.Log 中有点不寻常:它们可用于创建将在运行时计算的表达式。在这种情况下,接收器将只接受 tag_attr == tag_ 的条目。因此,当记录器记录某些内容时,它会将自己的标签设置为属性,而接收器将忽略任何没有此标签的内容。在 log() 中,您可以看到设置了 “Tag” 属性。

这是 main():

int main()
{
    bl::register_simple_formatter_factory<bl::trivial::severity_level, char>("Severity");
    boost::log::add_common_attributes();

    bl::add_console_log(std::clog, bl::keywords::format=g_format);

    logger lg1("1");
    logger lg2("2");

    lg1.log("a");
    lg1.log("b");
    lg1.log("c");

    lg2.log("d");
    lg2.log("e");
    lg2.log("f");
}

您会看到我已将常用内容移到 logger 之外,因为它实际上不属于那里。条目 “a”“b”“c” 将被写入 “1_0.txt” 并且“d”“e”“f”“2_0.txt”。所有六个条目都将写入控制台。

     +--------------+
     | lg1.log("a") |
     +--------------+
            |
            v
+-------------------------+
| Entry:                  |
|   Timestamp: 1472660811 |
|   Message:   "a"        |
|   LineID:    1          |
|   Severity:  info       |
|   Tag:       "1"        |
+-------------------------+
            |            
            v                 +----------------------+
         +------+             | console sink         |
         | core | -----+----> |   file: stdout       |  --> written
         +------+      |      |   filter: none       |
                       |      +----------------------+
                       |      
                       |      +----------------------+
                       |      | file sink            |
                       +----> |   file: "1_0.txt"    |  --> written
                       |      |   filter: tag == "1" |
                       |      +----------------------+
                       |      
                       |      +----------------------+
                       |      | file sink            |
                       +----> |   file: "2_0.txt"    |  --> discarded
                              |   filter: tag == "2" |
                              +----------------------+

关于c++ - boost 日志 : How to prevent the output will be duplicated to all added streams when it uses the add_file_log() function?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39247778/

相关文章:

C++ 类成员函数到 C 结构函数指针

c++ - 不确定将每个加载为 2 个字节的 double 值规范化

c++ - 如何将 ceres::CubicInterpolator 与不在统一网格上的数据一起使用

c++ - 使用 condition_variable 控制多线程流

c++ - 如何将 CUDA 集成到现有的类结构中?

c++ - 如何抛出文件和行号错误?

c++ - boost install windows msvc 10.0 -- example.cpp 链接失败

c++ - G++ 未解析的标识符,找不到链接器错误

c++ - shared_from_this 导致 bad_weak_ptr

c++ - 在 C++1y 中从模板化基派生的类型上对 unique_ptr 使用 std::move