I have an application which makes extensive use of boost log 2.0. Now I would like to set some default flags for that application like std::setprecision(std::numeric_limits<double>::digits10 + 1)
, std::scientific
and std::left
. But how do I do that? One approach is creating a logger at the very beginning of my main function and creating a dummy log message. This permanently sets the desired flags. But is there no nicer way to do this?
edit in reply to: "OP should show actual code."
I have a global Logging singleton, called L:
class L{
public:
enum severity_level
{
dddebug,
ddebug,
debug,
control,
iiinfo,
iinfo,
info,
result,
warning,
error,
critical
};
typedef boost::log::sources::severity_channel_logger<
severity_level, // the type of the severity level
std::string // the type of the channel name
> logger_t;
typedef boost::log::sinks::synchronous_sink< boost::log::sinks::text_ostream_backend > text_sink;
boost::shared_ptr< text_sink > sink_;
static L& get();
static boost::shared_ptr<text_sink> sink();
static double t0();
static double tElapsed();
private:
L();
double t0_p;
static std::string tElapsedFormat();
L(const L&) = delete;
void operator=(const L&) = delete;
};
which provides a logging sink, severity levels and utilizes MPI methods for synchronized timekeeping across MPI nodes. THe implementation of the class members follows here:
#include "log.h"
#include <iomanip>
#include <limits>
#include <fstream>
#include <boost/log/attributes/function.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
#include "mpiwrap.h"
#include <mpi.h>
BOOST_LOG_ATTRIBUTE_KEYWORD(t, "Time", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(rank, "Rank", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", L::severity_level)
L::L():
sink_(boost::make_shared< text_sink >()),
t0_p(MPI_Wtime())
{
sink_->locked_backend()->add_stream(
boost::make_shared< std::ofstream >("log." + std::to_string(MpiWrap::getRank())));
sink_->set_formatter
(
expr::stream
<< "< "
<< t << " "
<< "[p:" << rank << "] "
<< "[c:" << channel << "] "
<< "[s:" << severity << "] "
<< expr::smessage
);
logging::core::get()->add_sink(sink_);
logging::core::get()->set_filter(
(channel == "ChannelName1" && severity >= dddebug)
|| (channel == "ChannelName2" && severity >= info)
|| (channel == "ChannelName3" && severity >= result)
);
// Add attributes
logging::core::get()->add_global_attribute("Time", attrs::make_function(&tElapsedFormat));
logging::core::get()->add_global_attribute("Rank", attrs::constant<int>(MpiWrap::getRank()));
}
L& L::get(){
static L instance;
return instance;
}
boost::shared_ptr<L::text_sink> L::sink(){
return get().sink_;
}
double L::t0(){
return get().t0_p;
}
double L::tElapsed(){
return MPI_Wtime() - t0();
}
std::string L::tElapsedFormat(){
std::stringstream ss;
const int prec = std::numeric_limits<double>::digits10;
ss << std::setw(prec + 2 + 6) << std::left << std::setprecision(prec) << tElapsed();
return ss.str();
}
std::ostream& operator<< (std::ostream& strm, L::severity_level level)
{
static const char* strings[] =
{
"DBG3",
"DBG2",
"DBG1",
"CTRL",
"INF3",
"INF2",
"INF1",
"RSLT",
"WARN",
"ERRR",
"CRIT"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
Now for the usage: My classes usually have a static logger_t
(typedef for boost::log::sources::severity_channel_logger<severity_level, std::string>
) member
class A {
public:
logger_t logger;
//other stuff here
void function_which_does_logging();
};
L::logger_t A::logger(boost::log::keywords::channel = "ClassA");
void A::function_which_does_logging(){
//do non logging related stuff
BOOST_LOG_SEV(logger, L::result) << "the error is: " << 0.1234567890;
//do non logging related stuff
}
My current solution to the problem is putting a logging statement in the beginning of my program
int main(){
L::logger_t logger(boost::log::keywords::channel = "init");
BOOST_LOG_SEV(logger, L::critical) << "setting up logger" << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1);
//do stuff
}
I don't believe that your current method will work in all cases, especially if your code is threaded. It made me nervous when you said that a single logged format flag worked to fix multiple loggers, so I looked at the code (record_ostream.hpp and record_ostream.cpp).
Boost Log uses the Object pool design pattern to provide stream formatters to loggers with the auxiliary struct
stream_provider
. The implementation ofstream_provider
uses thread-local storage (when threading is supported) to use a separate pool ofostream
instances for each thread. Within a pool,ostream
instances are created as needed - if only one formatter is needed at a time, only one will ever be created. So I think your current workaround will work for the current Boost Log implementation if you log from a single thread and if you never log something in the middle of logging something else.How this fails with threading should be pretty obvious. Here's a simple example of how this can fail in a single thread:
which produces:
Note that the top level log entry (on the third line) is formatted properly while the function log entry (on the second line) is not. This is because the configured stream was in use when the function was called so a separate stream was created and used.
I think @WilliamKunkel's suggestion to define your own logging macro(s) is the best way I've seen to handle this. If you really want to use the Boost macros, however, this hack worked for me on Boost 1.55:
The basic idea is to specialize the template function that supplies the stream to the macro. When it is specialized to the actual logger class you are using, the specialized implementation passing the
std::scientific
flag will be preferred to the generic implementation.This is a hack because it depends on implementation details of Boost Log and is not guaranteed to work from release to release. Defining your own macro seems to me like a much better way to go.
I had hoped that something could be done with
boost::log::basic_formatting_stream
because its header says:Unfortunately, it looks like this only applies to class types and not primitive types as there are member function implementations of
operator<<
for all primitive types.@rhashimoto makes a good point about how your current solution will break down with multiple threads/concurrent logging operations. I feel the best solution is to define your own logging macros to replace
BOOST_LOG_SEV
which have the stream modifiers included, like this:Which can just be used as a drop-in replacement for
BOOST_LOG_SEV
which formats numbers as scientific. However, it's probably a pain to go through your code and replace every logging operation with a new custom macro. Instead of defining your own macro, you can also redefineBOOST_LOG_SEV
to behave how you want.boost/log/sources/severity_feature.hpp
definesBOOST_LOG_SEV
as follows:Because
BOOST_LOG_STREAM_SEV
is still a part of the public boost API, you ought to be able to safely redefineBOOST_LOG_SEV
like this:So long as this is defined after you have included the boost log headers, it should do what you want. However, I would recommend using a macro with a custom name, so that it is clear to others what your code is doing.