I want to save actual formatted message in memory with something like memory_sink
and then on flush
send messages in backend. But there is a problem, that attributes of saved record_view
are changed to the attributes of the last created record_view
, I have no idea why such behaviour is. But probably someone can say me, can I implement something like I want, or not?
Really minimal example:
#define BOOST_LOG_DLL 1
#include <boost/log/core.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/expressions/keyword.hpp>
#include <boost/log/trivial.hpp>
#undef BOOST_LOG_DLL
#include <boost/shared_ptr.hpp>
#include <queue>
namespace logging = boost::log;
namespace sinks = boost::log::sinks;
typedef sinks::combine_requirements<sinks::synchronized_feeding, sinks::flushing>::type sync_flushing;
struct message_t
{
message_t(const logging::record_view& r, const std::string& f) :
record(r), fstring(f)
{
}
logging::record_view record;
std::string fstring;
};
template<typename Sink>
class memory_sink : public sinks::basic_formatted_sink_backend<char, sync_flushing>
{
public:
memory_sink(const boost::shared_ptr<Sink>& sink) : sink_(sink)
{
}
void consume(const logging::record_view& rec, const string_type& fstring)
{
const message_t msg(rec, fstring);
messages_.push(msg);
}
void flush()
{
while (!messages_.empty())
{
const message_t& msg = messages_.front();
sink_->consume(msg.record, msg.fstring);
messages_.pop();
}
}
private:
typedef std::queue<message_t> buffer_t;
buffer_t messages_;
const boost::shared_ptr<Sink> sink_;
};
std::ostream& operator << (std::ostream& stream, logging::trivial::severity_level lvl)
{
return stream << boost::log::trivial::to_string(lvl);
}
class cout_sink : public sinks::basic_formatted_sink_backend< char, sinks::synchronized_feeding >
{
public:
std::string format(const logging::record_view& rec, const std::string& fstring) const
{
return fstring;
}
void consume(const logging::record_view& rec, const std::string& fstring)
{
std::cout << "[" << rec[boost::log::trivial::severity] << "] " << fstring << std::endl;
}
};
void init_cout()
{
typedef sinks::synchronous_sink<memory_sink<cout_sink> > sink_t;
boost::shared_ptr< logging::core > core = logging::core::get();
core->remove_all_sinks();
boost::shared_ptr< cout_sink > tsink = boost::make_shared<cout_sink>();
boost::shared_ptr<memory_sink<cout_sink> > backend = boost::make_shared<memory_sink<cout_sink> >(tsink);
boost::shared_ptr< sink_t > sink = boost::make_shared<sink_t>(tsink);
core->add_sink(sink);
}
void flush_logs()
{
logging::core::get()->flush();
}
int main()
{
logging::add_common_attributes();
init_cout();
BOOST_LOG_TRIVIAL(warning) << "warning message";
BOOST_LOG_TRIVIAL(error) << "error message";
flush_logs();
}
As you can see severity in both record is error
, but I expect warning
and error
. Probably there is better way to do this?
I know about formatter, here severity is printed in backend
, only for example, since real code is bigger and sinks on consume receives record_view
, than just send it to backend, that also receives only record_view
.
The behavior you observe is caused by an optimization technique used by the library. When creating a log record, Boost.Log can sometimes avoid copying attribute values into the record by referencing thread-specific data that holds the original values. Thread-specific data is supposed to stay constant while the record is being processed. Asynchronous sinks, which obviously need to pass records between threads, have a way to communicate that to the logging core, so that this optimization is disabled and log records are detached from the originating thread before passing to the sink. This is described here, see the note about detach_from_thread
.
The problem is that only the sink frontend decides whether this optimization has to be disabled, and only the asynchronous_sink
does that. There is no way to customize that for other frontends. So either you have to use your backend with asynchronous_sink
or you have to write your frontend as well.
To implement a sink frontend you need a class that derives from the sink
base class and performs filtering, formatting (if needed) and passes the record to the backend. You can use synchronous_sink
implementation as an example. The important difference from synchronous_sink
is that you have to pass true
to the sink
constructor. This will tell the logging core that your sink requires detached log records.