|
From: Caleb Epstein <caleb.epstein <at> gmail.com>
Subject: Re: [Review] Boost.Logging: formal review Newsgroups: gmane.comp.lib.boost.devel Date: 2005-12-19 22:15:20 GMT (3 years, 28 weeks, 8 hours and 20 minutes ago) On 12/19/05, Gennadiy Rozental <gennadiy.rozental <at> thomson.com> wrote: > > I am all for it ;) OK, so to that end, please find attached a sketchy implementation of some of these concepts. The concepts are: - Entry - represents a message to be logged. This can be a single std::string or a more complex structure, whatever the user likes. Should model OutputStreamable, but this requirement is up to the Sink. Filters may place additional requirements on the Entry. - Filter - a functor that is used to determine if an Entry should be sent to a Logger's Sink list. A model of std::unary_function<Entry, bool> - Sink - a destination for Entries. Concrete implementations might write to a file, debug window, etc. A model of std::unary_function<Entry, void> - Logger - the object to which Entries are sent by the user. Contains a Filter and a list of Sinks. When the user sends an Entry to the Logger, it will call the Filter functor and, if it returns true, pass it to each of the Sinks it holds. I've attached a sample implementation (log.h) which contains the following classes in "namespace logging" (I ran into collisions with math.h when I tried "namespace log"): - template<typename Entry> null_filter: a Filter that always returns true. - template<typename Entry> basic_file_sink: a Sink implementation that opens a std::ofstream and writes Entries to it - template<typename Entry> basic_ostream_sink: a Sink that holds an std::ostream reference and writes Entries to it. - template<typename Entry> basic_logger: the implementation of the Logger concept. Holds a Filter and a std::list of Sinks, where Filter is implemented as boost::function<bool(Entry)> and Sink is implemented as boost::function<void(Entry)>. Provides non-const reference getters for the filter and sinks members, and of course implements the all important "write" method that does the real work of checking the Filter and passing the Entry to the Sinks if the Filter returned true. I've also provided (in tuple_log.h) an implementation of the Entry concept which attempts to implement Gennadiy's ideas of level, category, and keyword. It is in "namespace tuple_log" and is simply a boost::tuple<level, category, std::string, std::string> where level and category are just wrapped enums with streaming operations added (the proposed BOOST_ENUM would be handy here). Along with the tuple-style Entry class there is an associated filter functor. It can be used to filter messages based on level (>= some threshold), category (matching a bitmask), and keyword (keyword appears in an "include" list and does not appear in an "exclude" list). There is a test program (log.cpp) that exercises these classes and attempts to do some benchmarking. Tested on g++ 3.3.4 on Linux and MSVC8 on Win2000. Some open issues I'm looking for help with: - I'm more or less ignorant when it comes to wide-strings, wide-streams and the like. Would these interfaces need to be paramterized on CharT, or would/should it be possible to support both narrow and wide input with a single interface? - I decided that the Entry concept would represent the entire message-to-be-logged, including any metadata that goes along with it ( e.g. level, category, keyword, thread ID, timestamp, __FILE__, __LINE__, etc). The Filter implementation is consequently parameterized on Entry, which means you need a fully-formed Entry (including the message portion) to call "basic_logger::is_enabled" to check to see if an Entry should even be formatted for writing. Thoughts and feedback are most welcome, -- Caleb Epstein caleb dot epstein at gmail dot com
#ifndef __CAE_LOG_H
#define __CAE_LOG_H
#include <iostream> // For basic_ostream_sink
#include <fstream> // For basic_file_sink
#include <string>
#include <functional>
#include <list>
#include <boost/function.hpp>
namespace logging {
/// No-op filter for use as the default Filter on a Logger. Passes
/// all messages.
template<typename Entry>
struct null_filter : public std::unary_function<bool, Entry> {
bool operator () (const Entry&) const { return true; }
};
/// Sink that opens an output file and writes Entries to that file
template<typename Entry>
struct basic_file_sink {
std::ofstream m_file;
bool m_flush;
basic_file_sink (const std::string& filename,
std::ios_base::openmode mode = std::ios_base::app,
bool flush = true)
: m_file (filename.c_str(), mode),
m_flush (flush)
{}
void operator () (const Entry& entry) {
m_file << entry << '\n';
if (m_flush)
m_file.flush();
}
};
/// Sink implementation that holds a reference to an ostream and
/// writes Entries to that stream
template<typename Entry>
struct basic_ostream_sink {
std::ostream& m_os;
bool m_flush;
basic_ostream_sink (std::ostream& os, bool flush = true)
: m_os (os),
m_flush (flush)
{}
void operator () (const Entry& entry) {
m_os << entry << '\n';
if (m_flush)
m_os.flush();
}
};
/// Basic Logger implementation. Contains a Filter and a list of
/// Sinks to which to write Entries
template<typename Entry>
class basic_logger {
public:
typedef Entry entry_type;
typedef boost::function<bool (Entry)> filter_type;
typedef boost::function<void (Entry)> sink_type;
typedef std::list<sink_type> sink_list;
basic_logger ()
: m_filter (null_filter<entry_type> ())
{}
basic_logger (const filter_type& filter)
: m_filter (filter)
{}
template<typename Iter>
basic_logger (const filter_type& filter,
Iter sink_begin, Iter sink_end)
: m_filter (filter),
m_sinks (sink_begin, sink_end)
{}
filter_type& filter ()
{ return m_filter; }
sink_list& sinks ()
{ return m_sinks; }
void is_enabled (const entry_type& entry)
{ return m_filter (entry); }
void write (const entry_type& entry)
{
if (!m_sinks.empty() && m_filter(entry))
{
for (typename sink_list::iterator i = m_sinks.begin (),
e = m_sinks.end(); i != e; ++i)
(*i) (entry);
}
}
private:
filter_type m_filter;
std::list<sink_type> m_sinks;
};
} // namespace logging
#endif // macro guard
#ifndef __CAE_TUPLE_LOG_H
#define __CAE_TUPLE_LOG_H
#include <set>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/tokenizer.hpp>
#include "log.h"
namespace tuple_log {
// The proposed BOOST_ENUM might be a nice replacement for this class:
struct level {
enum value_type {
trace,
debug,
info,
warning,
error,
fatal,
default_value = info
} value;
level () : value (default_value) {}
level (value_type l) : value (l) {}
/// Get the name of this level
const char* name () const { return name (value); }
/// Convert a level to a string representation
static const char* name (value_type val) {
const char* str = "";
switch (val)
{
case trace : str = "trace"; break;
case debug : str = "debug"; break;
case info : str = "info"; break;
case warning : str = "warning"; break;
case error : str = "error"; break;
case fatal : str = "fatal"; break;
default : break;
}
return str;
}
};
// Stream formatting for a level object
std::ostream& operator<< (std::ostream& os, const level& l)
{
return os << l.name ();
}
// The proposed BOOST_ENUM might be a nice replacement for this class:
struct category {
enum value_type {
program_flow = 0x01,
function_arguments = 0x02,
return_value = 0x04,
data_flow = 0x08,
default_value = program_flow,
all_categories = 0xffff
} value;
category () : value (default_value) {}
category (value_type t) : value (t) {}
std::string name () const { return name (value); }
static std::string name (value_type t) {
std::string str;
if (t & program_flow)
str.append ("program_flow|");
if (t & function_arguments)
str.append ("function_arguments|");
if (t & return_value)
str.append ("return_value|");
if (t & data_flow)
str.append ("data_flow|");
// Remove trailing pipe
if (!str.empty())
str.erase (str.size() - 1);
if (str.empty())
str = "(null)";
return str;
}
};
std::ostream&
operator<< (std::ostream& os, const category& c)
{
return os << c.name();
}
/// Specific implementation of Entry concept containing level,
/// category, keyword and message
typedef boost::tuple<level, category, std::string, std::string> entry;
/// Simple filter that implements level, category and keyword
/// filtering. Will match entry tuples where the level is >=
/// m_level, the category matches the m_category bitmask, and the
/// keyword is either in the include list or not in the exclude list.
class filter : public std::unary_function<entry, bool> {
public:
filter () {}
filter (level l, category c, const std::string& labels)
: m_level (l),
m_category (c)
{
configure (l, c, labels);
}
/// Configure level, category and keyword lists
void configure (level l, category c, const std::string& labels)
{
m_level = l;
m_category = c;
configure (labels);
}
/// Configure the include/exclude keyword list from a pipe or
/// comma delimited list of tokens. If a token begins with "!"
/// it is added to the exclude list (after removing the "!"
/// prefix), else it is added to the include list.
void configure (const std::string& labels)
{
m_include.clear ();
m_exclude.clear ();
boost::char_separator<char> sep ("|,");
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
tokenizer tokens (labels, sep);
for (tokenizer::iterator i = tokens.begin(),
e = tokens.end(); i != e; ++i)
{
if (i->empty())
continue;
// Token starting with '!' is added to the exclude list
if (*(i->begin()) == '!' && i->length() > 1)
m_exclude.insert (i->substr (1));
else
m_include.insert (*i);
}
}
/// Returns true if the Entry matches (see class description),
/// else false
result_type operator() (const argument_type& arg) const {
// If level must be >= m_level
if (arg.get<0>().value < m_level.value)
return false;
// Category must match m_category mask
else if (!(arg.get<1>().value & m_category.value))
return false;
const std::string& label = arg.get<2>();
if (label.empty()) // Can we filter on an empty label?
return true;
// If we have an include list, the label must match
else if (!m_include.empty() &&
m_include.find (label) == m_include.end())
return false;
// If we have an exclude list, the label must NOT match
else if (!m_exclude.empty() &&
m_exclude.find (label) != m_exclude.end())
return false;
return true;
}
private:
level m_level;
category m_category;
typedef std::set<std::string> keyword_set;
keyword_set m_include;
keyword_set m_exclude;
};
typedef logging::basic_file_sink<entry> file_sink;
typedef logging::basic_ostream_sink<entry> ostream_sink;
typedef logging::basic_logger<entry> logger;
} // namespace tuple_log
#endif // macro guard
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost |
|
|