Sign in to follow this  
Endar

sorting of formatting for log messages

Recommended Posts

I'm currently re-writing my (woefully) inadequate message logging system. It is currently a glorified wrapper to printf using vsprintf and a variable argument list. I'm writing a proper bunch of classes, with a log manager which handles all the logs. You can have different logs, like one to output to a file, one to output to the console, one to output to something specific to your engine (like my in-engine debug console), etc, etc. For each log I want to be able to associate a bunch of objects that the message to be output is run through, either to format it, or to add things to it. I'm currently going to have one that adds the date and time, makes sure that there is a newline at the end, etc, etc. Okay, with the (likely useless) background out of the way, here's my problem: I'm trying to figure out how to sort the objects in a way so that certain ones format, or add to the message before others. For instance, if I have an object that adds the message type to the message, the date and time needs to go before that. Hardcoding something like this would be silly as it's designed so that it is easily extensible, and I'm not exactly sure how I would go about writing a sort for something like this. I'm am almost completely in the dark.

Share this post


Link to post
Share on other sites
Seems tailor made for the Decorator Pattern.

Essentially, something akin to:


class Logger{
void Log(string str);
}

class FileTarget: Logger{ ... }

class DateDecoration: Logger{
Logger WrappedLogger;

void Log(string str){
WrappedLogger.Log( Date + str );
}

}

FileTarget log1("App.log");
DateDecoration log2(log1);

App.LocalLog=log2;






I posted a bit of C# code for just this thing in my journal (edit: 5 down from the top at time of writing, the 'link to entry' isn't working nicely.)

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
Seems tailor made for the Decorator Pattern.


I'm not really sure about the Decorator pattern. I get that it inherits from the thing that you want to alter, but I don't think that I quite need that.

I'm want to alter the actual message (which is a std::string), and not the actual ILogger object.

Maybe I just don't completely understand the Decorator pattern yet, please help.

In the Decorator pattern the class to be altered by the Decorators has a list of the Decorators that it has, right? This is the same was what I have, except my "Decorator" objects alter the std::string that represents the message to be logged. At the moment I'm looking for a way of sorting them, which will only happen when one is added, in a way so that some things are specifically before others, without hardcoding the differences so that I can still add more classes without fear of throwing everything out of whack.

So, no matter when the "DateTime" object is added, the list of objects is sorted so the date and time is added to the very front of the message to be logged, and whatever it was that previously went at the front of the message (message type) would then be coming second, because nothing should come before the date and time.

I suppose that I'm looking for a way to impose arbitrary importances on these objects and be able to run a sorting algorithm that will take those importances into account, and be able to maintain extensibility, all without hardcoding the importances of the different objects, as that would take away some of the ease of extensibility.

I think that might be fairly hard to do.

Share this post


Link to post
Share on other sites
Quote:

That seems to be some odd requirements. I mean if you want the user to be able to order them, why not just let them do it?


Eh, yes that's the jist of it.

If you're going to enforce the ordering of the whole then yeah, decoration won't work so well.

Share this post


Link to post
Share on other sites
My suggestion would be to use a std::set, using a custom predicate to order the log formatters. As far as the formatters go, I'd make use of Boost.Function and and have the std::set store boost::function objects.

More of a Visitor pattern than a Decorator pattern. The function argument should be a reference to the message about to be written, and the formatter can alter it or add to it as it will.

The actual write function would look something like this:



struct apply_func
{
std::string& message;
apply_func( std::string& msg ) : message(msg) {}

template< class T >
void operator()( const T& formatter ) const
{
formatter(message); // Apply the formatting
}
};


// no use of const here because the message
// will be altered by the formatters, so we
// might as well pass by value and save the
// explicit copy
void logger::write_message( std::string message )
{
// pass to formatters
// Assume that formatters is the
// set of formatters.
std::for_each( formatters.begin(), formatters.end(), apply_func(message) );

// Finally write the message to the fie
out_file << message;
}





You get the idea...

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
Quote:

That seems to be some odd requirements. I mean if you want the user to be able to order them, why not just let them do it?


Eh, yes that's the jist of it.

If you're going to enforce the ordering of the whole then yeah, decoration won't work so well.


