Boost.log: как предотвратить дублирование вывода во все добавленные потоки при использовании функции add_file_log ()?

Я использую 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  <--

Как я могу предотвратить это поведение? Я хочу, чтобы каждый файл сохранял только информацию, сгенерированную связанным логгером

7

Решение

Похоже, вы неправильно понимаете, как работает Boost.Log.

Есть источники а также умывальники. Источник берет данные, такие как строка, и создает запись с этим. Запись затем передается ядро, который отправляет его во все раковины. Затем приемники могут фильтровать, форматировать и выводить записи куда угодно, например 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%";

Теперь в этом примере фактический логгер буста является глобальным объектом (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" |
+----------------------+
19

Другие решения

Других решений пока нет …