Jump to content

  • Log In with Google      Sign In   
  • Create Account


C++ Logging Class


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
16 replies to this topic

#1 averron82   Members   -  Reputation: 101

Like
0Likes
Like

Posted 29 June 2011 - 06:52 AM

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

Sponsor:

#2 Texus   Members   -  Reputation: 233

Like
-2Likes
Like

Posted 29 June 2011 - 07:00 AM

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

TGUI.Net, a GUI for SFML.Net


#3 braindigitalis   Members   -  Reputation: 150

Like
1Likes
Like

Posted 29 June 2011 - 07:03 AM

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.

Edited by braindigitalis, 29 June 2011 - 07:05 AM.


#4 averron82   Members   -  Reputation: 101

Like
0Likes
Like

Posted 29 June 2011 - 07:10 AM

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.

#5 averron82   Members   -  Reputation: 101

Like
0Likes
Like

Posted 29 June 2011 - 07:20 AM

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.

#6 rip-off   Moderators   -  Reputation: 7963

Like
1Likes
Like

Posted 29 June 2011 - 07:20 AM

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.

#7 averron82   Members   -  Reputation: 101

Like
0Likes
Like

Posted 29 June 2011 - 09:15 AM

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.

#8 Nanoha   Members   -  Reputation: 296

Like
0Likes
Like

Posted 29 June 2011 - 09:38 AM

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";


#9 rip-off   Moderators   -  Reputation: 7963

Like
2Likes
Like

Posted 29 June 2011 - 09:43 AM

@averron82

typedef std::ofstream Log;

@Nanoha

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

#10 Nanoha   Members   -  Reputation: 296

Like
0Likes
Like

Posted 29 June 2011 - 09:53 AM

@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");

#11 jonathanjansson   Members   -  Reputation: 271

Like
1Likes
Like

Posted 29 June 2011 - 10:01 AM

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.


This worked for me

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


#12 ObsessedMikey   Members   -  Reputation: 101

Like
0Likes
Like

Posted 29 June 2011 - 11:01 AM

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.


This worked for me

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


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

#13 Prads   Members   -  Reputation: 139

Like
0Likes
Like

Posted 29 June 2011 - 11:13 AM

std::endl is used to put a new line right? So, why not use "\r\n" instead? That would also give you a new line...
My first 3D game: Click Here

#14 jonathanjansson   Members   -  Reputation: 271

Like
0Likes
Like

Posted 29 June 2011 - 11:44 AM

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


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.

#include <iostream>

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

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


#15 ApochPiQ   Moderators   -  Reputation: 14621

Like
1Likes
Like

Posted 29 June 2011 - 11:58 AM

Check out this code for an example of how to implement endl and your own stream manipulators with a custom stream wrapper class.

#16 Nanoha   Members   -  Reputation: 296

Like
0Likes
Like

Posted 29 June 2011 - 02:37 PM

Check out this code for an example of how to implement endl and your own stream manipulators with a custom stream wrapper class.


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.

#17 Bregma   Crossbones+   -  Reputation: 4823

Like
0Likes
Like

Posted 29 June 2011 - 04:42 PM

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.

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 std::ostream.
      __ostream_type&
      operator<<(__ostream_type& (*__pf)(__ostream_type&))
      {
	return __pf(*this);
      }
It's a member function that takes a function that operates on the ostream and calls it. Now here's std::endl.
template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>& 
    endl(basic_ostream<_CharT, _Traits>& __os)
    { return flush(__os.put(__os.widen('\n'))); }
It's a function that operates on an ostream. If you pass the address of std::endl to std::ostream::operator<<(), 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 std::clog 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.
Stephen M. Webb
Professional Free Software Developer




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS