Boost Synapse

Building a Simple Logging System

This program demonstrates how to use Boost Synapse to build a simple logging system. Logging is done by emitting log_message signals from different emitters based on the log message's severity.

It consists of the following source files:

  • logger.h/logger.cpp: these files implement the logging interface;
  • main.cpp: a simple program using the interface defined in logger.h.

Program output:

Message 1, Severity 0
Message 2, Severity 100
Message 2, Severity 100
Message 3, Severity 2
Message 3, Severity 2

logger.h:

namespace boost { template <class> class shared_ptr; }

#include <stdio.h>

struct logger;

//Create a logger object that can discriminate between messages based on severity up to the
//specified maximum. Messages with higher max_severity are assumed to be of max_severity.
boost::shared_ptr<logger> init_logger( int max_severity );

//Add a target for logging messages with at least the specified severity.
void add_log_target( logger &, boost::shared_ptr<FILE> const &, int min_severity );

//Get a Boost Synapse emitter based on the message's severity.
void const * severity( int severity );

//Emit this Boost Synapse signal to log a message.
typedef struct log_message_(*log_message)( char const * );

logger.cpp:

#include "logger.h"
#include <boost/synapse/translate.hpp>
#include <vector>

namespace synapse=boost::synapse;

namespace
    {
    //Emitters, indexed by severity.
    std::vector<boost::weak_ptr<void const> > emitters_;

    void
    log_string( boost::shared_ptr<FILE> const & f, char const * str )
        {
        assert(f);
        assert(str!=0);
        (void) fprintf(f.get(),"%s",str);
        }

    boost::weak_ptr<void const> const &
    severity_( int s )
        {
        assert(s>=0);
        assert(!emitters_.empty());
        return s>=emitters_.size()? emitters_.back() : emitters_[s];
        }
    }

struct
logger
    {
    private:
    logger( logger const & );
    logger & operator=( logger const & );
    public:
    explicit
    logger( int max_severity )
        {
        std::vector<boost::weak_ptr<void const> >(max_severity).swap(emitters_);
        }
    std::vector<boost::shared_ptr<synapse::connection> > connections_;
    };

boost::shared_ptr<logger>
init_logger( int max_severity )
    {
    assert(max_severity>0);
    boost::shared_ptr<logger> l(new logger(max_severity));

    //Populate the static emitters vector: each emitter is a weak_ptr initialized from a shared_ptr alias
    //of the logger object, but with a unique address.
    for( int i=0; i!=max_severity; ++i )
        emitters_[i]=boost::shared_ptr<void const>(l,&emitters_[i]);

    //Translate signals from higher severity emitters to lower severity emitters. This way a high severity
    //message will automatically trickle down to lower severity emitters. With this approach it is possible
    //to build a more complex translation DAG if needed.
    for( int i=0; i!=max_severity-1; ++i )
        l->connections_.push_back(synapse::translate<log_message,log_message>(emitters_[i+1],emitters_[i].lock().get()));

    return l;
    }

void
add_log_target( logger & l, boost::shared_ptr<FILE> const & target, int min_severity )
    {
    assert(target);
    assert(min_severity>=0);
    assert(min_severity<emitters_.size());

    //Connect the appropriate emitter based on severity. Since signals from higher severity emitters are
    //translated to lower severities, the target will only get the messages with severity >= min_severity.
    l.connections_.push_back(synapse::connect<log_message>(severity_(min_severity),
        [target]( char const * str )
            {
            log_string(target,str);
            } ) );
    }

void const *
severity( int s )
    {
    assert(s>=0);
    assert(!emitters_.empty());
    return (s>=emitters_.size()? emitters_.back() : emitters_[s]).lock().get();
    }

main.cpp:

#include "logger.h"
#include <boost/synapse/emit.hpp>

namespace synapse=boost::synapse;

namespace { struct null_deleter { void operator()( void const * ) { } }; }

void
do_some_logging()
    {
    synapse::emit<log_message>(severity(0),"Message 1, Severity 0\n");
    synapse::emit<log_message>(severity(100),"Message 2, Severity 100\n");
    synapse::emit<log_message>(severity(2),"Message 3, Severity 2\n");
    }

int
main()
    {
    //Create a logger that can distinguish between up to 3 severity levels: 0, 1 and 2.
    boost::shared_ptr<logger> const l=init_logger(3);

    //Severity 0 and 1 go to stdout, severity 2 and above go to stdout and stderr.
    add_log_target(*l,boost::shared_ptr<FILE>(stdout,null_deleter()),0);
    add_log_target(*l,boost::shared_ptr<FILE>(stderr,null_deleter()),2);

    do_some_logging();
    }