1. Edd Dawson
  2. blah

Wiki

Clone wiki

blah / filters

Filters

To see what filters are and how they work, let's first strip all the default behaviour out:

#include <blah.hpp>

#define MSG BLAH_PERMANENT_LOG("msg")

int main()
{
    while(blah::pop_filter())
        ;

    MSG << "Hello, world! " << 42;
    MSG << "Toodle-oo, world!";
    return 0;
}

Now the program will produce no output at all.

So let's build up the program again from scratch to illustrate how filters can be installed. blah has a class called filter that wraps a function or function-object with the following signature:

blah::progression (const blah::entry &, std::string &);

blah::progression is just an enum that defines two constants:

enum progression { stop = 0, carry_on = 1 };

A blah::entry is just a bundle of information about what was logged. I'll describe it in more detail later. Patience Daniel-san.

Our first filters

Let's define and add a filter that writes the string it is given to std::clog:

#include <blah.hpp>
#include <iostream>

#define MSG BLAH_PERMANENT_LOG("msg")

blah::progression write_to_clog(const blah::entry &, std::string &text)
{
    std::clog << text;
    return blah::carry_on;
}

int main()
{
    while(blah::pop_filter())
        ;

    blah::push_filter(&write_to_clog);

    MSG << "Hello, world! " << 42;
    MSG << "Toodle-oo, world!";
    return 0;
}

Now the output is:

Hello, world! 42Toodle-oo, world!

Hmm, it's an improvement, but we can now see the effect of not having blah insert new-line characters for us automatically. Let's define and add a filter for that:

// ...

blah::progression add_missing_newline(const blah::entry &, std::string &text)
{
    if (text.empty() || *text.rbegin() != '\n') text.push_back('\n');
    return blah::carry_on;
}

int main()
{
    while(blah::pop_filter())
        ;

    blah::push_filter(&write_to_clog);
    blah::push_filter(&add_missing_newline);

    MSG << "Hello, world! " << 42;
    MSG << "Toodle-oo, world!";
    return 0;
}

The output now:

Hello, world! 42
Toodle-oo, world!

Better. Note that each filter is passed a reference to a mutable std::string. In our add_missing_newline filter, we append '\n' to the text argument passed in.

The filters are kept internally in a stack so those added last are called before those added earlier. So in our code we've made sure that a new-line character is appended before any text is written to std::clog.

Filters defined by blah

Before I go any further, let me first point out that equivalents for the filters we've just written are provided by blah:

blah::push_filter(blah::write_to(std::clog)); // could use std::cerr or an std::ofstream instead
blah::push_filter(&blah::add_missing_newline);

The blah::write_to filter is a little more general than our write_to_clog. It creates a function object that writes to the given stream.

blah also defines a prepend_details filter which if added to our current example would result in the original output with time stamps and other decorations.

Please take a look at blah.cpp to see how that's defined. Getting the current date and time in a thread-safe and portable way takes a little bit of effort.

blah::entry objects

When you write to a log using blah, a bunch of additional information is recorded and passed to your filters so you can do some more involved things. This additional information is kept in an entry object:

struct entry
{
    // ...

    string log;         // The name of the log
    string file;        // The source file in which the logging occurred, __FILE__
    size_t line;        // The line of the source file in which the logging occurred, __LINE__
    string function;    // The name of the function in which the logging occurred
    string date;        // This is the date of *compilation*, __DATE__
    string time;        // This is the time of *compilation*, __TIME__
    thread_id_t thread; // The thread in which the logging occurred (integral id or pointer)
};

The strings in there aren't std::strings. They're a very light-weight string type provided by blah designed to wrap string literals (such as __FILE__, __DATE__ and __TIME__) at minimal cost.

They provide operators for stream insertion and comparisons with std::strings and const char *s among other things. You'd rather write

if (e.log == "msg") { /* ... */ } 

than

if (std::strcmp(e.log, "msg") == 0) { /* ... */ } 

right?

Have a look at blah.hpp for more information about blah::string.

To illustrate how entry objects might be used in a filter, let's write one that will print text to std::clog by default, but to std::cerr if the log's name is "err":

blah::progression errors_to_cerr(const blah::entry &e, std::string &text)
{
    (e.log == "err" ? std::cerr : std::clog) << text;
    return blah::carry_on;
}

When to return blah::stop in a filter

So far, we've always returned blah::carry_on from all our filters. If we instead return blah::stop, we can prevent logged text from making it any further down the filter stack.

A simple smut filter for illustration purposes:

#include <blah.hpp>

#define MSG BLAH_PERMANENT_LOG("msg")

blah::progression no_sex_please_im_british(const blah::entry &e, std::string &text)
{
    return (text.find("boobs") == std::string::npos) ? blah::carry_on : blah::stop;
}

int main()
{
    blah::push_filter(&no_sex_please_im_british);

    MSG << "I just had a sex change!";
    MSG << "What do you think of my new boobs?";
    return 0;
}

The second line doesn't appear in the output.

A note about thread safety

Even though blah is able to pass a thread identifier to each filter via the blah::entry argument, the library makes no attempt at thread safety. However, it is simple to add yourself.

To do this, ensure that any filter that exhibits side effects other than editing the given string locks and unlocks a shared mutex while those side-effects are occurring.

For example, we might put this filter at the bottom of our filters stack, rather than say, blah::write_to:

#include <blah.hpp>
#include <boost/thread/mutex.hpp> // or any implementation of your choice
#include <iostream>

namespace { boost::mutex logging_mutex; }

blah::progression serialized_write(const blah::entry &e, std::string &text)
{
    {
        boost::mutex::scoped_lock lk(logging_mutex);
        std::cout << text; // the side effect
    }
    return blah::carry_on;
}

Updated