sorting of formatting for log messages

Started by
8 comments, last by daerid 17 years, 9 months ago
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.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Advertisement
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.)
one approach is multimap and enum

Kuphryn
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.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
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.
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 copyvoid 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...
daerid@gmail.com
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.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
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 formattersstruct 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.
daerid@gmail.com
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.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

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.
daerid@gmail.com

This topic is closed to new replies.

Advertisement