提升日志和严重性/本地属性

时间:2016-03-09 15:03:45

标签: c++ boost boost-log

我是Boost日志的新手。我有一些简单的工作,但我坚持使用宏来轻松指定文件名和行号。这看起来很难,我想我错过了什么。

我写这篇文章是为了在最后使用演示日志初始化日志记录。请注意,我正在登录到控制台和旋转文件。

console.log($scope.param);

现在我想创建一个宏,以便我可以添加文件和行号,并使其行为像ostream。我希望能够做的是说

logging::add_console_log(std::clog, keywords::format = "%TimeStamp%: %_%",
                         keywords::auto_flush = true);
typedef sinks::synchronous_sink<sinks::text_file_backend> file_sink;
shared_ptr<file_sink> sink(new file_sink(
    keywords::file_name = "%Y%m%d_%H%M%S_%5N.log",
    keywords::rotation_size = 16384, keywords::auto_flush = true));

sink->locked_backend()->set_file_collector(sinks::file::make_collector(
    keywords::target = "logs",
    keywords::max_size = 16 * 1024 * 1024,
    keywords::min_free_space = 100 * 1024 * 1024));

sink->locked_backend()->scan_for_files();
sink->set_formatter(expr::stream
                    << expr::attr<boost::posix_time::ptime>("TimeStamp")
                    << " " << logging::trivial::severity << "["
                    << expr::attr<string>("FileName") << ":"
                    << expr::attr<unsigned int>("LineNumber") << "] "
                    << expr::smessage);
logging::core::get()->add_sink(sink);
logging::add_common_attributes();
logging::core::get()->add_global_attribute("TimeStamp",
                                           attrs::local_clock());

src::severity_logger<logging::trivial::severity_level> slg;
lg.add_attribute("LineNumber", attrs::constant<unsigned int>(__LINE__));
lg.add_attribute("FileName", attrs::constant<string>(__FILE__));
for (unsigned int i = 0; i < 5; ++i) {
    BOOST_LOG_SEV(slg, logging::trivial::info) << "Some log record";
}

我写了以下内容,但这不起作用,但它表达了我正在尝试做的事情:

LOG(info) << "Hello, world!";

2 个答案:

答案 0 :(得分:5)

回答了类似的问题here,我只是重复了我的建议。

  1. 最简单的解决方案是定义您自己的日志记录宏,它将在消息前面添加源文件中的位置。例如:
  2. #define LOG(lg, sev)\
        BOOST_LOG_SEV(lg, sev) << "[" << __FILE__ << ":" << __LINE__ << "]: "
    

    现在,您可以在代码中的任何位置使用此宏,而不是BOOST_LOG_SEV。此解决方案的缺点是您无法将文件和行用作属性,因此日志格式已修复。

    1. 使用stream manipulators将文件名和行号作为属性附加。同样,在宏中更容易做到这一点:
    2. BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
      BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", unsigned int)
      
      #define LOG(lg, sev)\
          BOOST_LOG_SEV(lg, sev) << logging::add_value(a_file, __FILE__) << logging::add_value(a_line, __LINE__)
      

      (请参阅here关于关键字。)完成后,您可以在格式化程序中使用这些属性:

      sink->set_formatter(
          expr::stream << "[" << a_file << ":" << a_line << "]: " << expr::message);
      

      以这种方式添加的属性不能在过滤器中使用,如果这是一个问题,你可以......

      1. 使用scoped attributes。同样,您可以定义一个宏来自动化该过程:
      2. #define LOG(lg, sev, strm)\
            do {\
                BOOST_LOG_SCOPED_LOGGER_ATTR(lg, a_file.get_name(), attrs::constant< tag::a_file::value_type >(__FILE__));\
                BOOST_LOG_SCOPED_LOGGER_ATTR(lg, a_line.get_name(), attrs::constant< tag::a_line::value_type >(__LINE__));\
                BOOST_LOG_SEV(lg, sev) strm;\
            } while (false)
        

        请注意,在这种情况下,流表达式作为参数传递给宏,因为我们需要将它注入范围的中间。另请注意,此解决方案的性能可能低于其他两种解决方案。

        由于__FILE____LINE__是在使用点展开的宏,因此在某些时候确实没有涉及宏的解决方案。我相信,这是您的代码中的一个问题,您在LogMessage函数中使用了宏。

        由于必须定义宏,无论如何都应该是用户特定的,Boost.Log不提供开箱即用的功能。

答案 1 :(得分:2)

这个答案非常重视these conversations以及@ guillermo-ruiz特别提出的解决方案。我选择遵循Guillermo的建议,因为它最少使用预处理器。 (当然,我必须使用预处理器,因为我想要__FILE____LINE__。)

为了向那些关注更广泛帮助但不仅仅是严格答复的人提供一些评论:

  • 是的,Boost日志并不能提供开箱即用的功能,这很奇怪。
  • Boost日志不会像我一样做错误检查。特别是,如果定义中的属性名称与使用中的名称不匹配,则结果是没有描述性错误的段错误,而不是运行时错误(后跟中止或不中止)。
  • Boost日志对于使include语句正确非常敏感。没有单一的包含来统治它们所有,并且缺少一个包含会导致错误消息,这表明编程错误,实际上它只是一个缺失的定义。

在文件log.cc中,我写了这个:

