
Dear boosters, I know a while ago there was some interest in a logging library. And of course, there are so many ways you can do logging, that it was somehow agreed that a unique logging library can't address them. However ;), recently I discovered a very easy way to do logging. Gradually, I've come to this class (shown at the end). Using it is extremely easy, and it totally decouples what you write from where it's written. The idea is to have a function that takes a const std::string& parameter, and it will be called whenever a new message is logged. If you want thread-safety, it's just too simple... Just implement it within your function. Example: // log.h logstream errors_log(); logstream activity_log(); // log.cpp void do_log( const std::string & msg) { // ... thread-safe writing to cout scoped_lock lock(some_static_mutex); std::cout << msg; std::cout.flush(); } logstream errors_log() { return logstream(&do_log) << "[ERROR] "; } logstream activity_log() { return logstream(&do_log) << "[activity] "; } // in code #include "log.h" activity_log() << "application started (" << ver.major_ver << "." << ver.minor_ver << ")" << std::endl; errors_log() << "too many users" << users << std::endl; // etc. Also, I have an idea of how to enable/disable (parts of) logging at runtime. Same for very easily enabling/disabling logging of different modules. Any interest in such a library? Best, John #ifndef LOG_LOGSTREAM_HPP #define LOG_LOGSTREAM_HPP #pragma once // Copyright (c) John Torjo - 2004 #include <iosfwd> #include <sstream> namespace log { // allow finding the prototype for a log function template<class char_type, class traits = ::std::char_traits<char_type>, class aloc = ::std::allocator<char_type> > struct log_func { typedef void (*type)(const std::basic_string<char_type,traits,aloc> &); }; // allow writing to a log template<class char_type, class traits = ::std::char_traits<char_type>, class aloc = ::std::allocator<char_type> > struct basic_logstream { typedef typename log_func<char_type,traits,aloc>::type log_func_type; typedef ::std::basic_ostringstream<char_type,traits,aloc> ostring_stream_type; explicit basic_logstream( log_func_type func) : m_func(func) {} basic_logstream( basic_logstream & other) : m_func(other.m_func) { m_buffer << other.m_buffer.str(); other.m_func = 0; } ~basic_logstream() { if ( m_func) m_func( m_buffer.str() ); } template< class T> basic_logstream & operator<<( const T & val) { m_buffer << val; return *this; } typedef std::ostream & (*ostream_func)(std::ostream & ); basic_logstream & operator<<( ostream_func f) { f( m_buffer); return *this; } private: ostring_stream_type m_buffer; log_func_type m_func; }; typedef basic_logstream<char> logstream; typedef basic_logstream<wchar_t> wlogstream; } #endif

Hi John,
I know a while ago there was some interest in a logging library... However ;), recently I discovered a very easy way to do logging.... The idea is to have a function that takes a const std::string& parameter, and it will be called whenever a new message is logged...
Well, to implement extra-simple logging I always can do: function<void (const std::string&)> error = cout << constant("error: ") << _1 << "\n"; function<void (const std::string&)> warning = cout << constant("warning: ") << _1 << "\n"; int main() { warning("something's wrong here"); error("nothing works"); return 0; } The complexity, though, is in processing which happens after the message is send to logger -- checking when message should be printed, adding timestamps, writing to files/syslog, etc. What would be really good is have the logging library be a set of components linked with boost::function or boost::signal. What I mean? Take a look at LogJ4: http://logging.apache.org/log4j/docs/api/index.html There's Appender, Filter, Layout and a big number of other classes which all know about each other and the whole system looks complicated. With boost::signals, the system can be structured as a number of small classes, each of which has operator() and can emit a signal. The the processing will work like this: 1. User gets hold of boost::logger and outputs something 2. logger emits signal which can be then connected to a number of consumer 3. First consumer might be 'filter' which determines if the message should be output at all. If it should be output, it emits signal again, which might be delivered further 4. 'filter' might be connected to 'formatter', which adds timestamp and again emits the signal. 5. 'formatter' might be connected to 'stream_writer' which stores the message to a file and does nothing more. So, I think it's good to have boost::logger just call a function/signal. - Volodya