And that's also a problem. Whether I should be enforcing the ordering at all. I mean, I want to make it as easy as possible for the user, but I also want to give them control.

So, I'm split between enforcing an arbitrary ordering and letting the user have as much control as possible at the expense of ease of use.

Share this post


Link to post
Share on other sites
Actually, you got me thinking, so I whipped up something that did kinda what you were thinking. Quick and dirty, but still fun to get working :)

formatter.h

#ifndef FORMATTER_H
#define FORMATTER_H

#include <iostream>
#include <set>
#include <fstream>
#include <algorithm>

#include <boost/function.hpp>

typedef boost::function< void ( std::string& ) > formatter_function_t;
typedef std::pair<int,formatter_function_t> formatter_obj;

struct compare_formatters
{
bool operator()( const formatter_obj& left, const formatter_obj& right ) const
{
return left.first < right.first;
}
};

struct apply_formatter
{
apply_formatter( std::string& msg ) : message(msg) {}
std::string& message;

void operator()( const formatter_obj& fmt ) const
{
fmt.second( message );
}
};

typedef std::set<formatter_obj,compare_formatters> formatter_set;

struct formatter
{
public:
formatter( std::ostream& out )
: m_out( out ), next_priority(50)
{
}

void add_formatter( formatter_function_t formatter )
{
formatters.insert( std::make_pair( next_priority, formatter ) );
next_priority++;
}

void add_formatter( int priority, formatter_function_t formatter )
{
formatters.insert( std::make_pair( priority, formatter ) );
}

void write( std::string message )
{
// Apply all formatting
std::for_each( formatters.begin(), formatters.end(), apply_formatter( message ) );

// Write out the final message
m_out << message;
}

private:
std::ostream& m_out;
formatter_set formatters;
int next_priority;
};

void operator << ( formatter& out, const std::string& message )
{
out.write( message );
}

// Sample append / prepend formatters
struct prepend_formatter
{
prepend_formatter( const std::string& msg ) : message(msg) {}
std::string message;

void operator()( std::string& message ) const
{
message.insert( 0, this->message );
}
};

struct append_formatter
{
append_formatter( const std::string& msg ) : message(msg) {}
std::string message;

void operator()( std::string& message ) const
{
message.append( this->message );
}
};

#endif//FORMATTER_H




And to use:


#include "formatter.h"

int main(int argc,char* argv[])
{
formatter log( std::cout );
// For a file, do something like:
//
// std::fstream outfile( "somefile.txt", std::ios::out | std::ios::app );
// formatter log( std::cout );

log.add_formatter( prepend_formatter( "<some data> " ) );
log.add_formatter( append_formatter( " [some more data]" ) );


log.add_formatter( 499, prepend_formatter( "[LOG] " ) );
log.add_formatter( 500, append_formatter( "\n" ) );

log << "Hello";
log << "Testing..";
log << "One, two, three...";

// Alternatively, you could do:
// log.write( "Hello" );
// log.write( "Testing.." );
// log.write( "One, two, three..." );

return 0;
}




A note about ordering: Unless specified with the add_formatter() overload that accepts a priority, then the formatters will be called in the order they're added (default priority is 50, and increments each time a formatter is added without a specific priority).

Yes, I know I could have achieved the same effect with a vector, but this would disallow explicit ordering of visitors.

Share this post


Link to post
Share on other sites
O...M...G... It's a logger it should be simple and fast to use, every class needing to include it, no thanks, other painful means, no thanks. You'll be less likely to use it as much as you likely should.

There is nothing wrong with global functions in a namespace to handle this for you. A simple Log(location, level, message) works so nice (just save your sanity and don't use global variables, debugging can be a pita with those). I have seperate functions to open new files which you can then use in the location value, or to predefined like console, remote debugger, etc which if they're not open is just dropped (I have a OutputDebugString() that is #ifdef/#endif, for example). I have a function to set the level messages are actually logged at so you can dyanmically change that (or without recompiling if you want your engine less verbose).

KISS is the key rule I believe with loggers.

Share this post


Link to post
Share on other sites
Yeah, that was definitely a bit over the top. More of an exercise in Boost.Function than anything else.

Personally I would advocate the use of a global level function for logging, as the above poster mentioned, but I had fun coming up with that, and wanted to share.

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