如何禁用 Boost Log 的自动刷新

How do you disable auto-flush for Boost Log

我正在使用 Boost.Log 将各种数据集记录到不同的文件中。我想为某些文件启用 auto_flush 功能,但对其他文件禁用(这样就不会在每个连续的日志语句中插入换行符)。我无法在我的大型项目中使用它。所以我将问题简化为一个文件,但似乎 auto_flush 仍然无法禁用。这是一个最小示例的代码:

test.hpp:

#include <fstream>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/trivial.hpp>

void init();

test.cpp:

#include "test.hpp"

void init() {
    // Initialize sink.
    typedef boost::log::sinks::synchronous_sink<boost::log::sinks::text_ostream_backend> text_sink;
    // Grab the Boost Log core.
    auto coreHandle = boost::log::core::get();
    // Add stream 1.
    boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();
    sink->locked_backend()->add_stream(
            boost::make_shared<std::ofstream>("category1.log"));
    sink->locked_backend()->auto_flush(false);
    coreHandle->add_sink(sink);
}

main.cpp:

#include <iostream>
#include <string>
#include "test.hpp"

// Main entry point to program.
int main(int numArg, char const * const arguments[]) {

    double number1 = 42;

    init();

    BOOST_LOG_TRIVIAL(info) << "This message should go to category 1 log..." << number1;
    BOOST_LOG_TRIVIAL(info) << "This message should also go to category 1 log on the same line..." << number1;

    return EXIT_SUCCESS;
}

写入 category1.log 的输出显示在对 BOOST_LOG_TRIVIAL 的两次调用之间应用了换行符,即使我在 init() 中明确将 auto_flush 设置为 false功能:

This message should go to category 1 log...42
This message should also go to category 1 log on the same line...42

我也使用 BOOST_LOG_CHANNEL_SEV 登录到多个文件,但是将 auto_flush 设置为 false 似乎仍然没有效果...

  1. 如何禁用 auto_flush,并将连续的日志语句打印到日志文件中的同一行?
  2. 是否可以将解决方案扩展到多个日志文件,这样您就可以为某些文件启用 auto_flush 而不是其他文件?

How do you disable auto_flush, and have consecutive log statements print to the same line in the log file?

首先,auto_flush与每条日志记录后的尾随换行符无关。它使接收器在写入每个日志记录后刷新其缓冲区,无论它是否包含换行符。其次,auto_flush 只能在每个接收器的基础上启用或禁用。在 text_ostream_backend 的特殊情况下,这意味着要么刷新所有连接到接收器的流,要么刷新其中的 none 个。第三,尾随换行符是sink后端内部输出的,目前无法禁用此行为。

如果您只想在 select 条日志记录后刷新日志文件,最好的方法是使用一个属性来指示何时需要刷新。然后,您将必须创建自己的接收器后端,以检查它处理的日志记录中的该属性并采取适当的行动。描述了如何创建接收器 here

Can a solution be scaled to multiple log files, such that you can enable auto_flush for some files, but not others?

当然可以。您必须为每个文件创建一个接收器,并在这些接收器中相应地设置 auto_flush

2019-10-17更新:

关于尾随换行插入的进展。在接收器后端的 Boost 1.71, in text-based sinks there appeared an option to disable automatic trailing newline insertion. See auto_newline_mode 枚举和 set_auto_newline_mode 方法中。

2019 年 10 月更新:

Boost 1.71 的新答案

Boost.Log 现在提供 set_auto_newline_mode 功能来控制如何为每个接收器后端处理新行。这消除了对 custom 后端的需要(在我的旧答案中实现)。现在,我们可以轻松地创建一个带有自动换行 disabled 的日志记录后端。 auto_newline_mode 选项是:

  • auto_newline_mode::disabled_auto_newline
  • auto_newline_mode::always_insert
  • auto_newline_mode::insert_if_missing

这是我的 multi-file Boost.Log example 的一个更简单的版本,它测试了这个。 Category1 日志具有正常的自动换行符。 Category2 日志不打印,因为它的严重级别不够。最后,Category3 日志禁用了自动换行,所以连续的日志放在同一行。

#include <iostream>
#include <string>
#include <fstream>

#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions/keyword.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/keywords/severity.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

#include <boost/filesystem.hpp>

// Includes from the example: http://www.boost.org/doc/libs/1_62_0/libs/log/example/doc/extension_stat_collector.cpp
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/phoenix.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/attributes/value_visitation.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>

// Includes from example: https://www.ociweb.com/resources/publications/sett/may-2016-boostlog-library/
#include <boost/log/sources/global_logger_storage.hpp>

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

BOOST_LOG_GLOBAL_LOGGER(Channel1Logger, src::severity_channel_logger<logging::trivial::severity_level>);
BOOST_LOG_GLOBAL_LOGGER(Channel2Logger, src::severity_channel_logger<logging::trivial::severity_level>);
BOOST_LOG_GLOBAL_LOGGER(Channel3Logger, src::severity_channel_logger<logging::trivial::severity_level>);

BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel1Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category1"));
BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel2Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category2"));
BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel3Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category3"));

