Question

I am looking to write a logger with multiple streams for different severities of logs:

class Logger{
public:
    std::ostream& errStream;
    std::ostream& warnStream;
}

This way I can use the streams as such:

Logger L;
L.errStream << "This is an error message.";
L.warnStream << "This is a warning message.";

The question is, how can I overload the operator<< for each of the streams separately? Meaning I want to take different actions based on which stream is written to.

If it helps, I already have member functions for errWrite and warnWrite that take a std::string as the argument:

void errWrite(std::string);
void warnWrite(std::string);

To use these I do:

Logger L;
L.errWrite("This is an error message.");
L.warnWrite("This is a warning message.");

The trouble with these is that they are not drop in replacements for std::cout and std::cerr, which my code is already filled with. I was trying to develop something that could easily be dropped into the existing code. So ultimately I would like either:

  1. A way to overload the operators separately for the different members.
  2. An alternative approach to what I'm trying to do.

Thanks.

Was it helpful?

Solution

To overload the operator<<, the type needs to be different.

So, to do this, you will have to make a new class to replace std::ostream, e.g. owarnstream and oerrstream.

I think something like this would work:

class oerrstream
{
 private:
   std::ostream& st;

 public:
   oerrstream(std::ostream &stream) : st(stream) {}
   std::ostream& getStream() { return st; };
};

Then you could override it using:

oerrstream& operator<<(oerrstream &es, const std::string& s)
{
   es.getStream() << s;
   return es;
}

Just bear in mind that you will need to override ALL output operatins... It may work to do that using a template, like this:

template <typename T>
oerrstream& operator<<(oerrstream &es, T t)
{
   es.getStream() << t;
   return es;
}

OTHER TIPS

You could take IO manipulators approach:

#include <iostream>
#include <string>

class Logger {
public:
    Logger()
        : errStream(std::cerr)
        , warnStream(std::cout)
        , active(&errStream)
    { }

    Logger& operator<<(const std::string& str)
    {
        *active << str;
        return *this;
    }

    Logger& operator<<(Logger&(*manip)(Logger&))
    {
        return manip(*this);
    }

private:
    std::ostream& errStream;
    std::ostream& warnStream;
    std::ostream* active;

    friend Logger& err(Logger&);
    friend Logger& warn(Logger&);
};

Logger& err(Logger& obj)
{
    obj.active = &obj.errStream;
    return obj;
}

Logger& warn(Logger& obj)
{
    obj.active = &obj.warnStream;
    return obj;
}

int main()
{
    Logger l;
    l << err << "error\n";
    l << warn << "warning\n";
}

Meaning I want to take different actions based on which stream is written to.

Not sure what "different actions" means, but here's an interpretation:

class Logger{
private:
    std::ostream& errStream;
    std::ostream& warnStream;

public:

    enum { err, warn } stream_type;


    std::ostream& get(stream_type const st)
    {
        // different actions per stream:
        switch(st)
        {
        case err:
            return errStream << "Error [" << gettime_function() << "] ";
        case warn:
            return warnStream << "Warning: ";
        }
    }
};

Edit:

In case you want to specialize the streams themselves, you can easily define a templated output operator:

class ErrorStream
{
     std::ostream& underlying_;
public:
    ErrorStream(std::ostream& underlying) : underlying_(underlying) {}
    template<typename T> friend ErrorStream& operator << (
        ErrorStream& out, const T& arg)
    {
        // custom action for error stream goes here
        out.underlying_ << arg;
        return out;
    }
};

This operator will work for whatever T that operator<< (std::ostream&, const T&) works for.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top