I found quite some info on output redirection, creation of streambuffers and ostream classes, but I did not manage to apply this succesfully yet for my purpose. This post has become quite lengthy because I wanted to describe my step by step approach.
I have an application that uses a class MyNotifier that captures events in the application and composes log messages based on the event data. By default it sends the log messages to std::cout, but the constructor of MyNotifier accepts a variable of type std::ostream& to overide this. I am trying to construct a class of that type which should send the logs to an different output channel, e.g. via an MQTT client. I have MQTT up and running well. My question is about the creation of the custom ostream class.
Here is the code that should use the new class (see the commented lines in app_main) and it's output when I use std::cout. For testing, the events are generated by calling MyNotifier::Notify directly.
class MyNotifier { public: //constructor MyNotifier(std::ostream& os = std::cout) : ost(os) {} //takes eventdata as a code for the event //composes some output string based on the input and outputs it to the customizable output stream ost virtual void Notify( unsigned long eventdata); protected: std::ostream& ost; }; //class MyNotifier Implementation: void MyNotifier::Notify(unsigned long eventdata) { //takes eventdata as dummy for an event //composes some output string based on the input and outputs it to the customizable output stream ost char s[200]; int wr = sprintf(s, "RECEIVED EVENT %s ", "of type 1 "); sprintf( s + wr , "with number %lu\n", eventdata); std::cout << "MyNotifier::Notify" << s << std::endl; //this shows up ost << "dummy custom_ostream output: " << eventdata << std::endl; //trial to send over MQTT, in the end ost should generate MQTT output esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "value", 0, 1, 0); //works fine } //MyNotifier::Notify void app_main(void) { MyNotifier notifier; //instantiate with default output stream (std::cout) //MyNotifier notifier(std::cout); //instantiate with default output stream explicitly, also works with the same result //MyNotifier notifier(custom_ostream) //desired way to send logs over a Custom_ostream object notifier.Notify(3142727); //notify some event } This gives the desired output over cout:
RECEIVED EVENT of type 1 with number 3142727
In my first step to customize the output I only customize the streambuf class (OutStreamBuf). It is used by a "plain" ostream class:
class OutStreamBuf : public std::streambuf { protected: /* central output function * - print characters in uppercase mode */ //converts each character to uppercase virtual int_type overflow (int_type c) { if (c != EOF) { // convert lowercase to uppercase c = std::toupper(static_cast<char>(c),getloc()); //output to standard output putchar(c); } return c; } // write multiple characters MUST USE CONST CHAR* ? virtual std::streamsize xsputn (char* s, std::streamsize num) { std::cout << "**size: " << num << std::endl; std::cout << "OutStreamBuf contents: " << s << std::endl; return 1; } }; //OutStreamBuf Implementation: OutStreamBuf outStreamBuf; std::ostream custom_ostream(&outStreamBuf); MyNotifier notifier(custom_ostream); //instantiate with customized output stream notifier.Notify(314132345); //notify some event custom_ostream << "Test << operator" << std::endl; Output: **MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345 DUMMY CUSTOM_OSTREAM OUTPUT: 314132345 <------ THIS IS THE DESIRED OUTPUT TEST << OPERATOR** In my second step I want to get hold of the buffer contents to be able to forward this to my MQTT handler. So I decided that I need a customized ostream object. In the second trial I therefore created a customized ostream class (OutStream) with an *embedded* customized streambuf class: class OutStream : public std::ostream { private: //private local Outbuf for OutStream class Outbuf : public std::streambuf { protected: /* central output function * - print characters in uppercase mode */ //converts each character to uppercase virtual int_type overflow (int_type c) { if (c != EOF) { // convert lowercase to uppercase c = std::toupper(static_cast<char>(c),getloc()); //output to standard output putchar(c); } return c; } // write multiple characters MUST USE CONST CHAR* (?) virtual std::streamsize xsputn (char* s, std::streamsize num) { std::cout << "**size: " << num << std::endl; std::cout << "OUTBUF contents: " << s << std::endl; return 1; } }; //Outbuf Outbuf outbuf; std::streambuf * buf; public: //constructor OutStream() { //buf = this->rdbuf(); //compiles OK, but what does it do ? buf = rdbuf(); //compiles OK, but what does it do ? std::cout << "SOME MESSAGE FROM OutStream constructor" <<std::endl; esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream constructor", , 1, 0);
};
// << operator //https://www.geeksforgeeks.org/overloading-stream-insertion-operators-c/ //have a good look on what parameters the operator should take , see the above article friend std::ostream & operator << (std::ostream &stream, const OutStream& outStream){ esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream << operator", 0, 1, 0); //doesn't show stream << "Test << operator inside " << std::endl; //doesn't show return stream; //return the stream }; }; //OutStream Implementation: ``` OutStream custom_ostream; //using a composite ostream/streambuf object MyNotifier notifier(custom_ostream); //instantiate with customized output stream notifier.Notify(314132345); //notify some event custom_ostream << "Test << operator" << std::endl; This does not show the customised output. Therefore I added a log in the constructor (properly shown) and a modified << operator with a log (also not shown):
SOME MESSAGE FROM OutStream constructor
MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345
As the << operator log also fails I think that something is wrong with the constructor of the ostream object and/or it's binding with the streambuf. This is pretty complex stuff for me. Some help would be appreciated.
[EDIT] After discussion with Stephen M. Webb I focused on finding an example of a class based on std::streambuf that contains additional buffering. I found the following code that will hopefully be a good basis for further steps:
//p. 837 The C++ Standard Library Second Edition, Nicolai M. Josuttis class Outbuf_buffered : public std::streambuf { protected: static const int bufferSize = 10; // size of data buffer char buffer[bufferSize]; // data buffer public: // constructor // - initialize data buffer // - one character less to let the bufferSizeth character cause a call of overflow() Outbuf_buffered() { setp (buffer, buffer+(bufferSize-1)); } // destructor // - flush data buffer virtual ~Outbuf_buffered() { sync(); } protected: // flush the characters in the buffer int flushBuffer () { int num = pptr()-pbase(); if (write (1, buffer, num) != num) { return EOF; } pbump (-num); // reset put pointer accordingly return num; } // buffer full // - write c and all previous characters virtual int_type overflow (int_type c) { if (c != EOF) { // insert character into the buffer *pptr() = c; pbump(1); } // flush the buffer if (flushBuffer() == EOF) { // ERROR return EOF; } return c; } // synchronize data with file/destination // - flush the data in the buffer virtual int sync () { if (flushBuffer() == EOF) { // ERROR return -1; } return 0; } }; //Outbuf_buffered