C++ Logging Class

Started by
15 comments, last by Bregma 12 years, 9 months ago
Hi, I'm looking for advice on implementing a C++ class for logging messages to one or more text files. Ideally I'd like such a class to overload the standard io operators, allowing usage in the form:

Log error_log("example.txt");

error_log << "An example error message " << 123;


In particular I'm unsure whether I should be looking to inherit from a subclass of std::ostream, or whether to encapsulate its functionality through composition.

Any advice or examples would be appreciated - thanks. :)
Advertisement
Why don't you just use a function that will open a file, writes an error message in it and then closes the file.
Then you just call the function like this:

error ( "example.txt", "An example error message 123" );
TGUI, a C++ GUI for SFML
texus.me
For something a bit more heavyweight have you considered a prewritten logging library such as log4cpp for example (http://log4cpp.sourceforge.net/) which is a c++ imitation of the 'famous' log4j java library?

I would seriously recommend against opening and closing the file for each line unless your logging is VERY sparse. Doing it this way causes immense CPU and I/O usage for each log line as close() usually invokes a flush to disk, and will make verbose logging infeasible for any larger project (been there, done that!)
If you are rolling your own, keep the file handle open at all times, and close it when the program exits, or on a timer if you want to see up-to-date information and are for example watching the log in another window and want it to update.

Why don't you just use a function that will open a file, writes an error message in it and then closes the file.
Then you just call the function like this:

error ( "example.txt", "An example error message 123" );



Thanks, I've used that approach in the past - I was just looking for something a little more consistent with standard output streams like std::cout, std::cerr, etc. I'm doing this for practise more than anything, so would like to be familiar with both techniques.

I would seriously recommend against opening and closing the file for each line unless your logging is VERY sparse. Doing it this way causes immense CPU and I/O usage for each log line as close() usually invokes a flush to disk, and will make verbose logging infeasible for any larger project (been there, done that!)


That's good advice, thank you.
One solution is to do nothing. Keep writing to std::cout, std::clog or std::cerr as usual. Then you can use the external environment to handle writing to files, etc. On most operating systems you can redirect/append the standard streams to a file, or you can pipe them through an additional process, something like "grep" to filter for particular messages or a more complicated process which might handle remote logging, etc.

Another solution is writing custom std::streambuf classes and replace the ones in std::cin, std::cerr and std::clog. This allows you to control where the output from these objects goes.

I like the first option. It is simple as it requires no code, and is flexible because you can change what you are doing with the logging after you've built the executable.
The approach I'm experimenting with at the moment is along the lines of:

class Log
{
public:
Log(const char* filename)
{
m_filestream.open(filename, std::fstream::out);
}

~Log(void)
{
m_filestream.close();
}

inline Log& operator<<(const char* text)
{
m_filestream << text;
return *this;
}

inline Log& operator<<(const float text)
{
m_filestream << text;
return *this;
}

private:
std::fstream m_filestream;
};


This appears to work, however I'm very much open to feedback. One slight difficulty is that I would ideally like to make it compatible with std::endl, and I'm unsure how this would be achieved.
I've been thinking about doing this too. I did this:


template <class T>
Logger& operator<<(const T &in)
{
if(m_File) { *m_File << in; }
if(m_ConsoleStream) { *m_ConsoleStream << in; }
return *this;
}

Logger::GetInstance() << "Hello" << " this is a test" << 23 << "new line\n" << 8.01 << "\n" << "blah blah";


Just waiting for my project to rebuild before I can see if the numebrs work. m_File is just an fstream, m_ConsoleStream is a console window (inherits from iosteam). It wouldn't compile with std::endl but escape characters should work. I could perhaps do a specific overload for endl. Though I have a get instance, I can have many logger objects (one can set itself as being global).

Best I could get with endl was:


class Logger
{
public:
class endl {};
template <class T>
Logger& operator<<(const T &in)
{
if(m_File) { *m_File << in; }
if(m_ConsoleStream) { *m_ConsoleStream << in; }
return *this;
}
Logger& operator<<(const endl&)
{
if(m_File) { *m_File << std::endl; }
if(m_ConsoleStream) { *m_ConsoleStream << std::endl; }
return *this;
}
};

Logger::GetInstance() << "Hello" << " this is a test" << 23 << "new line\n" << 8.01 << Logger::endl() << "blah blah";

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

@averron82

typedef std::ofstream Log;

@Nanoha

I don't see the benefit of a singleton over a simple global or free function here.


@Nanoha

I don't see the benefit of a singleton over a simple global or free function here.


It's not a singleton:

Logger::Logger(LoggerLevel level, bool setAsGlobal)
{
if(setAsGlobal)
{
assert((instance == NULL) && "Global logger object already set");
instance = this;
}
}

Its owned by something and I can have many (but only one globally accessaable one at a time). Its somehting I saw a Game Programming Complete that I found to be neat and useful. In reality I just use functions that can access the "instance" directly

Log(LL_ERROR, "Pixels must be consecutive");

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

This topic is closed to new replies.

Advertisement