Boost.log: 当使用add_file_log() 函数时,如何防止输出被复制到所有添加的流中?
Boost.log: How to prevent the output will be duplicated to all added streams when it uses the add_file_log() function?
我使用 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 的工作原理有误解。
有源和汇。源获取数据,例如字符串,并用它创建一个 entry。然后将条目提供给 core,后者将其分派给所有接收器。然后接收器可以过滤、格式化条目并将其输出到他们想要的任何地方,例如 stdout
或文件。
来源 的一个示例是您正在使用的 severity_logger
。您可能习惯使用术语 "logger" 而不是 "source",但是 "logger" 不是很准确,因为日志记录是一个 multi-stage 过程。
您通常不必创建多个 来源 ("loggers")。相反,您可以添加多个全局 接收器 。在您的情况下,每个文件都需要一个过滤接收器。
+--------------+
+---> | console sink | ----> stdout
| +--------------+
|
+--------+ +------+ | +--------------+
| source | ---> | core | ---+---> | file sink | ----> log1.txt
+--------+ +------+ | +--------------+
|
| +--------------+
+---> | file sink | ----> log2.txt
+--------------+
现在,您可以有多个源,每个源都有自己的线程模型、属性、字符类型等,但它们仍然会生成条目并将它们提供给核心。在你的情况下,它不会很有用。
让我们把 headers 移开:
#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%]"
),但我们将添加一个新属性以允许区分不同的文件。我将属性命名为 "Tag".
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 记录器是全局 object (g_logger
)。您可能希望限制其范围并将其传递给您自己的 logger
objects。我还将格式设置为全局常量。 YMMV.
这是logger
class:
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" |
+----------------------+
我使用 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 的工作原理有误解。
有源和汇。源获取数据,例如字符串,并用它创建一个 entry。然后将条目提供给 core,后者将其分派给所有接收器。然后接收器可以过滤、格式化条目并将其输出到他们想要的任何地方,例如 stdout
或文件。
来源 的一个示例是您正在使用的 severity_logger
。您可能习惯使用术语 "logger" 而不是 "source",但是 "logger" 不是很准确,因为日志记录是一个 multi-stage 过程。
您通常不必创建多个 来源 ("loggers")。相反,您可以添加多个全局 接收器 。在您的情况下,每个文件都需要一个过滤接收器。
+--------------+
+---> | console sink | ----> stdout
| +--------------+
|
+--------+ +------+ | +--------------+
| source | ---> | core | ---+---> | file sink | ----> log1.txt
+--------+ +------+ | +--------------+
|
| +--------------+
+---> | file sink | ----> log2.txt
+--------------+
现在,您可以有多个源,每个源都有自己的线程模型、属性、字符类型等,但它们仍然会生成条目并将它们提供给核心。在你的情况下,它不会很有用。
让我们把 headers 移开:
#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%]"
),但我们将添加一个新属性以允许区分不同的文件。我将属性命名为 "Tag".
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 记录器是全局 object (g_logger
)。您可能希望限制其范围并将其传递给您自己的 logger
objects。我还将格式设置为全局常量。 YMMV.
这是logger
class:
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" |
+----------------------+