#define LOG_CATEGORY1(LEVEL) BOOST_LOG_SEV(Channel1Logger::get(), logging::trivial::LEVEL)
#define LOG_CATEGORY2(LEVEL) BOOST_LOG_SEV(Channel2Logger::get(), logging::trivial::LEVEL)
#define LOG_CATEGORY3(LEVEL) BOOST_LOG_SEV(Channel3Logger::get(), logging::trivial::LEVEL)

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)


/** Initialize the Boost.Log logging. */
void init() {
    // Alias the backend and sink types.
    typedef boost::log::sinks::text_ostream_backend MyBackendType;
    typedef boost::log::sinks::synchronous_sink< MyBackendType > MyBackendSinkType;
    // Grab the Boost Log core.
    auto coreHandle = boost::log::core::get();
    // Define the path where the log files should reside.
    boost::filesystem::path destinationDir("D:\test");

    // Create a minimal severity table filter
    typedef expr::channel_severity_filter_actor< std::string, logging::trivial::severity_level > min_severity_filter;
    min_severity_filter minSeverity = expr::channel_severity_filter(channel, severity);

    // Set up the minimum severity levels for different channels.
    minSeverity["Category1"] = logging::trivial::info;
    minSeverity["Category2"] = logging::trivial::info;
    minSeverity["Category3"] = logging::trivial::info;

    // Define log file 1.
    boost::filesystem::path logFile1(destinationDir / "category1.log");
    // Create a log backend, and add a stream to it.
    boost::shared_ptr< MyBackendType > customBackend(new MyBackendType());
    customBackend->add_stream(boost::shared_ptr< std::ostream >(
            new std::ofstream(logFile1.string().c_str())));
    // Enable auto flush for this backend.
    customBackend->auto_flush(true);
    // Create a sink with the custom backend.
    boost::shared_ptr< MyBackendSinkType > customSink(new MyBackendSinkType(customBackend));
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category1") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);

    // Define log file 2.
    boost::filesystem::path logFile2(destinationDir / "category2.log");
    // Create a log backend, and add a stream to it.
    customBackend = boost::make_shared< MyBackendType >();
    customBackend->add_stream(boost::shared_ptr< std::ostream >(
            new std::ofstream(logFile2.string().c_str())));
    // Enable auto flush for this backend.
    customBackend->auto_flush(true);
    // Create a sink with the custom backend.
    customSink = boost::make_shared< MyBackendSinkType >(customBackend);
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category2") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);

    // Define log file 3.
    boost::filesystem::path logFile3(destinationDir / "category3.log");
    // Create a log backend, and add a stream to it.
    customBackend = boost::make_shared< MyBackendType >();
    customBackend->add_stream(boost::shared_ptr< std::ostream >(
            new std::ofstream(logFile3.string().c_str())));
    // Enable auto flush for this backend.
    customBackend->auto_flush(true);
    // Disable the auto newline for this backend.
    customBackend->set_auto_newline_mode(boost::log::sinks::auto_newline_mode::disabled_auto_newline);
    // Create a sink with the custom backend.
    customSink = boost::make_shared< MyBackendSinkType >(customBackend);
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category3") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);
}

// This is the main entry point to the program.
int main (int numArgs, char const * const argList) {
    double number1 = 42;

    // Initialize the Boost.Log logging.
    init();

    // The Category1 logger has normal auto-newline. 
    LOG_CATEGORY1(info) << "New Category1 log1.";
    LOG_CATEGORY1(info) << "New Category1 log2.";
    // Category2 logger won't print to file b/c doesn't meet severity requirements.
    LOG_CATEGORY2(trace) << "New Category2 log.";

    // Category3 logger has auto-newline disabled. 
    LOG_CATEGORY3(info) << "[Put this on line 1]";
    LOG_CATEGORY3(info) << "[Put this on line 1 also]" << std::endl;
    LOG_CATEGORY3(info) << "[Put this on line 2]";

    std::cout << "Successful Completion!" << std::endl;
    return EXIT_SUCCESS;
}

Boost 版本 <1.71

的旧答案

通过@Andrey 的回答,我能够创建一个自定义接收器后端,允许用户将可选的“NoNewline”值传递给某些日志以删除换行符 (\n)。这是通过 add_value Boost.Log 关键字实现的。这是一个使用 severity_channel_logger 通过严重性和通道过滤日志的解决方案示例,其中特定通道的日志将发送到特定文件。我从其他示例中提取了这个解决方案,并在代码中引用了这些示例。

main.cpp:

#include <iostream>
#include <string>
#include <fstream>

#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions/keyword.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/keywords/severity.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

#include <boost/filesystem.hpp>

// Includes from the example: http://www.boost.org/doc/libs/1_62_0/libs/log/example/doc/extension_stat_collector.cpp
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/phoenix.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/attributes/value_visitation.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>

// Includes from example: https://www.ociweb.com/resources/publications/sett/may-2016-boostlog-library/
#include <boost/log/sources/global_logger_storage.hpp>

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