Well, to implement extra-simple logging I always can do:
[...]
we can always do that ;) I chose to use the popular '<<' syntax.
The complexity, though, is in processing which happens after the message is send to logger -- checking when message should be printed, adding timestamps, writing to files/syslog, etc.
True. I looked at log4j (for the nth time ;)) I don't like it. It's way too complex. There's no need for Filter, Layout and a lot of other classes (IMHO). What I want is to create a simple correspondence from a log_id (a level) into a log function. Having that, you can always say something like: LOG(activity) << whatever... << std::endl; if 'activity' is turned off this nothing gets evaluated (yes, LOG is a macro ;)) If it's turned on, the whole message is composed and the function corresponding to 'activity' level is called. What I assume is that in the end the whole output will end up in one or two files. Thread-safety and prefixing each message with timestamp is easy - I've done it in about 40 lines of code.
[...] 1. User gets hold of boost::logger and outputs something 2. logger emits signal which can be then connected to a number of consumer 3. First consumer might be 'filter' which determines if the message should be output at all. If it should be output, it emits signal again, which might be delivered further 4. 'filter' might be connected to 'formatter', which adds timestamp and again emits the signal. 5. 'formatter' might be connected to 'stream_writer' which stores the message to a file and does nothing more.
I think the above is too complicated. Here's how I see it: 1. Given a log level, the user gets hold of the corresponding boost::logger and tries to output something. 2a. If that log is disabled, nothing else happens. 2b. If that log is enabled, the message to be outputted is written to a stringstream, then converted to a string.. The function corresponding to its level is called. That's it! If you want formatter (your step 5.) or prefix (your step 4.), you can simply do them in your custom function. The log library will contain classes that allow you to manipulate such things. For instance: void do_log(const std::string & msg) { // thread-safe logging on a dedicate thread. Writing each 100 ms static ts_logger func( "my_log_file", 100); func(msg); } As I see it, the it's really easy ;) Best, John

"John Torjo" <john.lists@torjo.com> wrote
The complexity, though, is in processing which happens after the message is send to logger -- checking when message should be printed, adding timestamps, writing to files/syslog, etc.
True. I looked at log4j (for the nth time ;)) I don't like it. It's way too complex.
There's no need for Filter, Layout and a lot of other classes (IMHO).
What I want is to create a simple correspondence from a log_id (a level) into a log function.
Maybe it is possible to design a library which has (by default) this simple interface but allows very fine customization. /Pavel

Pavel Vozenilek wrote:
"John Torjo" <john.lists@torjo.com> wrote
The complexity, though, is in processing which happens after the message
is
send to logger -- checking when message should be printed, adding timestamps, writing to files/syslog, etc.
True. I looked at log4j (for the nth time ;)) I don't like it. It's way too complex.
There's no need for Filter, Layout and a lot of other classes (IMHO).
What I want is to create a simple correspondence from a log_id (a level) into a log function.
Maybe it is possible to design a library which has (by default) this simple interface but allows very fine customization.
That's exactly what I want to do :) Since there seems to be some interest, I'll start working on it in the very near future. Best, John

Simon J. Julier wrote:
Since there seems to be some interest, I'll start working on it in the very near future.
Most definitely! I'm be very interested to see the outcome of this. I'm also willing to lend a hand, if that would be useful.
Thanks. I'll need people willing to test it pretty soon ;) Also, I don't forsee a lot of code going into this library. If that is not true, I'll count on you ;) Best, John
Cheers,
Simon
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

From: John Torjo <john.lists@torjo.com>
What I want is to create a simple correspondence from a log_id (a level) into a log function. Having that, you can always say something like: LOG(activity) << whatever... << std::endl;
if 'activity' is turned off this nothing gets evaluated (yes, LOG is a macro ;))
But each invocation of operator <<() must be evaluated, if only to learn that the ostream is disabled, right? That could amount to a lot of overhead or clients would need to write custom functions that return a string from a set of arguments to reduce the number of operator <<() invocations. Otherwise, it sounds useful. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Rob Stewart wrote:
From: John Torjo <john.lists@torjo.com>
What I want is to create a simple correspondence from a log_id (a level) into a log function. Having that, you can always say something like: LOG(activity) << whatever... << std::endl;
if 'activity' is turned off this nothing gets evaluated (yes, LOG is a macro ;))
But each invocation of operator <<() must be evaluated, if only to learn that the ostream is disabled, right? That could amount to a lot of overhead or clients would need to write custom functions that return a string from a set of arguments to reduce the number of operator <<() invocations.
Otherwise, it sounds useful.
Not if the LOG macro is something like: #define LOG(x) if(!is_log_enabled(x)) ; else x Then, LOG(x) << "me " << me << " and you " << you << std::endl; Writing will be evaluated only if is_log_enabled returns true. Best, John

