6

I want to create a logger class such that with a functionality like this:

Logger log;
log << "Error: " << value << "seen" << endl;

This should print me a custom formatted message. E.g. "12-09-2009 11:22:33 Error 5 seen"

My simple class currently looks like this:

class Logger {
    private:
        ostringstream oss;
    public:
        template <typename T>
        Logger& operator<<(T a);
}

template <typename T>
Logger& Logger::operator<<(T a) {
    oss << a;
    return *this;
}

void functionTest(void) {
    Logger log;
    log << "Error: " << 5 << " seen";
}

This will cause oss to correctly have the buffer "Error: 5 seen". But I dont know what other function I need to write/modify so that something prints on the screen. Does anyone know how to get this to work or is there another way to design this class to have my functionality work?

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
  • Question: The time stamp. Do you want the time stamp out: 1) Every expression 2) Only at the beginning of each line. Do you want the line to self terminate (as per your functionTest()). Basically you need to be a bit more specific about the conditions under which the time stamp is added. Also are you logging to a file the console both? Why do you need a special class and why can;t you use the standard stream? – Martin York Feb 10 '10 at 15:39
  • Martin, this is only a sample class. I stripped down the original logger class with only the issue regarding cout style usage of logger. – Dheeraj Agrawal Feb 10 '10 at 17:17

5 Answers5

4

Behind every std::ostream is a streambuf. It cab be retrieved and set via std::stream::rdbuf(). In particular, it can be wrapped - you can provide a streambuf object that post-processes the streamed text. (post-processing means you can't distinguish std::cout << 123; from std::cout << "123"; )

In your particular case, the postprocessing is fairly simple. At the start of every line you want to insert some bytes. This merely means that you should keep track of whether you've already output the prefix for the current line. If not, do so and set the flag. And whenever you see a newline, reset it. Your streambuf wrapper has just a single bool worth of state.

Appleshell
  • 7,088
  • 6
  • 47
  • 96
MSalters
  • 173,980
  • 10
  • 155
  • 350
1

Check out the simple logger proposed in Compile time optimization - removing debug prints from release binaries. Should be sufficient for your needs. Br, Gracjan

Community
  • 1
  • 1
fttrobin
  • 71
  • 7
1

As far as i can see your logger is no different than ostringstream. It just takes what is given and outputs it to the string stream. If you want to use it like this, you can write a destructor for Logger which outputs the string to cout.

Logger::~Logger()
{
    std::cout<<getcurrentDateTimeAsString()<<" "<<oss.str()<<std::endl;
}

But of course, this will make no sense if a Logger* is created and used throughout the program.

erelender
  • 6,175
  • 32
  • 49
1

The question is to choose when and how informations are to be sync'ed - by line ? So no matter it is buffered or not, there is no choice but to control EOL and the informations on the line - flushing it or direct outputs.

Even if the destructor is to be used as EOL/Flush,

{ log << [anything]; } as inline-local stack brackets syntax to invoke log's destructor exiting the brackets, or as std::endl, either must be used.

Unless implementing meta-object with some append operator such as '<<' or "+', you are ending all the way obligated to use an explicit way to end the line and or flush.

0

This (from this post) does what you want, but it forces you to end each line with std::endl:

class Logger {
    private:
        ostringstream oss;
    public:
        template <typename T>
        Logger& operator<<(T a);

    Logger& operator<<( std::ostream&(*f)(std::ostream&) )
    {
        if( f == std::endl )
        {
            std::cout << "12-09-2009 11:22:33" << oss.str() << std::endl;   
            oss.str("");
        }
        return *this;
    }
};

template <typename T>
Logger& Logger::operator<<(T a) {
    oss << a;
    return *this;
}

void functionTest(void) {
    Logger log;
    log << "Error: " << 5 << " seen" << std::endl;
}

int main()
{
    functionTest();
}

EDIT: Well according to your comment it doesn't seem to be what you want. Then I recommend you do as MSalters say.

Community
  • 1
  • 1
Manuel
  • 12,749
  • 1
  • 27
  • 35
  • After dumping the contents when `std::endl` is outputed, you should probably clear the contents of `oss` so that output is not repeated in the next line. – David Rodríguez - dribeas Feb 10 '10 at 09:28
  • No, this came to my mind too. But I do not want to force the user to always use endl. How does cout know when its the right to print? It doesn't need a endl from user. – Dheeraj Agrawal Feb 10 '10 at 09:31
  • @David - thanks, fixed @Dheeraj - cout prints things as they arrive (buffering issues aside). – Manuel Feb 10 '10 at 09:47
  • @Dheeraj Agrawal: ostreams write the contents when they are 'flushed', that can be done by passing `std::flush` to the stream, when calling `std::flush` on the stream or incidentally when passing `std::endl` to them (the behavior of passing `std::endl` is defined as equivalent to passing `"\n"` followed by `std::flush`) – David Rodríguez - dribeas Feb 10 '10 at 10:34
  • You seem to be dropping the standard manipulators! Don't you want to apply them to the stream? – Martin York Feb 10 '10 at 15:34
  • Martin, this is a bare bones example with the core of my problem – Dheeraj Agrawal Feb 10 '10 at 17:25
  • Manuel..I didn't have a clear understanding of how stream classes work. Now I do. Your solution actually works for me :) – Dheeraj Agrawal Feb 16 '10 at 09:09
  • @Dheeraj - as as I said I basically adapted from that other thread. Credit should go there :) – Manuel Feb 16 '10 at 09:21