0

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();
}

Live version

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.

ForEveR
  • 55,233
  • 2
  • 119
  • 133

1 Answers1

1

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.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • Thank you for answer. Will try that. However, how do you think, is `memory_sink` good way to implement what I want, or probably I should do something, when I add attributes to record? – ForEveR Aug 15 '15 at 11:56
  • I don't know what problem you are solving with `memory_sink` but personally I would try to avoid buffering records. Not only it tends to grow memory consumption of your app, it also reduces reliability and performance - the `detach_from_thread` method I mentioned can be rather expensive. If your real sink that you feed in `memory_sink` tends to block and that is what you're trying to mitigate then you're probably better off with async logging. – Andrey Semashev Aug 16 '15 at 15:45
  • Problem, that I want to solve is just send messages in actual backend only when there are max messages, or flush function is called. So, I need store records with attributes (like severity/facility/time/line/file) somewhere, `memory_sink` was for these. But probably I should just store list of attributes and messages and construct record just before sending... – ForEveR Aug 16 '15 at 20:52
  • And one more question, now about `async_frontend`, why only this frontend has `stop` function? And why there is no some base `async_frontend` with `stop` function, that is not templated? It's very hard to write function, that returns all possible sinks for stop call. – ForEveR Aug 17 '15 at 08:38
  • The `stop` function breaks the `run` loop which no other sink has. No other frontends have a dedicated processing thread, so no `stop` needed. – Andrey Semashev Aug 17 '15 at 21:30