John Torjo wrote:
True. I looked at log4j (for the nth time ;)) I don't like it. It's way too complex.
There's no need for Filter, Layout and a lot of other classes (IMHO).
What I want is to create a simple correspondence from a log_id (a level) into a log function. Having that, you can always say something like: LOG(activity) << whatever... << std::endl;
if 'activity' is turned off this nothing gets evaluated (yes, LOG is a macro ;)) If it's turned on, the whole message is composed and the function corresponding to 'activity' level is called.
That's OK.
[...] 1. User gets hold of boost::logger and outputs something 2. logger emits signal which can be then connected to a number of consumer 3. First consumer might be 'filter' which determines if the message should be output at all. If it should be output, it emits signal again, which might be delivered further 4. 'filter' might be connected to 'formatter', which adds timestamp and again emits the signal. 5. 'formatter' might be connected to 'stream_writer' which stores the message to a file and does nothing more.
I think the above is too complicated.
Is it? The basic idea is (1) to have functional object to do the logging (2) allow to form chain of responsibility pattern from individual objects to make the system for flexible.
Here's how I see it: 1. Given a log level, the user gets hold of the corresponding boost::logger and tries to output something.
I think that hierarchical loggers ala log4j are necessary.
2a. If that log is disabled, nothing else happens. 2b. If that log is enabled, the message to be outputted is written to a stringstream, then converted to a string.. The function corresponding to its level is called. That's it!
What I propose is that this function can forward the request (after some processing) to other functions. In other words, you focus on front-end interface while I talk about inner workings.
If you want formatter (your step 5.) or prefix (your step 4.), you can simply do them in your custom function.
It simpler (IMO) to write syslog_appender sl; file_appender fl; timestamp_adder t; t.message.connect(sl); t.message.connect(fl); your_logger.message.connect(timestamp); that to write a custom function do to the same. Further, with a custom function you cannot customize the processing chain from config file. Besides, I think configuring loggers from config file is a very good thing, too. - Volodya

