🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Loggers acceptable use of global variables?

Started by
23 comments, last by Bregma 17 years, 1 month ago
Quote: Original post by Extrarius
Quote: Original post by Bregma
[...]It's called std::clog and for error logging, std::cerr.[...]
Is there a standard way to redirect it to send to a different stream? If not, you'll have to use OS- or compiler- specific code to make sure it gets to the console or to a file (since win32 gui programs, for example, don't have a console by default and don't redirect the streams anywhere useful and I imagine the same is true when dealing with n?x windowing systems)


Shouldn't need any of that. Like Bregma said: just replace the streambuf.
Advertisement
Okay, I'm now very interested in this idea of using std::cerr and std::cout, could any of you point me in the direction of some kind of documentation that'll tell me how to modify them (replacing streams and whatnot)? Or better yet, Bregma, could you post a little code, if it's not too messy/big/much trouble/against your wishes? I see the error of my ways now, I'm going to ditch my current logging system. It was buggy anyway.
Quote: Original post by hymerman
Okay, I'm now very interested in this idea of using std::cerr and std::cout, could any of you point me in the direction of some kind of documentation that'll tell me how to modify them (replacing streams and whatnot)?


Hmm, well, I've been intending to write a series of articles on my website but as usual time and the elements have conspired against me. I'll give a few basics, though.

First off, I use a custom streambuf. Here's the header file for a class that just wraps whatever is already in a standard stream and prepends a severity, a timestamp, and an optional facility. Just as an example.
#include <streambuf>#include <string>namespace Bregma{	/**	 * The DebugStreambuf provides a concrete basic_streambuf that appends a	 * timestamp and process ID (or thread ID) at the start of each new line.	 *	 * This streambuf is an unbuffered streambuf.  It is not capable of input.	 */	template<typename Char, typename Traits = std::char_traits<Char> >		class DebugStreambuf		: public std::basic_streambuf<Char, Traits>		{		public:			typedef typename std::basic_streambuf<Char, Traits>::traits_type traits_type;			typedef typename std::basic_streambuf<Char, Traits>::int_type    int_type;		public:			/**			 * Buffer construction.			 */			DebugStreambuf(std::basic_streambuf<Char, Traits> *pRealBuf);			void setLogLevel(const LogLevel &logLevel)			{ m_logLevel = logLevel; }			void setFacility(const std::string &facility)			{ m_facility = facility; }		protected:			/**			 * Function called by an ostream when it's time to send something out.			 *			 * @param c The value to be written out (generally a single character).			 *			 * @returns A value equal to traits_type::eof() on failure,			 * traits_type::not_eof() on success.			 */			int_type			overflow(int_type c = traits_type::eof());		private:			DebugStreambuf(const DebugStreambuf&);			DebugStreambuf& operator=(const DebugStreambuf&);		private:			std::basic_streambuf<Char, Traits> *m_pRealBuf;			bool                                m_bAtBeginningOfLine;			LogLevel                            m_logLevel;			std::string                         m_facility;		};} // namespace Bregma

The LogLevel is an enum in another header file. Don't worry about it for now. The important thing is the implementation. Keep in mind this is modified from production code, so any errors are mine during online editing (not sure if I got the braces right, this online editor is awkward).
#include "debugstreambuf.h"#include <sstream>using namespace std;/** * Constructs a basic DebugStream. */template<typename C, typename T>	Bregma::DebugStreambuf<C,T>::		DebugStreambuf(basic_streambuf<C,T>* pRealBuf)		: m_pRealBuf(pRealBuf)		, m_bAtBeginningOfLine(true)		, m_logLevel(kLOG_INFO)		{		}/** * Actual function to move bytes to the logging stream if appropriate. */template<typename C, typename T>	typename Bregma::DebugStreambuf<C,T>::int_type Bregma::DebugStreambuf<C,T>::	overflow(int_type c)	{		int_type retval = traits_type::not_eof(c);		if (!traits_type::eq_int_type(c, traits_type::eof()))		{			if (m_bAtBeginningOfLine)			{				m_bAtBeginningOfLine = false;				basic_ostringstream<C,T> ostr;				// Format and display the level tag.				char tag = '?';				switch (m_logLevel)				{					case kLOG_FATAL:						tag = 'F';						break;					case kLOG_ERROR:						tag = 'E';						break;					case kLOG_WARNING:						tag = 'W';						break;					case kLOG_INFO:						tag = 'I';						break;					case kLOG_VERBOSE:						tag = 'V';						break;					case kLOG_DEBUG:						tag = 'D';						break;					default:						tag = '?';				}				ostr << '-' << tag << '-';				// Format and display the time stamp.				time_t curTime = std::time(NULL);				char timestamp[32];				std::strftime(timestamp,					      sizeof(timestamp),                                              "%Y.%m.%dT%H:%M:%S",					      localtime(&curTime));				ostr << timestamp;				// Format and display the facility.				if (!m_facility.empty())				{					ostr << '[' << m_facility << ']';				}				if (!ostr.str().empty())				{					ostr << ": ";				}				// Send the prefix string out.				const basic_string<C,T>& str = ostr.str();				streamsize sz = m_pRealBuf->sputn(str.c_str(), str.length());				if (sz != str.length())				{					return traits_type::eof();				}			}			// Send the real character out.			retval =  m_pRealBuf->sputc(c);		}		// If the end-of-line was seen, reset the beginning-of-line indicator and		// the default log level.		if (traits_type::eq_int_type(c, traits_type::to_int_type('\n')))		{			m_bAtBeginningOfLine = true;			m_logLevel = kLOG_INFO;		}		return retval;	}