BOOST_LOG_GLOBAL_LOGGER(Channel1Logger, src::severity_channel_logger<logging::trivial::severity_level>);
BOOST_LOG_GLOBAL_LOGGER(Channel2Logger, src::severity_channel_logger<logging::trivial::severity_level>);
BOOST_LOG_GLOBAL_LOGGER(Channel3Logger, src::severity_channel_logger<logging::trivial::severity_level>);

BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel1Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category1"));
BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel2Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category2"));
BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS(Channel3Logger, src::severity_channel_logger<logging::trivial::severity_level>, (keywords::channel = "Category3"));

#define LOG_CATEGORY1(LEVEL) BOOST_LOG_SEV(Channel1Logger::get(), logging::trivial::LEVEL)
#define LOG_CATEGORY2(LEVEL) BOOST_LOG_SEV(Channel2Logger::get(), logging::trivial::LEVEL)
#define LOG_CATEGORY3(LEVEL) BOOST_LOG_SEV(Channel3Logger::get(), logging::trivial::LEVEL)

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)


// The backend collects records being forwarded from the front-end loggers.
class MyCustomBackend : public sinks::basic_sink_backend< 
        sinks::combine_requirements<
            sinks::synchronized_feeding,            
            sinks::flushing                         
        >::type>
{
private:
    // The file to write the collected information to.
    std::ofstream logFile;

public:
    // The constructor initializes the internal data
    explicit MyCustomBackend(const char * file_name);

    // The function consumes the log records that come from the frontend
    void consume(logging::record_view const& rec);
    // The function flushes the file
    void flush();

};

// The constructor initializes the internal data
MyCustomBackend::MyCustomBackend(const char * file_name) :
        logFile(file_name) {
    if (!logFile.is_open()) {
        throw std::runtime_error("Could not open the specified file!");
    }
}

// This function consumes the log records that come from the frontend.
void MyCustomBackend::consume(logging::record_view const & rec) {
    // If the NoNewline attribute is present, skip the newline.
    if (rec.attribute_values().count("NoNewline")) {
        logFile << *rec[boost::log::expressions::smessage];
    } else {
        logFile << *rec[boost::log::expressions::smessage] << std::endl;
    }
    // This is the equivalent of setting auto_flush.
    this->flush();
}

/** The function flushes the file. */
void MyCustomBackend::flush() {
    logFile.flush();
}

/** Initialize the Boost.Log logging. */
void init() {
    // Alias the custom sink types.
    typedef boost::log::sinks::synchronous_sink<MyCustomBackend> CustomBackendType;
    // Grab the Boost Log core.
    auto coreHandle = boost::log::core::get();
    // Define the path where the log files should reside.
    boost::filesystem::path destinationDir("C:\test");

    // Create a minimal severity table filter
    typedef expr::channel_severity_filter_actor< std::string, logging::trivial::severity_level > min_severity_filter;
    min_severity_filter minSeverity = expr::channel_severity_filter(channel, severity);

    // Set up the minimum severity levels for different channels.
    minSeverity["Category1"] = logging::trivial::info;
    minSeverity["Category2"] = logging::trivial::info;
    minSeverity["Category3"] = logging::trivial::info;

    // Define log file 1.
    boost::filesystem::path logFile1(destinationDir / "category1.log");
    // Create a custom backend.
    boost::shared_ptr<MyCustomBackend> customBackend(new MyCustomBackend(logFile1.string().c_str()));
    // Create a sink with the custom backend.
    boost::shared_ptr<CustomBackendType> customSink(new CustomBackendType(customBackend));
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category1") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);
    
    // Define log file 2.
    boost::filesystem::path logFile2(destinationDir / "category2.log");
    // Create a custom backend.
    customBackend = boost::make_shared<MyCustomBackend>(logFile2.string().c_str());
    // Create a sink with the custom backend.
    customSink = boost::make_shared<CustomBackendType>(customBackend);
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category2") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);

    // Define log file 3.
    boost::filesystem::path logFile3(destinationDir / "category3.log");
    // Create a custom backend.
    customBackend = boost::make_shared<MyCustomBackend>(logFile3.string().c_str());
    // Create a sink with the custom backend.
    customSink = boost::make_shared<CustomBackendType>(customBackend);
    // Add a filter to the sink.
    customSink->set_filter((channel == "Category3") && minSeverity && (severity >= logging::trivial::info));
    // Add the sink to the Boost.Log core.
    coreHandle->add_sink(customSink);
}

int main (int numArgs, char const * const argList) {
    double number1 = 42;

    // Initialize the Boost.Log logging.
    init();

    LOG_CATEGORY1(info) << "New Category1 log.";
    // Won't print to file b/c doesn't meet severity requirements.
    LOG_CATEGORY2(trace) << "New Category2 log.";

    // Remove newline character ('\n') from specific logs. 
    LOG_CATEGORY3(info) << logging::add_value("NoNewline", true) << "[Put this on line 1]";
    LOG_CATEGORY3(info) << "[Put this on line 1 also]";
    LOG_CATEGORY3(info) << "[Put this on line 2]";

    return EXIT_SUCCESS;
}