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 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;
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.
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");
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With