None of the above is publicly available and can live in its own little DLL if necessary (or not). The public interface would be in a header file.
namespace Bregma{	enum LogLevel	{		kLOG_FATAL,		kLOG_ERROR,		kLOG_WARNING,		kLOG_INFO,		kLOG_VERBOSE,		kLOG_DEBUG	};	/**	 * Convert an IOStream to a Bregma logging stream.	 *	 * This can be used to convert, for example, std::cerr into a Bregma logging	 * stream.	 *	 * @param ostr  [IN]  The IOStream to convert.	 * @param level [IN]  The default loglevel cutoff (default is INFO).	 * @param flags [IN]  Flags to toggle various output fields.	 */	void convertToBregmaLogger(std::ostream   &ostr);	/**	 * Setter for the log level cutoff.	 *	 * @param ostr  [IN]  The IOStream for which the log cutoff is to be set.	 * @param level [IN]  The new log level cutoff.	 */	void setLogCutoff(std::ostream &ostr, const LogLevel &level);	/**	 * Manipulator helper for setting the current log level on a debug stream.	 */	class LogLevelSetting	{	public:		LogLevel level() const { return m_level; }	private:		explicit LogLevelSetting(LogLevel level): m_level(level) {}		friend const LogLevelSetting logLevel(LogLevel);	private:		LogLevel m_level;	};	/**	 * Ostream manipulator for setting the current log level.	 */	inline const LogLevelSetting logLevel(LogLevel level)	{		return LogLevelSetting(level);	}	/**	 * Ostream inserter for the log level manipulator.	 *	 * @param ostr [IN] The output stream.	 * @param ls   [IN] The log level setting.	 */	std::ostream& operator<<(std::ostream& ostr, const LogLevelSetting ls);	/**	 * A manipulator helper for seeting the facility.	 */	class LogFacilitySetter	{	public:		const std::string &facility() const { return m_facility; }	private:		LogFacilitySetter(const std::string &facility): m_facility(facility) {}		friend const LogFacilitySetter logFacility(const std::string &facility);	private:		const std::string& m_facility;	};	/**	 * An ostream manipulator for setting the current facility in the log stream.	 */	inline const LogFacilitySetter logFacility(const std::string &facility)	{		return LogFacilitySetter(facility);	}	/**	 * Ostream inserter for the facility manipulator.	 *	 * @param ostr [IN] The output stream.	 * @param ls   [IN] The log facility setter manipulator helper..	 */	std::ostream& operator<<(std::ostream& ostr, const LogFacilitySetter ls);	/**	 * A handy stream to mark the entry and exit of a scope.	 *	 * @param ostr [IN] The output stream.	 * @param ls   [IN] A string.	 */	class ScopeMarker	{	public:		ScopeMarker(std::ostream &ostr, const std::string &what)		: m_ostr(ostr)		, m_what(what)		{ 			m_ostr << m_what << " begins\n";		}		~ScopeMarker()		{			m_ostr << m_what << " ends\n";		}	private:		std::ostream  &m_ostr;		std::string    m_what;	};} // namespace Bregma

