Re: [boost] [Review] Boost.Logging: formal review

Some thoughts and ideas about some of the topics that has been discussed regarding the proposed Boost.Logging library. 1. Enabled/disabled logger, logging macros and efficiency The logging macro BOOST_LOG should accept two arguments instead of one. The logger and the log message. The syntax (1) BOOST_LOG(some_log) << "my message " << foo() << bar(); should be replaced with (2) BOOST_LOG(some_log, "my message " << foo() << bar()); and the macro being defined something like this (3) #define BOOST_LOG(logger, msg) \ if(logger.isEnabled()) { /* code that logs msg */ ... } Thus, disabled loggers will never take longer than simple if-statement, as long as the isEnabled() method is fast, which IMO should take no longer than comparing booleans. In the original solution (1) the method foo() will always be called, which is inefficient when the logger is disabled, in solution (2), foo() will never be called when the logger is disabled. Further more, the macro could insert extra information such as __LINE__, __FILE__ etc. If needed or wanted, the macro could of course be redefined to leave no trace of the logging. I could also envision a few other macros that for example creates a logger object on the stack that logs upon creation and destruction, a special macro to aid developers that are always compiled out of release code. Perhaps such macros is best left to the user of the library although I do think that there are common set of macros that is useful for most applications and situations and thus are good candidates for being defined in the library. 2. Filtering There has been some discussion about the ability of a library to be able to do more filtering than simple log levels, notably by Gennadiy Rozental. Gennadiy writes:
At the bare minimum it should support:
entry level - levels set is ordered set of values indication importance of information provided. Filtering is based on threshold. Examples: DEBUG, INFO, MAJOR
entry category - categories set is a set of unique values indicating kind of information provided Filtering is based on masking. Examples: ARGS, RETURN_VALUE, ERROR_LOG, DATA_FLOW, PROG_FLOW
entry keyword - keyword set is set of user defined keywords (most frequently strings) identifying area of the program. Filtering is based on match of keywords. Keywords usually are used to mark specific part of application.
I agree that it is very important for a logging library to support this. However, I do not think that the solution is for the logging library to be aware of such special values. The library should be as simplistic as possible, with no or few assumptions about its use, at least at the basic "framework level". Only then IMO could the library accodomate various neeeds. Let me explain. If special values are introduced in the library, there will always be applications and users that lack some other special value, perhaps specific to their domain, but even more likely, to their taste and style. My proposed solution is to basically let loggers be somewhat entry (Gennadiy's definition above) unaware. Instead, let the developer be entry aware. By that I mean that the developer should log messages that belong to a specific entry category/level to a specific logger. For example, if there is a need to log and filter a RETUN_VALUE category, then a specific logger, perhaphs named RETURN_VALUE_LOGGER (or whatever), and let the user send log messages to that logger. This could look something like this: BOOST_LOG( RETURN_VALUE_LOGGER, "The return value is: " << value ); This solution also means that you have a different logger for each level, level filtering perhaps being the most common filter. For example: BOOST_LOG( debug, "some debug output " << a << foo() ); BOOST_LOG( error, "Error: " << e.what() ); where 'debug' and 'error' are loggers. Now, the drawback with this approach is that several loggers needs to be defined and configured, but as long as this is simple this should be ok. There should also be default definitions and configurations available from the library, which "sits" on top of the basic library framework. As for entry keywords (see above), I see two interpretations of Gennadiy's idea. (1) A log statement is marked with a keyword. For example: BOOST_LOGX( logger, "user defined keyword", "my log msg " << value ); Or (2), logging is filtered on what strings are contained in the log message. That is, log statements are written in the normal way by the programmer but is filtered by some mechanism where the log message is matched against certain keywords, like the grep command. For interpretation (1): My proposed solution should solve the problem. Declare a specific logger that the programmer send log statements to for the specified keyword. For interpretation (2 ): IMO the filtering mechanism should either sit on top of loggers or possibly in the appenders, or the tools that are used to view logs (e.g. grep and such). The "on top" solution would be to use specicial functions that utilizes the "lower" layer of simple loggers. It should be an extension to the log library and not be part of the core functionality (but could still be provided by a logging library). For example: BOOST_LOGX( logger, Predicate( ... ), "my log msg" << value ); Thus, the approach to filtering, based on Gennadiy's idea of various criteria is to to enable/disable loggers depending on system/users/developers needs'. Hence, enabling/disabling must be simple. Using John Torjo's solution of manipulate_log-functions this ought be relative easy. The programmer should be able to enable/disable loggers on various critiera. One solution for this would be to attach user, as well as common/default library, supplied name-value pairs to loggers and let the manipulate_log functions enable/disable loggers that meet certain criteria for these name-value pairs. For example, when defining which loggers that are available to the programmer (by himself or some other developer), a log level name-value object is attached to the loggers. The logger named 'debug' is attached with a log level of 'debug' (a literal integer value chosen carefully e.g. 0). Later on in the program, the programmer can issue the statement, using the manipulate_log-functions to enable all loggers that have a property name log level with a value greater than 'debug' and so on. Additional benefits of using this clean approach (maybe possible with Johns solution as well (?)) to loggers is that it is quite possible to have debug loggers log in one subsystem while normal logging is done in the rest of the program. Any combination is possible. The major drawback with this clean approach to loggers as I see it is the additional complexity, cognitive burden, for setting up loggers. Part of this problem should be alleviated by the library by providing a default set of loggers for common/simple/I just wanna get started -situations. Cheers, Richard Glanmark The content of this e-mail is intended only for the confidential use of the person(s) to whom it is addressed. If the reader of this message is not such a person, you are hereby notified that you have received this communication in error and that reading it, copying it, or in any way disseminating its content to any other person, is strictly prohibited. If you have received this message in error, please notify the author by replying to the e-mail immediately. NeoNet AB and its subsidiaries (NeoNet Securities AB, NeoNet Securities Inc. and NeoNet Technology AB) are unable to exercise control over the content of information contained in transmissions made via the Internet and hereby excludes any warranty as to the quality or accuracy of any information contained in this message and any liability of any kind for the information contained in it, or for its transmission, reception, storage or use in any way whatsoever.

1. Enabled/disabled logger, logging macros and efficiency
The logging macro BOOST_LOG should accept two arguments instead of one. The logger and the log message. The syntax
(1) BOOST_LOG(some_log) << "my message " << foo() << bar();
should be replaced with
(2) BOOST_LOG(some_log, "my message " << foo() << bar());
and the macro being defined something like this
(3) #define BOOST_LOG(logger, msg) \ if(logger.isEnabled()) { /* code that logs msg */ ... }
Thus, disabled loggers will never take longer than simple if-statement, as long as the isEnabled() method is fast, which IMO should take no longer than comparing booleans.
1. I think this is also the case in original interface.
In the original solution (1) the method foo() will always be called, which is inefficient when the logger is disabled, in solution (2), foo() will never be called when the logger is disabled.
I think you are mistaken
Further more, the macro could insert extra information such as __LINE__, __FILE__ etc.
This is also true for original interface. All in all I prefer message not be inside brackets. It looks much more natural and I don't see any theoretical problens to get the same performance. But I also believe that macro shouldn't be a primary interface. So if you prefer message inside the breakets - it's your choice.
2. Filtering
There has been some discussion about the ability of a library to be able to do more filtering than simple log levels, notably by Gennadiy Rozental.
Gennadiy writes:
At the bare minimum it should support:
entry level - levels set is ordered set of values indication importance of information provided. Filtering is based on threshold. Examples: DEBUG, INFO, MAJOR
entry category - categories set is a set of unique values indicating kind of information provided Filtering is based on masking. Examples: ARGS, RETURN_VALUE, ERROR_LOG, DATA_FLOW, PROG_FLOW
entry keyword - keyword set is set of user defined keywords (most frequently strings) identifying area of the program. Filtering is based on match of keywords. Keywords usually are used to mark specific part of application.
I agree that it is very important for a logging library to support this. However, I do not think that the solution is for the logging library to be aware of such special values. The library should be as simplistic as
My position that library should be configurable by any class satisfying Filter concept. This particular filters could probably be supplied by the library as an example and as most widely useful.
My proposed solution is to basically let loggers be somewhat entry (Gennadiy's definition above) unaware. Instead, let the developer be
I do not see how it's possible. IMO framework should employ some MPL magic and construct proper entry structure based on set of Filters passed as template policy parameters.
entry aware. By that I mean that the developer should log messages that belong to a specific entry category/level to a specific logger. For
This is never be acceptable IMO. Even with 3 filters, each ahving 5 possible values you looking into 125 different loggers.
example, if there is a need to log and filter a RETUN_VALUE category, then a specific logger, perhaphs named RETURN_VALUE_LOGGER (or whatever), and let the user send log messages to that logger. This could look something like this:
BOOST_LOG( RETURN_VALUE_LOGGER, "The return value is: " << value );
This solution also means that you have a different logger for each level, level filtering perhaps being the most common filter. For example:
BOOST_LOG( debug, "some debug output " << a << foo() ); BOOST_LOG( error, "Error: " << e.what() );
What if I need to filter by RETURN_VALUE category and INFO level?
where 'debug' and 'error' are loggers. Now, the drawback with this approach is that several loggers needs to be defined and configured, but as long as this is simple this should be ok. There should also be default definitions and configurations available from the library, which "sits" on top of the basic library framework.
As for entry keywords (see above), I see two interpretations of Gennadiy's idea. (1) A log statement is marked with a keyword. For example:
BOOST_LOGX( logger, "user defined keyword", "my log msg " << value );
This is my interpretation. And I need keyword based filter to filter out entries that doesn't comply.
Or (2), logging is filtered on what strings are contained in the log message. That is, log statements are written in the normal way by the programmer but is filtered by some mechanism where the log message is matched against certain keywords, like the grep command.
Wow. That's novelty. You propose first format the message. Do all this hard work and then start costly substring searching? No, I wouldn't go this direction. Sorry. I don't have a time for the rest. But you should get my opinion from above. Regards, Gennadiy

Or (2), logging is filtered on what strings are contained in the log message. That is, log statements are written in the normal way by the programmer but is filtered by some mechanism where the log message is matched against certain keywords, like the grep command.
Wow. That's novelty. You propose first format the message. Do all this hard work and then start costly substring searching? No, I wouldn't go this direction.
In fact, that's quite neat. In fact, Easy LogView (a program that I wrote) allows for this. In other words, log as much as possible into a log, then have an external tool that allows you to filter exactly what you need. I like the idea and I've used the program quite a few times. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/surfaces.html - Sky's the limit! -- http://www.torjo.com/cb/ - Click, Build, Run!
participants (3)
-
Gennadiy Rozental
-
John Torjo
-
Richard Glanmark