Sign in to follow this  
averron82

C++ Logging Class

Recommended Posts

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:

[code]Log error_log("example.txt");

error_log << "An example error message " << 123;[/code]

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. :)

Share this post


Link to post
Share on other sites
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:
[code]
error ( "example.txt", "An example error message 123" );
[/code]

Share this post


Link to post
Share on other sites
For something a bit more heavyweight have you considered a prewritten logging library such as log4cpp for example ([url="http://log4cpp.sourceforge.net/"]http://log4cpp.sourceforge.net/[/url]) which is a c++ imitation of the 'famous' log4j java library?

I would seriously recommend [b]against[/b] 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. Edited by braindigitalis

Share this post


Link to post
Share on other sites
[quote name='Texus' timestamp='1309352416' post='4829051']
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:
[code]
error ( "example.txt", "An example error message 123" );
[/code]
[/quote]

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.

Share this post


Link to post
Share on other sites
[quote name='braindigitalis' timestamp='1309352609' post='4829053']
I would seriously recommend [b]against[/b] 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!)
[/quote]

That's good advice, thank you.

Share this post


Link to post
Share on other sites
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 [url="http://www.mr-edd.co.uk/blog/beginners_guide_streambuf"]custom std::streambuf classes[/url] 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.

Share this post


Link to post
Share on other sites
The approach I'm experimenting with at the moment is along the lines of:

[code]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;
};[/code]

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.

Share this post


Link to post
Share on other sites
I've been thinking about doing this too. I did this:


[code] 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";
[/code]

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:


[code] 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";[/code]

Share this post


Link to post
Share on other sites
[quote name='rip-off' timestamp='1309362239' post='4829111']

@Nanoha

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

It's not a singleton:

[code]Logger::Logger(LoggerLevel level, bool setAsGlobal)
{
if(setAsGlobal)
{
assert((instance == NULL) && "Global logger object already set");
instance = this;
}
}
[/code]
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");

Share this post


Link to post
Share on other sites
[quote name='averron82' timestamp='1309360511' post='4829097']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.
[/quote]

This worked for me

[code]template <typename T>
Log& operator<<(T const& value)
{
m_filestream << value;
return *this;
}[/code]

Share this post


Link to post
Share on other sites
[quote name='jonathanjansson' timestamp='1309363296' post='4829125']
[quote name='averron82' timestamp='1309360511' post='4829097']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.
[/quote]

This worked for me

[code]template <typename T>
Log& operator<<(T const& value)
{
m_filestream << value;
return *this;
}[/code]
[/quote]

This does work, but how would you go about implementing std::endl or something equivalent?

Share this post


Link to post
Share on other sites
[quote name='ObsessedMikey' timestamp='1309366871' post='4829144']This does work, but how would you go about implementing std::endl or something equivalent?[/quote]

I never bothered to find out until now :-)

They are called stream manipulators and it's my guess that if you pass the address of a function to a stream then the stream will call that function.

[code]#include <iostream>

std::ostream& hello(std::ostream& os)
{
return os << "world!";
}

int main(int argc, char** argv)
{
std::cout << hello << std::endl;
return 0;
}[/code]

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1309370286' post='4829167']
Check out [url="http://code.google.com/p/epoch-language/source/browse/Shared/User%20Interface/OutputStream.inl"]this code[/url] for an example of how to implement endl and your own stream manipulators with a custom stream wrapper class.
[/quote]

Nice link, took me a while to realise why I couldn't get endl to work (I'm not using unicode), had to change the wchar_t to plain old char and it works great.

Share this post


Link to post
Share on other sites
[quote name='averron82' timestamp='1309360511' post='4829097']
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.
[/quote]
Since you're intent on reinventing std::ostream as closely as possible, here's how std::endl works with std::ostream.

This is a member function of [font="Courier New"]std::ostream[/font].
[code]
__ostream_type&
operator<<(__ostream_type& (*__pf)(__ostream_type&))
{
return __pf(*this);
}
[/code]
It's a member function that takes a function that operates on the ostream and calls it. Now here's [font="Courier New"]std::endl[/font].
[code] template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
endl(basic_ostream<_CharT, _Traits>& __os)
{ return flush(__os.put(__os.widen('\n'))); }
[/code]
It's a function that operates on an ostream. If you pass the address of [font="Courier New"]std::endl[/font] to [font="Courier New"]std::ostream::operator<<()[/font], it will just call the function. Clever, no?

Using manipulators with parameters is a little trickier: you end up creating a functor that passes the function bound to its parameter (currying). It's not rocket surgery.

I would really recommend just sticking with [font="Courier New"]std::clog[/font] as your logger an replacing its streambuf. You are unlikely to do better and you stand to lose a lot of functionality when you reinvent it yourself.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this