I am trying to make my team go away from log4cxx
and try to use Boost.Log v2
instead. Our current log4cxx pattern is rather simple:
log4cxx::helpers::Properties prop;
prop.setProperty("log4j.rootLogger","DEBUG, A1");
prop.setProperty("log4j.appender.A1","org.apache.log4j.ConsoleAppender");
prop.setProperty("log4j.appender.A1.layout","org.apache.log4j.PatternLayout");
prop.setProperty("log4j.appender.A1.layout.ConversionPattern","%d{ABSOLUTE} %-5p [%c] %m%n");
log4cxx::PropertyConfigurator::configure(prop);
However I failed to find a solution to support filename and line number printing. I've found so far an old post, but there is no clear solution (no accepted solution). I've looked into using those BOOST_LOG_NAMED_SCOPE
but they feel very ugly as it make it impossible to print the proper line number when multiple of those are used within the same function.
I've also found a simpler direct solution, here. But that also feel ugly, since it will print the fullpath, instead of just the basename (it is not configurable). So the fullpath and line number are always printed on a fixed location (before expr::smessage
).
I've found also this post, which looks like an old solution.
And finally the most promising solution, I found was here. However it does not even compile for me.
So my question is simply: how can I use Boost.Log v2 to print filename (not fullpath) and line number with minimal formatter flexibility (no solution with MACRO
and __FILE__
/ __LINE__
accepted, please). I'd appreciate a solution which does not involve BOOST_LOG_NAMED_SCOPE
, as described here:
If instead you want to see line numbers of particular log records then
the best way is to define a custom macro which you will use to write
logs. In that macro, you can add the file name and line number as
attributes to the record (you can use manipulators for that). Note
that in this case you won't be able to use these attributes in
filters, but you probably don't need that.
I finally found a simple solution based on add_value. Here is the full source code:
#include <ostream>
#include <fstream>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/filesystem.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
// Get the LineID attribute value and put it into the stream
strm << logging::extract< unsigned int >("LineID", rec) << ": ";
strm << logging::extract< int >("Line", rec) << ": ";
logging::value_ref< std::string > fullpath = logging::extract< std::string >("File", rec);
strm << boost::filesystem::path(fullpath.get()).filename().string() << ": ";
// The same for the severity level.
// The simplified syntax is possible if attribute keywords are used.
strm << "<" << rec[logging::trivial::severity] << "> ";
// Finally, put the record message to the stream
strm << rec[expr::smessage];
}
void init()
{
typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
boost::shared_ptr< text_sink > sink = boost::make_shared< text_sink >();
sink->locked_backend()->add_stream(
boost::make_shared< std::ofstream >("sample.log"));
sink->set_formatter(&my_formatter);
logging::core::get()->add_sink(sink);
}
#define MY_GLOBAL_LOGGER(log_,sv) BOOST_LOG_SEV( log_, sv) \
<< boost::log::add_value("Line", __LINE__) \
<< boost::log::add_value("File", __FILE__) \
<< boost::log::add_value("Function", BOOST_CURRENT_FUNCTION)
int main(int, char*[])
{
init();
logging::add_common_attributes();
using namespace logging::trivial;
src::severity_logger< severity_level > lg;
MY_GLOBAL_LOGGER(lg,debug) << "Keep";
MY_GLOBAL_LOGGER(lg,info) << "It";
MY_GLOBAL_LOGGER(lg,warning) << "Simple";
MY_GLOBAL_LOGGER(lg,error) << "Stupid";
return 0;
}
On my Linux box, I compiled it using:
$ c++ -otutorial_fmt_custom -DBOOST_LOG_DYN_LINK tutorial_fmt_custom.cpp -lboost_log -lboost_log_setup -lboost_thread -lpthread -lboost_filesystem
If you run and check the log file generated here is what you get:
$ ./tutorial_fmt_custom && cat sample.log
1: 61: tutorial_fmt_custom.cpp: <debug> Keep
2: 62: tutorial_fmt_custom.cpp: <info> It
3: 63: tutorial_fmt_custom.cpp: <warning> Simple
4: 64: tutorial_fmt_custom.cpp: <error> Stupid
My solution is based on two inputs (thanks!):
- http://www.boost.org/doc/libs/1_58_0/libs/log/example/doc/tutorial_fmt_custom.cpp
- http://d.hatena.ne.jp/yamada28go/20140215/1392470561
In order to output filename and line you will need to register Scopes
:
logging::core::get()->add_global_attribute("Scopes", attributes::named_scope());
and add custom formatter:
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
const auto cont = logging::extract< attributes::named_scope::value_type >("Scopes", rec);
if(cont.empty())
return;
auto it = cont->begin();
boost::filesystem::path path(it->file_name.c_str());
strm << path.filename().string() << ":" << it->line << "\t" << rec[expr::smessage];
}
Note: In order for this to work the scopes container needs to be cleared before each logging:
if(!attributes::named_scope::get_scopes().empty()) attributes::named_scope::pop_scope();