views:

619

answers:

5

Hi,

I'm looking at making a logging class which has members like Info, Error etc that can configurably output to console, file, or to nowhere.

For efficiency, I would like to avoid the overhead of formatting messages that are going to be thrown away (ie info messages when not running in a verbose mode). If I implement a custom std::streambuf that outputs to nowhere, I imagine that the std::ostream layer will still do all the formatting. Can anyone suggest a way to have a truly "null" std::ostream that avoids doing any work at all on the parameters passed to it with <<?

Thanks.

A: 

Probably you'll need more than just text formatting and message filtering. What about multithreading?

I would implement the filtering and multithreading synchronization as the responsibility of a separate class.

However, logging is a not-so-simple problem, and I would try to use existing logging solutions, instead of developing a new one.

Cătălin Pitiș
+3  A: 

To pervent the operator<<() invocations from doing formatting, you should know the streamtype at compile-time. This can be done either with macros or with templates.

My template solution follows.

class NullStream {
public:
    void setFile() { /* no-op */ }
    template<typename TPrintable>
    NullStream& operator<<(TPrintable const&)
    { /* no-op */ }
}

template<class TErrorStream> // add TInfoStream etc
class Logger {
public:
    TErrorStream& errorStream() {
        return m_errorStream;
    }

private:
    TErrorStream m_errorStream;
};

//usage
int main() {
    Logger<std::ofstream> normal_logger; // does real output
    normal_logger.errorStream().open("out.txt");
    normal_logger.errorStream() << "My age is " << 19;

    Logger<NullStream> null_logger; // does zero output with zero overhead
    null_logger.errorStream().open("out.txt"); // no-op
    null_logger.errorStream() << "My age is " << 19; // no-op
}

Since you have to do this at compile-time, it is of course quite inflexible.

For example, you cannot decide the logging level at runtime from a configuration file.

+1: simple, clean, does the job well. Note that e.g. "NullStream s; s << expensive_function();" will most likely still evaluate expensive_function(), especially if it lives in another module.
j_random_hacker
j_random_hacker
expensive_function() will _definitely_ be evaluated no matter where it lives. There is no way to pervent that barring macros and conditional compilation :) ... as to Neil's onullstream, I do not believe it fulfils the "zero formatting overhead" requirement :)
That's true, Neil's has a different tradeoff. I suppose your class could offer a conversion function to ostream)
j_random_hacker
+5  A: 

A swift google came up with this example which may be of use. I offer no guarantees, except that it compiles and runs :-)

#include <streambuf>
#include <ostream>

template <class cT, class traits = std::char_traits<cT> >
class basic_nullbuf: public std::basic_streambuf<cT, traits> {
    typename traits::int_type overflow(typename traits::int_type c)
    {
        return traits::not_eof(c); // indicate success
    }
};

template <class cT, class traits = std::char_traits<cT> >
class basic_onullstream: public std::basic_ostream<cT, traits> {
    public:
        basic_onullstream():
        std::basic_ios<cT, traits>(&m_sbuf),
        std::basic_ostream<cT, traits>(&m_sbuf)
        {
            init(&m_sbuf);
        }

    private:
        basic_nullbuf<cT, traits> m_sbuf;
};

typedef basic_onullstream<char> onullstream;
typedef basic_onullstream<wchar_t> wonullstream;

int main() {
    onullstream os;
    os << 666;
}
anon
j_random_hacker
+2  A: 

Why not using existing logging solutions used by millions of users? log4j, log4net, log4cxx.., to name just a few..

Dima
A: 

Hi, all, thanks for sharing the code, I just do a test, then Neil's method will still do the string formating, for example:

#include <streambuf>
#include <ostream>
#include <iostream>
using namespace std;


template <class cT, class traits = std::char_traits<cT> >
class basic_nullbuf: public std::basic_streambuf<cT, traits> {
    typename traits::int_type overflow(typename traits::int_type c)
    {
        return traits::not_eof(c); // indicate success
    }
};

template <class cT, class traits = std::char_traits<cT> >
class basic_onullstream: public std::basic_ostream<cT, traits> {
    public:
        basic_onullstream():
        std::basic_ios<cT, traits>(&m_sbuf),
        std::basic_ostream<cT, traits>(&m_sbuf)
        {
            init(&m_sbuf);
        }

    private:
        basic_nullbuf<cT, traits> m_sbuf;
};

typedef basic_onullstream<char> onullstream;
typedef basic_onullstream<wchar_t> wonullstream;

class MyClass
{
    int a;
    friend ostream& operator<< (ostream&, MyClass const&);
};

ostream& operator<<(ostream& out,MyClass const& b)
{
    std::cout<<"call format function!!";
    out << b.a;
    return out;
}

int main() {
    onullstream os;
    MyClass obj;
    os<<obj;
}

Running this program, you will find that "ostream& operator<<(ostream& out,MyClass const& b)" will be called. So, doing format on the obj will still be called. So, we still can't avoid the overhead of formatting messages.

ollydbg23