Log::Log() {
try {
    // The first thing we have to do to get using the library is
    // to set up the logging sinks - i.e. where the logs will be written to.
    logging::add_console_log(std::clog,
                 keywords::format = "%TimeStamp%: %_%",
                 keywords::auto_flush = true);

    // Create a text file sink
    typedef sinks::synchronous_sink<sinks::text_file_backend> file_sink;
    shared_ptr<file_sink> sink(new file_sink(
    // File name pattern.
    keywords::file_name = "%Y%m%d_%H%M%S_%5N.log",
    // Rotation size, in characters
    keywords::rotation_size = 16384,
    // Rotate daily if not more often.  The time is arbitrary.
    keywords::time_based_rotation =
        sinks::file::rotation_at_time_point(4, 33, 17),
    // Flush after write.
    keywords::auto_flush = true));

    // Set up where the rotated files will be stored.
    sink->locked_backend()->set_file_collector(sinks::file::make_collector(
    // Where to store rotated files.
    keywords::target = "logs",
    // Maximum total size of the stored files, in bytes.
    keywords::max_size = 16 * 1024 * 1024,
    // Minimum free space on the drive, in bytes.
    keywords::min_free_space = 100 * 1024 * 1024));

    // Upon restart, scan the target directory for files matching the
    // file_name pattern.
    sink->locked_backend()->scan_for_files();
    boost::log::register_simple_formatter_factory<
    logging::trivial::severity_level, char>("Severity");
    sink->set_formatter(expr::stream
            << expr::attr<boost::posix_time::ptime>("TimeStamp")
            << " " << logging::trivial::severity << "["
            << expr::attr<string>("FileName") << ":"
            << expr::attr<unsigned int>("LineNumber") << "] "
            << expr::smessage);
    // Add it to the core
    logging::core::get()->add_sink(sink);
    // Add some attributes too
    logging::add_common_attributes();
    logging::core::get()->add_global_attribute("TimeStamp",
                           attrs::local_clock());
    logging::core::get()->add_global_attribute(
    "LineNumber", attrs::mutable_constant<unsigned int>(5));
    logging::core::get()->add_global_attribute(
    "FileName", attrs::mutable_constant<string>(""));

    src::severity_logger<logging::trivial::severity_level> slg;
    slg.add_attribute("LineNumber",
              attrs::constant<unsigned int>(__LINE__));
    slg.add_attribute("FileName", attrs::constant<string>(__FILE__));
    for (unsigned int i = 0; i < 2; ++i) {
    BOOST_LOG_SEV(slg, logging::trivial::info) << "Testing log, #" << i;
    }
} catch (std::exception& e) {
    std::cerr << "Failed to establish logging: " << e.what() << std::endl;
    throw LoggingInitException();
}
}

Log::~Log() { boost::log::core::get()->remove_all_sinks(); }

string PathToFilename(const string& path) {
string sub_path = path.substr(path.find_last_of("/\\") + 1);
return sub_path;
}

在文件log.h中,我写了这个:

// An exception if logging fails to initialize.
class LoggingInitException : public std::exception {};

/*
  Class to set up logging as we want it in all services.

  Instantiate a Log object near the beginning of main().
*/
class Log {
   public:
    Log();
    ~Log();
};

// Logging macro that includes severity, filename, and line number.
// Copied and modified from
// https://stackoverflow.com/questions/24750218/boost-log-to-print-source-code-file-name-and-line-number
// Set attribute and return the new value
template <typename ValueType>
ValueType SetGetAttrib(const char* name, ValueType value) {
    auto attr = boost::log::attribute_cast<
        boost::log::attributes::mutable_constant<ValueType>>(
        boost::log::core::get()->get_global_attributes()[name]);
    attr.set(value);
    return attr.get();
}

// Convert file path to only the filename
std::string PathToFilename(const std::string& path);

// Shortcut to declare a log source.  To insert in each function that will call
// the LOG macro.
#define LOGGABLE                                                              \
    boost::log::sources::severity_logger<boost::log::trivial::severity_level> \
        slg;

#define LOG(sev)                                                 \
    BOOST_LOG_STREAM_WITH_PARAMS(                                \
        (slg),                                                   \
        (SetGetAttrib("FileName", PathToFilename(__FILE__)))(    \
            SetGetAttrib("LineNumber", (unsigned int)__LINE__))( \
            ::boost::log::keywords::severity = (boost::log::trivial::sev)))

然后,要从文件foo.cc登录,我这样做:

void MyFunction() {
    LOGGABLE;
    LOG(debug) << "I made it to MyFunction().";
}

int main(int argc, char *argv[]) {
    Log log;
    LOGGABLE;
    LOG(info) << "Hey, I'm a message!";
    MyFunction();
    return 0;
}

仍有一些狡辩,但它们超出了这个问题/答案的范围:

  • 为了使这个更有用,我应该让日志更具体而不是&#34; logs&#34;,可能由调用标志和应用程序名称指示,无论我是否在生产中,等等。
  • 我应检查stdout是否已连接到终端,如果没有,请跳过登录到stdout。或者我应该在数据中心提供一个标志来禁用它,至少。
  • 有时,神秘地,我得到一个名为(例如)20160310_093628_00000.log的顶级日志文件,而不是写入logs/目录的文件。我花了很长时间才意识到boost日志写在那里,然后旋转到logs /目录。因此,如果程序崩溃,我最终会得到该级别的日志文件。