1

Internally we have a logging function with the interface OurLog(const char *). I'd like to be able to use it with an interface similar to std::ostringstream. In other words, I'd love to have an adaptor object so I can write:

 logging_class log;
 log << "There are " << num_lights << " lights\n";

And this call OurLog() as necessary to write the message to the log.

It looks like making buffer class derived from std::streambuf is the right way to go; how does one go about this? What functions need to be implemented?

leecbaker
  • 3,611
  • 2
  • 35
  • 51

3 Answers3

3

If you want each line

log << "There are " << num_lights << " lights\n";

resulting in a call to your OurLog(const char *) then this toy example might help to get started:

struct toy_logger {
    std::stringstream data;
    ~toy_logger() { OurLog(data.c_str()); }
    template <typename T> operator<<(const T& t) { data << t; }
};

with only a minor difference in using it:

toy_logger() << "There are " << num_lights << " lights\n";
        //^ create temporary that will get its destructor called at the end of the line
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

Found a simple example in the libstdc++ documentation here.

class LoggingBuffer : public std::streambuf {
protected:
    virtual int_type overflow (int_type c) {
        if (c != EOF) {
            char msg[2] = {static_cast<char>(c), 0};
            OurLog(msg);
        }
        return c;
    }
};

class Logger : public std::ostream {
    LoggingBuffer logging_buffer;
public:
    Logger() : logging_buffer(), std::ostream(&logging_buffer) {}

};

extern Logger log; //instantiated in a cpp file for global use

Unfortunately it doesn't look very performant, since it requires a function call for every single character. Is there a more efficient way of doing this?

leecbaker
  • 3,611
  • 2
  • 35
  • 51
  • That's the general approach. There are other virtual functions in `std::streambuf` that you can override. Start by reading some [documentation](https://en.cppreference.com/w/cpp/io/basic_streambuf). – Pete Becker Aug 28 '18 at 11:51
0

I recently did something like that. Depending on your requirements (particular to when flushing is required) there are some different approaches.

A very simple way is to simply inherit std::stringstream (and then exploit copy elision/RVO if you need an easier factory function for example). This will then write when object goes out of scope.

class LogStream : public std::ostringstream
{
public:
  LogStream(){}

  ~LogStream()
  {
        log(str());
  }
};

With the streambuf approach, you basic need is to just override sync() (and possible the destructor as well).

  virtual int sync() override {
    ::log(str());
    str("");//empty buffer
    return 0;//success
  }

Then instantiate std::ostream with your streambuf. Hope it helps.

darune
  • 10,480
  • 2
  • 24
  • 62
  • If you need more info / details, perhaps with the streambuf approach (?), don't hessitate to ask. – darune Aug 29 '18 at 07:51