Naturally you'll need an implementation for some of the above.
void Bregma::convertToBregmaLogger(ostream        &ostr){	ostr.rdbuf(new DebugStreambuf<char>(ostr.rdbuf()));}ostream &Bregma::operator<<(ostream& ostr, const Bregma::LogLevelSetting ls){	typedef DebugStreambuf<char> Dstr;	Dstr *pDstr = dynamic_cast<Dstr*>(ostr.rdbuf());	if (pDstr)	{		pDstr->setLogLevel(ls.level());	}	return ostr;}ostream &Bregma::operator<<(ostream& ostr, const Bregma::LogFacilitySetter ls){	typedef DebugStreambuf<char> Dstr;	Dstr *pDstr = dynamic_cast<Dstr*>(ostr.rdbuf());	if (pDstr)	{		pDstr->setFacility(ls.facility());	}	return ostr;}

At last, an example of how to use this.
#include "bregmalogger.h"#include <iostream>using namespace std;using namespace Bregma;int main(int, char*[]){  convertToBregmaLogger(cerr);  cerr << logFacility("TEST") << logLevel(kLOG_WARNING) << "This is a demo.\n";}


As I said, I have a whole lot more that needs to be turned into human-readable annotated code (and provided as a downloadable source). I just need a Round Tuit and another 25 hours in the day.

--smw

Stephen M. Webb
Professional Free Software Developer

Thank you very much, Bregma, you've been "Extremely helpful and/or friendly" and have been rated as such. I look forward to a proper release of this, but I shan't hold my breath; you've been incredibly helpful already. I'll scurry off and adapt this now, and try to explain to my team why I've chosen to drop XML/XSLT/other goodies in favour of std::cerr ;)

Again, thanks :)
Sorry for the double-post, but I do have one quick question: Is your implementation thread-safe, Bregma? I notice one of your comments seems to indicate it'll work with threads, but I'd like to hear it from the horse's mouth, so to speak :)
You can write some #defines to get the __FUNC__, __FILE__ and __LINE__. I'm not a fan of defines myself but for this it works wonders.
Quote: Original post by hymerman
Sorry for the double-post, but I do have one quick question: Is your implementation thread-safe?


The implementation I posted, no it's not threadsafe. As it stands, you can run into two threading-related issues: first, output from multiple threads is intermingled and second, an application can actually deadlock during output if a thread has crashed due to double deletions (at least under Linux, because of the mutex in glibc's malloc()/free() implementation). I tell you this from direct experience.

I did not include any of the code that makes this threadsafe because I thought it already complicated enough considering there was no explanation of how it works and because by its nature threading is platform-specific. In the production code I'm using, I have a bunch of threading stuff wrapped in a library but that's just yet more code to pull in and have to explain in a quick example.

There are in fact a number of ways to make the code threadsafe. The simplest is to use a separate streambuf for each thread: as soon as a thread starts, you can replace the std::clog streambuf with a thread-specific ofstreambuf that sends its output to, say, a file with the threadid as a part of its name. No intermingling, no deadlocks, completely threadsafe.

By the way, my example code just wrapped the existing streambuf from stderr. There's no reason why you can't use a different streambuf entirely so you could redirect output to, say, a file, a pipe or a Window, and no reason why this can't be done based on settings in a configuration file. There's also no reason why you can't output in XML so you can later transform the log info using XSLT or whatever today's silver bullet is.

--smw

Stephen M. Webb
Professional Free Software Developer

Okay Bregma, I'm really sorry for being such a pesky ho of a noob, but I'm having trouble making this thread safe. I'm trying to splice your code together with the only reference I could find, this article:

http://uk.builder.com/programming/c/0,39029981,20279107-1,00.htm

It's horribly written and a bit heavyweight, but it's all I can find! Would it be possible to either post a quick example as to how to make your code thread safe, or to post your production code? I doubt it'll be more confusing than the article above ;)

Of course, I totally understand if you don't want to waste any more time on this, or if you don't want to post your code. Just say so and I'll bugger off :)
Quote: Original post by hymerman
Would it be possible to either post a quick example as to how to make your code thread safe, or to post your production code?


Unfortunately I am not at liberty to post the production code at this time and it seems we just hit crunch mode at work so I don't have the time to do a reasonable treatment for public consumption. Solutions for thread safety are of necessity platform-specific, but if you need a hint I suggest using thread-local storage to hold a pointer to a local buffer for each thread and a single mutex to guard the wrapped filebuf, grabbing the mutex and flushing only when you encounter a newline (the latter is already there in the code above).

--smw

Stephen M. Webb
Professional Free Software Developer

That's okay, I figured my question was particularly cheeky :) You've been a great help anyway. Good luck with the crunch.

This topic is closed to new replies.

Advertisement