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()
函数来初始化将日志记录存储到文本文件中的日志记录接收器。当我定义几个接收器,我观察到:
- 为每个接收器创建一个文件。
- 这是我的记录器: pre>
class logger
{
public:
logger(const logger&)= delete;
logger(logger&&)= delete;
logger& operator =(const logger&)= delete;
logger& operator =(logger&&)= delete;
静态记录器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 +_%
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()
function: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)[信息]:1
[2016年8月31日11:49:48.585376](2)[信息]:2
[2016年8月31日11: 49:48.585418](3)[info]:3
[2016年8月31日11:49:48.585442](4)[信息]:4
[2016-Aug-31 11:49: 48.585462](5)[信息]:5
[2016年8月31日11:49:48.585505](6)[信息]:你好< -
[2016年8月31日11: 49:48.585610](7)[info]:World < - 由第二个记录器生成
[2016-Aug-31 11:49:48.585672](8)[info]:Bye < -
[2016年8月31日11:49:48.585709](9)[信息]:6
[2016年8月31日11:49:48.585744] [2016年8月31日11:49:48.585777](11)[信息]:8
[2016年8月31日11:49:48.585813] -Aug-31 11:49:48.585842](13)[info]:10
< b.txt_0.log[2016-Aug-31 11:49:48.585505] ]:Hello
[2016-Aug-31 11:49:48.585610](7)[info]:World
[2016年8月31日11:49:48.585672] Bye
[2016-Aug-31 11:49:48.585709](9)[info]:6 < -
[2016-Aug-31 11:49:48.585744] ]:7 < -
[2016-Aug-31 11:49:48.585777](11)[info]:8 < - 由第一个记录器生成
[2016- 11:49:48.585813](12)[info]:9 < -
[2016-Aug-31 11:49:48.585842](13)[info]:10 < -
如何防止此行为?
解决方案你似乎有一个误解Boost.Log
有源和 sink 。源接收数据(例如字符串),并使用它创建条目。然后将条目给予核心,其将其分派给所有的宿。然后,汇可以过滤,格式化和输出到任何他们想要的地方,如
stdout
或一个文件。
源的示例是您正在使用的
severity_logger
。你可能习惯使用logger而不是source,但是logger不是很精确,因为logging是一个多阶段的过程。
通常不必创建多个源(logger)。相反,您可以添加多个全局 sink 。
+ -----------在这种情况下, --- +
+ ---> |控制台槽| ----> stdout
| + -------------- +
|
+ -------- + + ------ + | + -------------- +
|源| ---> |核心| --- + ---> |文件接收器| ----> log1.txt
+ -------- + + ------ + | + -------------- +
|
| + -------------- +
+ ---> |文件接收器| ----> log2.txt
+ -------------- +
现在,你可以有多个logger,每个都有自己的线程模型,属性,字符类型等,但它们仍然会生成条目并将其提供给核心。
让我们得到标题的方式:
#include< string>
#include< fstream>
#include< boost / log / sinks.hpp>
#include< boost / log / utility / setup / formatter_parser.hpp>
#include< boost / log /源/ severity_channel_logger.hpp>
#include< boost / log / trivial.hpp>
#include< boost / log / utility / setup / file.hpp>
#include& lt; 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>
命名空间bl = boost :: log;
让我们开始:
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr,Tag,std :: string);
一个日志条目具有属性 。这些属性通常用于格式化(如
[%TimeStamp%] [%Message%]
),但我们将添加一个新属性,不同的文件。我使用logger_type =
bl :: sources :: severity_logger< bl :: 来调用属性Tag。trivial :: severity_level> ;;
static logger_type g_logger;
const std :: string g_format =
[%TimeStamp%](%LineID%)[%Severity%] [%Tag%]:%Message%;
现在,在这个例子中,实际的boost记录器是一个全局对象。您可能想限制其范围,并将其传递给您自己的
logger
对象。我也把这个格式作为一个全局常量。 YMMV。
这是
logger
类:
<$ pre>class logger
{
public:
logger(std :: string file);
:tag_(file)
{
使用backend_type = bl :: sinks :: text_file_backend;
using sink_type = bl :: sinks :: synchronous_sink< backend_type> ;;
auto backend = boost :: make_shared< backend_type>(
bl :: keywords :: file_name = file +_%N.log,
bl ::关键字: :rotate_size = 10 * 1024 * 1024,
bl :: keywords :: time_based_rotation =
bl :: sinks :: file :: rotation_at_time_point(0,0,0),
bl :: keywords :: 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
+ ------------------------- +
|条目:|
|时间戳:1472660811 |
|消息:a|
| LineID:1 |
|严重性:info |
|标签:1|
+ ------------------------- +
|
v
+ ------ + + --------------------- +
|核心| ----- + ----> |控制台槽|
+ ------ + | | file:stdout | - >写为
| | filter:none |
| + --------------------- +
|
| + --------------------- +
| |文件接收器|
+ ----> |文件:1_0.txt| - >写为
| | filter:tag ==1|
| + --------------------- +
|
| + --------------------- +
| |文件接收器|
+ ----> |文件:2_0.txt| - >丢弃
| filter:tag ==2|
+ --------------------- +
I use the
add_file_log()
function to initialize a logging sink that stores log records into a text file. When I define several sinks, I have observed:- a file is created for each sink.
- the output is copied to all files.
This is my logger:
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
This is my
main()
function: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; }
After running the example, the files contain:
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 <--
How I can prevent this behavior? I want each file only stores the information generated by its associated logger.
解决方案You seem to have a misunderstanding of how Boost.Log works.
There are sources and sinks. A source takes data, such as a string, and creates an entry with it. The entry is then given to the core, which dispatches it to all the sinks. The sinks can then filter, format and output the entries to wherever they want, such as
stdout
or a file.An example of a source would be the
severity_logger
you are using. You might be used to the term "logger" instead of "source", but "logger" isn't very precise because logging is a multi-stage process.You don't usually have to create multiple sources ("loggers"). Instead, you can add multiple global sinks. In your case, you'll need a filtered sink per file.
+--------------+ +---> | console sink | ----> stdout | +--------------+ | +--------+ +------+ | +--------------+ | source | ---> | core | ---+---> | file sink | ----> log1.txt +--------+ +------+ | +--------------+ | | +--------------+ +---> | file sink | ----> log2.txt +--------------+
Now, you could have multiple loggers, each with their own threading model, attributes, character type, etc., but they would still all generate entries and give them to the core. In your case, it wouldn't be very useful.
Let's get the headers out of the way:
#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;
Let's start:
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string);
A log entry has attributes which can be set every time something is logged. These attributes are usually used in formatting (such as
"[%TimeStamp%] [%Message%]"
), but we'll add a new attribute to allow for differentiating between the different files. I called the attribute "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%";
Now, in this example, the actual boost logger is a global object. You may want to restrict its scope and pass it around to your own
logger
objects. I've also made the the format a global constant. YMMV.This is the
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>; auto backend = boost::make_shared<backend_type>( bl::keywords::file_name = file + "_%N.log", bl::keywords::rotation_size = 10 * 1024 * 1024, bl::keywords::time_based_rotation = bl::sinks::file::rotation_at_time_point(0, 0, 0), bl::keywords::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_; };
I've used the file name as a tag, but it could be anything else as long as it's unique. Every log entry will have this tag as an attribute, which will be used in the sink filter.
First, a
text_file_backend
is created and is given to a new sink, which is then added to the core. This is actually what happens when you calladd_file_log()
, it's just a helper function. I've reused the same parameters you had in your example (filename pattern, rotation, etc.)The interesting line is this one:
sink->set_filter(tag_attr == tag_);
Here,
tag_attr
was defined above as an attribute keyword. Keywords are a bit unusual in Boost.Log: they can be used to create expressions that will be evaluated at runtime. In this case, the sink will only accept entries wheretag_attr == tag_
. So when a logger logs something, it sets its own tag as a attribute, and the sink will ignore anything that doesn't have this tag. Inlog()
, you can see the"Tag"
attribute being set.Here's
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"); }
You'll see that I've moved the common stuff outside of
logger
since it doesn't really belong there. Entries"a"
,"b"
and"c"
will be written to"1_0.txt"
and"d"
,"e"
and"f"
to"2_0.txt"
. All six entries will be written to the console.+--------------+ | lg1.log("a") | +--------------+ | v +-------------------------+ | Entry: | | Timestamp: 1472660811 | | Message: "a" | | LineID: 1 | | Severity: info | | Tag: "1" | +-------------------------+ | v +------+ +---------------------+ | core | -----+----> | console sink | +------+ | | 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" | +---------------------+
这篇关于Boost.log:当使用add_file_log()函数时,如何防止输出将被复制到所有添加的流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!