Jump to content
  • Advertisement
Sign in to follow this  
Replicon

inserter operator woes

This topic is 4191 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I guess this counts as a newbie topic, since this is the first time I play with operator<<... I'm trying to implement a very simple logger that does two things: - log levels - outputs log to as many streams as I want it to. Roughly speaking, the usage looks something like:
Logger log(LogLevel::WARNING);
log.attachStream(std::cerr); // and any others, say file handles....

log << LogLevel::ERROR << "uh-oh, " << 75 << " errors occured...\n";
Currently, I don't yet have the LogLevel stuff working. So if we ignore the loglevel, it's pretty straight forward:
template< typename T >
Logger &operator<<(Logger &logger, const T &x)
Then, just keep shoving what you get into all the streams. But right off the bat, I am seeing an annoyance: I'd like to be able to call flush() on each of my output streams ONCE per "line of code" (for the lack of a better way of saying it). In other words, for something like:
log << 1 << 2 << 3 << 4 << 5;
I want to call flush() just once, after the whole "12345" has been shoved into my streams. Is there a way to do this without having to write a Logger::flush and call it manually? I'm thinking 'no'. And I'm thinking it probably doesn't matter (depending on implementation of flush I guess), but it's worth a shot hehe. Secondly, I'm not really sure how I'll put the log levels in there. Should I just change the operator<< above to have these two instead:
template< typename T >
LogLevel &operator<<(LogLevel &logLevel, const T &x)

Logger &operator<<(Logger &logger, LogLevel &logLevel);
and then have the loglevel one maintain an ostringstream, and have the Logger's operator<< look at logLevel and decide whether to publish the data? The problem I forsee there is that there's probably no guarantee on order of execution, so instead of the desirable:
operator<<(log,  operator<<(LogLevel::WARNING, "test"));
It might try something goofy like:
operator<<( operator<<(log, LogLevel::WARNING), "test" );
Which would not even compile... Thoughts?

Share this post


Link to post
Share on other sites
Advertisement

class LogWriter
{
public:
~LogWriter() {
m_appender.commit( m_log );
}

template < class T >
LogWriter &operator<<( const T &t )
{
m_log.m_text << t;
return *this;
}
private:
CoutAppender m_appender;
LogMessage m_log;
bool m_write;
};

struct LogMessage
{
std::stringstream m_text;
};

class Logger
{
LogWriter warning( void ) {
return LogWriter();
}
}


...


Logger log;

log.warning() << "1" << "2" << "3";




Simplified version of what I use.

To be fully thread-safe you need to buffer the message you're constructing.

The reason this works is that LogWriter object lives exactly for the duration of the statement. When it gets destroyed, the message is commited to actual output. And the reason that the "warning()" doesn't create an extra copy, despite returning by value is due to a very common optimization. To be safe, this first copy can be explicitly checked. There should be no additional copies made after that, since only references get passed around.

Appender is the implementation of the your actual log destination. Above there's only cout appender, but you could define multi-casting appender, that outputs same message to multiple destinations.

This isn't a perfect solution, nor will I claim it's guaranteed to work as intended. There's also the possibly problem of your appender throwing an exception. But it serves the syntactic form I chose.

Another reason which isn't shown here is that template magic regulates which log statements get compiled, and logs that are useful in debug builds are never generated for release builds, even without any use of macros.

Share this post


Link to post
Share on other sites
Quote:
Original post by M Eversberg II
Is that a valid operation for the object you named?
Yes.
Quote:
Original post by M Eversberg II
Normally it has to be an ostreamVar of some kind.
The operator is actually the bitwise shift operator that has been adopted for use in streams. [something that is a bit of an annoyance of mine, as it's frequent use in streams has led a whole generation of C++ coders thinking that this operator is something that it isn't. Case in point.]

Anyway, as to the actual questions presented. These operators will be processed from left to right, where such can be done, so you do indeed have a clue about the order inwhich your commands will be processed.

Log levels really depend on how you intend to be using them. For example, if this is legal :
Log a;
a << LogLevel::ERROR;
a << "this will be reported as an error";
a << "this will also be reported as an error";
Then you are intending your LogLevel to act on your logger class as a modifier [similar to how 'setprecision' can be used on iostreams]. In this case you should likely implement your logLevel modifier as a template specialization. Try setting up operator<<(Logger,LogLevel) to just modify internal state information for Logger, and have it return a Logger reference, instead of a LogLevel reference. This is pretty much how iostream handles it's modifiers.

If you intend on having your "LogLevel" be required in each logging instance [in other words, the above example would be illegal], then of course it would be different....

In any case, getting it to automaticly call 'flush' at the end of a line... good luck with that one. Honestly I can't think of a way to do it, though I've not dedicated much time to thinking about it.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Another reason which isn't shown here is that template magic regulates which log statements get compiled, and logs that are useful in debug builds are never generated for release builds, even without any use of macros.


Hey, thanks for that, I'm really liking that approach. Can you expand a bit on that last part? I was originally going to use an uglyish macro for this, but if you say the template magic fixes that, then I'm all ears.

Drigovas, I definitely want to enforce log levels, which is why I didn't go the iomanip way.

cheers

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!