|
Boost : |
Subject: [boost] [boost.log] Using stream manipulators to influence record formatting per sink
From: Jamie Allsop (ja11sop_at_[hidden])
Date: 2010-11-08 16:26:52
So I've been using the previously reviewed log library by Andrey
Semashev with some degree of success. One thing however has caused me a
great deal of trouble and I'm sure with a couple of changes would become
a lot easier.
Basically I have an application that outputs tabular data in the logs. I
have objects that know how to stream themselves either as an ascii table
or as an html table. I have a custom defined manipulator that can be set
for a stream that the objects can check the value of, and stream
themselves either as text or html as appropriate.
In using the log library I face two major challenges. The first is how
can I set the manipulator to the desired value so that the log record
will be aware of the manipulator while building its message? It is not
possible to do this by calling set_formatter() on the sink since the
this receives *an already composed* record.message() string.
My work-around for this is a lot more complex than I'd like it to be and
I'll detail it here in the hope that it provides some insight to Andrey
so that he might be able to offer a nicer alternative more in-keeping
with his library.
So starting at the beginning I have objects that implement streaming
something like this:
---- template< class CharT, class Traits> friend std::basic_ostream< CharT, Traits>& operator<<( std::basic_ostream<CharT, Traits>& Ostream, const my_class& Value ) { if( log::output_format::value_in( Ostream ) == log::output_format::text ) { write_text( Ostream, Value ); } else if( log::output_format::value_in( Ostream ) == log::output_format::html ) { write_html( Ostream, Value ); } return Ostream; } ---- where 'value_in()' simply retrieves the value from the stream. Now during logging a temporary stream object is created that must have this manipulator applied. What I'd like to do is be able to specify if I want this manipulator applied on a per-sink basis. In much the same way as I can define a specific formatter for a sink, or a specific filtering rule for a sink. Currently there is no way to do that (at least not without a *lot* of effort) so I'll address this later. Instead I'll consider how we can deal with this be setting this desire globally by using a global attribute. We could write something like this: ---- log::output_format::type OutputFormat = log::output_format::html; std::pair< boost::log::core::attribute_set_type::iterator, bool > OutputFormatAttribute = boost::log::core::get()->add_global_attribute ( "OutputFormat", boost::make_shared< boost::log::attributes::constant<log::output_format::type> >( OutputFormat ) ); ---- Later we can extract this value to decide if we need the manipulator applied to the stream that is used to compose the log record message. We can use a helper object that is aware of all the manipulators that are to be applied and have it the log record stream based on attributes that represent them. For example, given a class called 'manipulators' we can define this insertion operator as: ---- template< class CharT, class TraitsT > inline std::basic_ostream<CharT,TraitsT>& operator<< ( std::basic_ostream<CharT,TraitsT>& Ostream, const manipulators& Manipulators ) { Manipulators.apply( Ostream ); return Ostream; } ---- where apply could be implemented (in our case) as this: --- template< class CharT, class TraitsT > void apply( std::basic_ostream<CharT,TraitsT>& Ostream ) const { boost::log::basic_record<CharT> Record( RecordHandle_ ); apply_output_format( Ostream, Record ); } private: ---- template< class CharT, class TraitsT > void apply_output_format( std::basic_ostream<CharT,TraitsT>& Ostream, const boost::log::basic_record<CharT>& Record ) const { log::output_format::type OutputFormat = log::output_format::text; boost::log::extract<log::output_format::type> ( "OutputFormat", Record.attribute_values(), boost::lambda::var( OutputFormat ) = boost::lambda::_1 ); if( OutputFormat == log::output_format::text ) { Ostream << log::set_output_format( log::output_format::text ); } else if( OutputFormat == log::output_format::html ) { Ostream << log::set_output_format( log::output_format::html ); } } ---- This then requires that the insertion operator is called as part of the streaming for any log record. In order to achieve this I need to write my own logging macros that do essentially what the severity logging macros do already but with the addition of streaming a 'manipulator' object at the same time so that the manipulators I want added can be applied to the stream that is constructed during that process. Here is the key macro that I need to write which gets substituted in a chanin of macros: ---- #define MY_LOG_STREAM_WITH_PARAMS_INTERNAL(logger, rec_var, params_seq)\ for (::boost::log::record_handle rec_var = (logger).open_record((BOOST_PP_SEQ_ENUM(params_seq))).handle(); !!rec_var; rec_var.reset())\ ::boost::log::aux::make_pump_stream((logger), rec_var).stream() << log::manipulators(rec_var) ---- The important part is this: ---- log::manipulators(rec_var) ---- The stream object is created and immediately passed an instance log::manipulators passing the RecordHandle to the constructor. Finally then I can have manipulators passed down to the stream that is used to create the log record message, and in my case allow the logged objects to either stream themselves as text or as html. A downside of this is that this only works with global attributes. So either all log sinks would one format or the other. Given that it is generally desirable to treat different sinks differently (and often the reason they exist) it would be much nice if there was the concept of sink-level attributes. These could be added to the record's attribute set along with the source and global attributes. Currently there is no way (it seems) to associate any state with a sink or make any decision during logging that relates to a sink. We can set a custom formatter that applies to the log message created but that has no effect on the record message itself. (Decorators only seem to be for post-processing of the record message). We can also set a custom filter to determine whether a log record gets logged by a given sink. It seems odd then that we have no way to to be able to say (for example) that a given sink will have an attribute that indicates that all messages through that sink should be in a certain format (for those objects that support the formatting). From looking at the code this does not appear to be a major issue so hopefully it could be supported. It would also be highly desirable to be able to specify manipulators directly that could then be applied to the record message stream itself. Supporting this at the global and sink level should be possible. If we had sink-level attributes the technique I described could be used to simulate this, even though it is somewhat kludgey. Most directed to Andrey, but I'd welcome comments and suggestions from others familiar with the log library. Jamie
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk