提升日志和严重性/本地属性
Boost log and severity / local attributes
我是 Boost 日志的新手。我有一些简单的东西在工作,但我一直坚持使用宏来轻松指定文件名和行号。看起来好难,我想我错过了什么。
我写这篇文章是为了在最后用演示日志初始化日志记录。请注意,我正在登录控制台和旋转文件。
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";
}
现在我想制作一个宏,这样我就可以添加文件和行号并让它像 ostream 一样工作。我想做的是说
LOG(info) << "Hello, world!";
我写了以下内容,这不起作用,但它表达了我正在尝试做的事情:
#define LOG(severity_level) LogMessage(__FILE__, __LINE__, severity_level)
// What's my return type?
auto LogMessage(const char* filename, const int line_number,
const enum SeverityLevel) {
src::severity_logger slg;
slg.add_attribute("LineNumber", attrs::constant<unsigned int>(__LINE__));
slg.add_attribute("FileName", attrs::constant<string>(__FILE__));
return BOOST_LOG_SEV(slg, severity_level); // NO
}
这个答案在很大程度上要归功于 these conversations,尤其要归功于@guillermo-ruiz 提出的解决方案。我选择遵循 Guillermo 的建议,因为它对预处理器的使用最少。 (当然,预处理器的某些使用是强制性的,因为我想要 __FILE__
和 __LINE__
。)
为了向那些追随者提供比严格答案更广泛的帮助,有几点要说:
- 是的,很奇怪 Boost 日志没有提供开箱即用的功能。
- Boost 日志没有像我希望的那样进行错误检查。特别是,如果定义时的属性名称与使用时的名称不匹配,则结果是没有描述性错误的段错误,而不是运行时错误(后跟是否中止)。
- Boost 日志对于正确包含语句非常敏感。没有单一的包含来统治它们,缺少包含会导致错误消息提示编程错误,而实际上,它只是缺少定义。
在文件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
//
// 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;
}
还有一些问题,但它们超出了本 SO 的范围 question/answer:
- 为了让它更有用,我应该让日志去到比 "logs" 更具体的地方,可能由调用标志和应用程序名称决定,无论我是否在生产中,等等。
- 我应该检查 stdout 是否连接到终端,如果没有,则跳过记录到 stdout。或者我至少应该提供一个标志以在数据中心禁用它。
- 有时,神秘地,我得到一个名为(例如)
20160310_093628_00000.log
的顶级日志文件,而不是写入 logs/
目录的文件。我花了很长时间才意识到 boost 日志写在那里,然后轮换到 logs/ 目录。因此,如果程序崩溃,我最终会得到该级别的日志文件。
有人回答了类似的问题 here,我将重复一下我的建议。
- 最简单的解决方案是定义您自己的日志记录宏,它将在消息前加上源文件中的位置。例如:
#define LOG(lg, sev)\
BOOST_LOG_SEV(lg, sev) << "[" << __FILE__ << ":" << __LINE__ << "]: "
现在您可以在代码中的任何地方使用这个宏,而不是 BOOST_LOG_SEV
。此解决方案的缺点是您不能将文件和行用作属性,因此日志格式是固定的。
- 使用stream manipulators附加文件名和行号作为属性。同样,在宏中更容易做到这一点:
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);
虽然以这种方式添加的属性不能在过滤器中使用,但如果这是一个问题,您可以...
- 使用scoped attributes。同样,您可以定义一个宏来自动执行该过程:
#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 没有提供开箱即用的这个功能。
我是 Boost 日志的新手。我有一些简单的东西在工作,但我一直坚持使用宏来轻松指定文件名和行号。看起来好难,我想我错过了什么。
我写这篇文章是为了在最后用演示日志初始化日志记录。请注意,我正在登录控制台和旋转文件。
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";
}
现在我想制作一个宏,这样我就可以添加文件和行号并让它像 ostream 一样工作。我想做的是说
LOG(info) << "Hello, world!";
我写了以下内容,这不起作用,但它表达了我正在尝试做的事情:
#define LOG(severity_level) LogMessage(__FILE__, __LINE__, severity_level)
// What's my return type?
auto LogMessage(const char* filename, const int line_number,
const enum SeverityLevel) {
src::severity_logger slg;
slg.add_attribute("LineNumber", attrs::constant<unsigned int>(__LINE__));
slg.add_attribute("FileName", attrs::constant<string>(__FILE__));
return BOOST_LOG_SEV(slg, severity_level); // NO
}
这个答案在很大程度上要归功于 these conversations,尤其要归功于@guillermo-ruiz 提出的解决方案。我选择遵循 Guillermo 的建议,因为它对预处理器的使用最少。 (当然,预处理器的某些使用是强制性的,因为我想要 __FILE__
和 __LINE__
。)
为了向那些追随者提供比严格答案更广泛的帮助,有几点要说:
- 是的,很奇怪 Boost 日志没有提供开箱即用的功能。
- Boost 日志没有像我希望的那样进行错误检查。特别是,如果定义时的属性名称与使用时的名称不匹配,则结果是没有描述性错误的段错误,而不是运行时错误(后跟是否中止)。
- Boost 日志对于正确包含语句非常敏感。没有单一的包含来统治它们,缺少包含会导致错误消息提示编程错误,而实际上,它只是缺少定义。
在文件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
//
// 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;
}
还有一些问题,但它们超出了本 SO 的范围 question/answer:
- 为了让它更有用,我应该让日志去到比 "logs" 更具体的地方,可能由调用标志和应用程序名称决定,无论我是否在生产中,等等。
- 我应该检查 stdout 是否连接到终端,如果没有,则跳过记录到 stdout。或者我至少应该提供一个标志以在数据中心禁用它。
- 有时,神秘地,我得到一个名为(例如)
20160310_093628_00000.log
的顶级日志文件,而不是写入logs/
目录的文件。我花了很长时间才意识到 boost 日志写在那里,然后轮换到 logs/ 目录。因此,如果程序崩溃,我最终会得到该级别的日志文件。
有人回答了类似的问题 here,我将重复一下我的建议。
- 最简单的解决方案是定义您自己的日志记录宏,它将在消息前加上源文件中的位置。例如:
#define LOG(lg, sev)\
BOOST_LOG_SEV(lg, sev) << "[" << __FILE__ << ":" << __LINE__ << "]: "
现在您可以在代码中的任何地方使用这个宏,而不是 BOOST_LOG_SEV
。此解决方案的缺点是您不能将文件和行用作属性,因此日志格式是固定的。
- 使用stream manipulators附加文件名和行号作为属性。同样,在宏中更容易做到这一点:
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);
虽然以这种方式添加的属性不能在过滤器中使用,但如果这是一个问题,您可以...
- 使用scoped attributes。同样,您可以定义一个宏来自动执行该过程:
#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 没有提供开箱即用的这个功能。