i would like to create a flexible logger class. I want it to be able to output data to a file or to standard output. Also, i want to use streams. The class should look something like:
class Logger
{
private:
std::ostream m_out; // or ofstream, iostream? i don't know
public:
void useFile( std::string fname);
void useStdOut();
void log( symbol_id si, int val );
void log( symbol_id si, std::string str );
//etc..
};
The symbol_id
is an enum and defines the formatting. What i want to achieve is to be able to easily switch from standart output to a file and vice versa (this is the purpose of the use*
methods). Preferably by just using m_out
and simply writing m_out << "something";
without any checks whether i want to write to a file or stdout.
I know there are many ways how to get around this (using if's
to test if i want to write to a file or stdout, the "C way" (using FILE*
and fprintf
)) and so on, but i'm sure there must be a way how to achieve this with C++ streams in a nice way. But i can't seem to find the way how to do it. Can somebody help me please?
The way I've attacked this problem before is to make Logger
an abstract base class and create separate FileLogger
and OutStreamLogger
classes. Then create a CompositeLogger
object that implements the Logger
interface, which just outputs all loggers:
CompositeLogger compLogger;
compLogger.Add(new FileLogger("output.txt"));
compLogger.Add(new StreamLogger(std::cout));
...
compLogger.log(...);
If you don't need this level of flexibility and want to keep all this in a single class you could make the m_Out variable a pointer to std::ostream
and add an extra flag to keep track of whether you need to delete it on cleanup:
private:
std::ostream* m_out;
bool m_OwnsStream;
Logger() {
m_Out=&std::cout; // defaults to stdout
m_OwnsStream=false;
}
void useFile(std::string filename) {
m_Out=new std::ofstream(filename);
m_OwnsStream=true;
}
~Logger() {
if (m_OwnStream)
delete m_Out;
}
Obviously you'd need some more checks in useFile
and useStdOut
to prevent memory leaks.
The std::o*stream
classes in C++ inherit from std::ostream. This means you should write your interface depending on a std::ofstream pointer or reference:
class Logger
{
std::ostream *m_out; // use pointer so you can change it at any point
bool m_owner;
public:
// constructor is trivial (and ommited)
virtual ~Logger()
{
setStream(0, false);
}
void setStream( std::ostream* stream, bool owner )
{
if(m_owner)
delete m_out;
m_out = stream;
m_owner = owner;
}
template<typename T>
Logger& operator << (const T& object)
{
if(!m_out)
throw std::runtime_error("No stream set for Logger class");
(*m_out) << object;
return *this;
}
};
// usage:
Logger logger;
logger.setStream( &std::cout, false ); // do not delete std::cout when finished
logger << "This will be logged to std::cout" << std::endl;
// ...
logger.setStream(
new std::ofstream("myfile.log", std::ios_base::ate|std::ios_base::app),
true ); // delete the file stream when Logger goes out of scope
logger << "This will be appended to myfile.log" << std::endl;
Dr. Dobbs published an article that I've used as inspiration for logging. It's worth a read. http://www.drdobbs.com/cpp/201804215
It looks like another article has been published more recently too, but I have not read it.
http://www.drdobbs.com/cpp/225700666
A variation to the_mandrill solution, for that I thought a Strategy pattern would fit this problem better, conceptually.
We can change the log strategy at any time just by calling context->SetLogger.
We can also use different files for the file logger.
class Logger
{
protected:
ostream* m_os;
public:
void Log(const string& _s)
{
(*m_os) << _s;
m_os->flush();
}
};
class FileLogger : public Logger
{
string m_filename;
public:
explicit FileLogger(const string& _s)
: m_filename(_s)
{
m_os = new ofstream(m_filename.c_str());
}
~FileLogger()
{
if (m_os)
{
ofstream* of = static_cast<ofstream*>(m_os);
of->close();
delete m_os;
}
}
};
class StdOutLogger : public Logger
{
public:
StdOutLogger()
{
m_os = &std::cout;
}
};
class Context
{
Logger* m_logger;
public:
explicit Context(Logger* _l) {m_logger = _l;}
void SetLogger(Logger* _l) {m_logger = _l;}
void Log(const string& _s)
{
if (m_logger)
{
m_logger->Log(_s);
}
}
};
int main()
{
string filename("log.txt");
Logger* fileLogger = new FileLogger(filename);
Logger* stdOutLogger = new StdOutLogger();
Context* context = new Context(fileLogger);
context->Log("this log out to file\n");
context->SetLogger(stdOutLogger);
context->Log("this log out to standard output\n");
}