Is it? The basic idea is (1) to have functional object to do the logging (2) allow to form chain of responsibility pattern from individual objects to make the system for flexible.
this can be implemented within the log function, if it's really necessary. I don't see why it would need to be too formal.
Here's how I see it: 1. Given a log level, the user gets hold of the corresponding boost::logger and tries to output something.
I think that hierarchical loggers ala log4j are necessary.
Yes, they are, and I will certainly implement them. Each type of log will have a unique internal ID, by which you use it within your program (example: LOG(activity) << "blabla") and a string associated with it (example: "app.activity"). Based on this hierarchy the message will be forwarded to a certain function. Also, enabling/disabling of logs can happen like this: logs().disable( "*.warn.*"); // disable all warning messages
2a. If that log is disabled, nothing else happens. 2b. If that log is enabled, the message to be outputted is written to a stringstream, then converted to a string.. The function corresponding to its level is called. That's it!
What I propose is that this function can forward the request (after some processing) to other functions. In other words, you focus on front-end interface while I talk about inner workings.
yes
If you want formatter (your step 5.) or prefix (your step 4.), you can simply do them in your custom function.
It simpler (IMO) to write
syslog_appender sl; file_appender fl; timestamp_adder t; t.message.connect(sl); t.message.connect(fl); your_logger.message.connect(timestamp);
haven't used boost::signals before, but I guess I know what you mean. I'll definitely think about it...
that to write a custom function do to the same. Further, with a custom function you cannot customize the processing chain from config file.
you can do basically anything with a custom function (if you wish:D) As with the config file, if anyone wants to specify processing chain in a config file, they will implement it on top of my log library. I don't intend to provide working with a config file from the log library - that would be too much coupling.
Besides, I think configuring loggers from config file is a very good thing, too.
Yes, me too (mostly enabling/disabling). But, you should use another library to read from the config file (for instance: Persisting Settings Library (http://builder.com.com/5100-6370-5157525.html?tag=sc)), and then specify the logging options. Best, John
- Volodya
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

John Torjo wrote:
I think that hierarchical loggers ala log4j are necessary.
Yes, they are, and I will certainly implement them. Each type of log will have a unique internal ID, by which you use it within your program (example: LOG(activity) << "blabla")
What's "activity"? Is that variable?
and a string associated with it (example: "app.activity").
Based on this hierarchy the message will be forwarded to a certain function. Also, enabling/disabling of logs can happen like this: logs().disable( "*.warn.*"); // disable all warning messages
Ok, that's fine.
that to write a custom function do to the same. Further, with a custom function you cannot customize the processing chain from config file.
you can do basically anything with a custom function (if you wish:D) As with the config file, if anyone wants to specify processing chain in a config file, they will implement it on top of my log library. I don't intend to provide working with a config file from the log library - that would be too much coupling.
I meant something different. Customizing things from config file is much easier if you have fine-grained building blocks -- for adding timestamp, for some filtering, for adding to file. If library does not provide such building blocks, it's usability will be very limited.
Besides, I think configuring loggers from config file is a very good thing, too.
Yes, me too (mostly enabling/disabling). But, you should use another library to read from the config file (for instance: Persisting Settings Library (http://builder.com.com/5100-6370-5157525.html?tag=sc)), and then specify the logging options.
Well, I can use the program_options library for that ;-) - Volodya

Yes, they are, and I will certainly implement them. Each type of log will have a unique internal ID, by which you use it within your program (example: LOG(activity) << "blabla")
What's "activity"? Is that variable?
a unique ID (variable). Something like: boost::logtl::unique_id activity_logid; and #define LOG(x) if (!is_enabled_log(x##_logid)) ; else x or something like it ;)
that to write a custom function do to the same. Further, with a custom function you cannot customize the processing chain from config file.
you can do basically anything with a custom function (if you wish:D) As with the config file, if anyone wants to specify processing chain in a config file, they will implement it on top of my log library. I don't intend to provide working with a config file from the log library - that would be too much coupling.
I meant something different. Customizing things from config file is much easier if you have fine-grained building blocks -- for adding timestamp, for some filtering, for adding to file. If library does not provide such building blocks, it's usability will be very limited.
of course, it'll be easy to add filtering/prefixing (timestamp,etc.).
Besides, I think configuring loggers from config file is a very good thing, too.
Yes, me too (mostly enabling/disabling). But, you should use another library to read from the config file (for instance: Persisting Settings Library (http://builder.com.com/5100-6370-5157525.html?tag=sc)), and then specify the logging options.
Well, I can use the program_options library for that ;-)
Got that right :D Best, John

Hello John I found logstream implementation very useful. Could I use it? Regards Janusz

Janusz Piwowarski wrote:
Hello John
I found logstream implementation very useful. Could I use it?
Certainly! Looking forward to your feedback ;) Best, John
Regards Janusz
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

It's needed copy contructor for temporaries:
Right on :D Thanks for pointing it out. I used it in a different manner before, and there was no need for it.
Second, gcc doesn't accept "log" for namespace name.
Got it. Here's the new version: #include <iosfwd> #include <sstream> namespace boost { namespace logtl /* note: gcc does not allow 'log' as a namespace */{ // allow finding the prototype for a log function template<class char_type, class traits = ::std::char_traits<char_type>, class aloc = ::std::allocator<char_type> > struct log_func { typedef void (*type)(const std::basic_string<char_type,traits,aloc> &); }; // allow writing to a log template<class char_type, class traits = ::std::char_traits<char_type>, class aloc = ::std::allocator<char_type> > struct basic_logstream { private: typedef typename log_func<char_type,traits,aloc>::type log_func_type; typedef ::std::basic_ostringstream<char_type,traits,aloc> ostring_stream_type; typedef ::std::basic_ostream<char_type,traits> ostream_type; public: explicit basic_logstream( log_func_type func) : m_func(func) {} basic_logstream( const basic_logstream & other) : m_func(other.m_func) { m_buffer << other.m_buffer.str(); other.m_func = 0; } ~basic_logstream() { if ( m_func) m_func( m_buffer.str() ); } template< class T> basic_logstream & operator<<( const T & val) { m_buffer << val; return *this; } typedef std::ostream & (*ostream_func)(std::ostream & ); basic_logstream & operator<<( ostream_func f) { f( m_buffer); return *this; } // in case the underlying stream is needed... ostream_type & ostream() { return m_buffer; } private: ostring_stream_type m_buffer; mutable log_func_type m_func; }; typedef basic_logstream<char> logstream; typedef basic_logstream<wchar_t> wlogstream; }} Best, John

On 4/15/04 6:26 AM, "John Torjo" <john.lists@torjo.com> wrote: [SNIP]
// allow writing to a log template<class char_type, class traits = ::std::char_traits<char_type>, class aloc = ::std::allocator<char_type> > struct basic_logstream { [SNIP] typedef std::ostream & (*ostream_func)(std::ostream & ); basic_logstream & operator<<( ostream_func f) { f( m_buffer); return *this; } [TRUNCATE]
Shouldn't "ostream_func" match the character and traits types? -- Daryle Walker Mac, Internet, and Video Game Junkie darylew AT hotmail DOT com

[SNIP]
typedef std::ostream & (*ostream_func)(std::ostream & ); basic_logstream & operator<<( ostream_func f) { f( m_buffer); return *this; }
[TRUNCATE]
Shouldn't "ostream_func" match the character and traits types?
yup, my mistake. thanks for pointing it out! Best, John
participants (7)
-
Daryle Walker
-
Janusz Piwowarski
-
John Torjo
-
Pavel Vozenilek
-
Rob Stewart
-
Simon J. Julier
-
Vladimir Prus