thoughts on logging library

Hi, I saw that the previously proposed logging library went into the review queue again, so I thought of pointing out some remarks prior the actual review. First of all, I think that a boost logging library should come up with one, and one only, way of expressing a log line. BOOST_LOG(some, kind, of, args); Now, how should the interface of BOOST_LOG(...) look like? To find that out, I'll start by explaining how I would like boost log to behave from a users point of view, for instance using boost.Asio. #define BOOST_ASIO_LOG_ENABLE #define BOOST_ASIO_LOG_MIN_LEVEL boost::log::level::debug #include <boost/asio/asio.hpp> // Insert lots of broken client code here... =) What I want to do is configure the amount of logging code that gets generated inside asio, not if the logging functionality is enabled at runtime or so. Without BOOST_ASIO_LOG_ENABLE there should be no logging code, from asio, in the executable.. I could use side by side another config for another boost library #define BOOST_SERIALIZATION_LOG_ENABLE #define BOOST_SERIALIZATION_LOG_MIN_LEVEL boost::log::level::error // only care about error logs to avoid spamming, for instance.. #include <boost/serialization.hpp> So, I've only informed the libraries that I want log to be generated, not anything about where it'll end up, or what kind of information it'll contain. The author of Asio also doesn't know what kind of information that will be generated either, but can, and should, only provide what is available from the library's point of view. // somewhere in asio code BOOST_ASIO_LOG(log::level::debug, "shutting down service.."); or, another way BOOST_ASIO_LOG_DEBUG(asio::g_log, "closing socket " << native_handle_id_); something like this is what I'm used to, but it's just for discussion. in the million ++ lines of code we have in our project we configure logs depending on system and level. That's the input required to make a log statement, the rest are details filled in by the log system itself, but it's not very interesting. a log message could automatically create the following in default config. struct msg { const char* system; //i.e. "asio" const char* source_file; const char* source_line; const char* function_name; std::string user_message; }; or it could be anything else that boosters think is portable and makes sense. My point is that libraries in boost should not deal with this, they must only learn the interface and some configuration macros of boost log (for handling the compile time enable/disable of loggin macros), and then provide the log system with the information -they have-, the rest is config details (such as log sinks and formatters). Configuring the above with a compiled library like boost serialization is an interesting task as well, that I definitely think should be addressed. could be that the flexibility decreases some in such a scenario, or it could be configued in site-config.jam or whatever. Is this along the line what boost.log has in mind, or will it be a logging library not to be used by the boost libraries themselves? Regards, Christian

Although I'm not the author of the library in the review queue, I'm working on my own library and would like to share some thoughts on this. Christian Holmquist wrote:
Agreed, the simpler the interface is, the easier to learn the lib.
[snip]
Although it looks very tempting, I have two concerns about it: 1. Such configuration macros tend to introduce ODR violations. This problem probably won't get in the way in scope of a single executable, but what if the application consists of a dozen of them? It gets worse in case if the Boost library is compiled into binary itself. 2. Recursive calls can become a real issue. Assume that I want to send logs by network using a sink based on Asio. Trying to log from Asio would be impossible. The only solution I imagine right now is to detect such recursive calls in the logging library and filter away logging records from inside processing of other logging records. IMHO, the first issue is not very special for logging. There are quite a lot of various configuration macros of different Boost libraries and yet there are no common means to define them _both_ when building the Boost itself and user-side applications except to manually add them to every compiler command line. I think a common user configuration header file is needed to address this problem. [snip]
Is this along the line what boost.log has in mind, or will it be a logging library not to be used by the boost libraries themselves?
I think, making Boost libraries able to write logs is definitely worth thinking of, although it may not be the top priority feature. IMO, we should first provide users with ability to easily write logs. We can add logging to boost libraries at a later stage.

On 08/04/2008, Andrey Semashev <andysem@mail.ru> wrote:
I don't think there should be any ODR violations with header only libraries? Each executable (we're talking shared objects/libraries here, right?) may use boost libraries independently with different configs, as long as additional data members are not introduced to allow logging. The logging facility should IMO be a shared global entry for the executable to avoid such problems. I think it'd be a reasonable requirement that library A-with-log.dll/so is binary compatible with A-without-log.dll/so. As I wrote in my review a logging mechanism becomes a part of a library's public interface one way or another, so totally ignoring how that interaction behaves across library/executable borders seems like giving most of the burden to the end user.
Agreed.
If boost doesn't use its own logging library because that library doesn't address the more difficult tasks, why should it match the any other usage? IMO one could build sufficiently with boost signals, function and possibly bind... (simple code just tested with msvc 8..) #include <boost/signal.hpp> #include <boost/function/function0.hpp> #include <boost/function/function1.hpp> #include <sstream> #include <string> namespace mlog { struct message { const char* system; const long level; const char* source; const char* function; const long line; const boost::function0<const char*> msg; }; typedef boost::function1<bool, const message&> filter; struct level_filter { long level; level_filter(long lev) { level = lev; } bool operator()(const message& msg) const { return level <= msg.level; } }; boost::signal<void(const message&)> log; volatile bool enable = false; } #define LOG(sys, lev, user_msg) \ if(mlog::enable) \ { \ struct \ { \ const char* operator()() \ { \ if(msg_.empty()) \ { \ std::ostringstream os; \ os << user_msg; \ msg_ = os.str(); \ } \ return msg_.c_str(); \ } \ std::string msg_; \ } writer; \ mlog::message m = {sys ? sys : "", lev, __FILE__, __FUNCTION__, __LINE__, writer}; \ mlog::log(m); \ } #include <iostream> void log_to_cout(const mlog::filter& f, const mlog::message& msg) { if(f(msg)) { std::cout << msg.system <<"::" << msg.function << " " << msg.source << " : " << msg.line <<" -> " << msg.msg() << std::endl; } } #define MY_LOG(lev, msg) LOG("mysys", lev, msg) #include <boost/bind.hpp> int main() { mlog::log.connect(boost::bind(&log_to_cout, mlog::level_filter(2), _1)); mlog::enable = true; MY_LOG(3, "hello " << "world"); MY_LOG(1, "no hello " << "world"); return 0; }

Christian Holmquist wrote:
No, that is not correct. There are platforms (at least Linux is the one I know of) where ODR is required across module boundaries. If you have one definition of function foo in module A and another definition in module B, it is generally not defined which foo you'll end up using in executable C which links against A and B _and_ in both modules A and B. The point is, all the three modules will be using the same (one of the two) definition of foo. There are means to change this behavior but it either means changing foo definition in a non-portable way or changing compiler settings, which will influence other things in a bad way. I would really hate to resort to solutions like this.
Not necessarily. I'd opt for both global and object-like logging entry.
I'm afraid, this will only be possible if the library interface (everything in headers, that is) does not depend on logging in any way. And that makes these configuration macros in user code or compiler settings useless. The library would have to compile either with or without logging support.
In most cases modularity does not introduce problems with logging. And the problems that can be introduced (such as ODR violations or loading/unloading modules in run time) cannot be solved in scope of the logging library itself.
[snip] Such code doesn't solve the problems I've pointed out. Although it solves the simple case, it won't provide things like flexible filtering and attributes. You'll have problems with log initialization and multi-module applications. Thread safety is another issue. If you try to develop this solution to a product state, you'll end up with a full-fledged logging library, pretty similar to the many out there. :) IMHO, whether boost uses its own library or not is irrelevant to the library usability for end users.

[snip]
Well, given the 5 minutes it took to write it, of course.
Log initialization - user responsibility Thread safety - user responsibility Multi-module issues - user responsbility Log code generation config - user / library author responsibility I still don't see why it's up to the end user to deal with all this..
logging library', maybe I misunderstood the intentions of that, but I thought it meant to support logging from inside boost. Anyways, if it's not possible to draw any general conclusions on how library code/config for log support should look like, I'll to live with that :)

Christian Holmquist wrote:
It depends. The library may provide utilities to ease this or even reduce to a single function call in some cases. For example, tools for logging initialization from a settings file can be provided out-of-box. Yes, your particular needs can deviate significantly from what the library provides. And strictly speaking, I agree to the opinion that such tools are out of scope of the logging library. But providing it would be a good gesture of the library author and can serve as a starting point for either a specialized project or your particular initialization code.
Thread safety - user responsibility
Why? Unless you use the library in a not thread-safe way the library should work well in multithreading. Moreover, IMO it should be as much scalable as possible in order not to limit user application scalability. The library can provide out-of-box various threading-related features, like asynchronous logging and thread-specific attributes.
Multi-module issues - user responsbility
Yep, some corner cases just can't be handled in the logging library itself. At least, I don't see how.
Log code generation config - user / library author responsibility
You don't want it to configure itself or add logging to your app just by including a couple of headers, do you? :) Of course the logging library should provide some reasonable configuration defaults, but configuring it to fit for your own needs is yours only task. As for logging from inside of Boost, as I said, it can be added later. I for one, have never had such need in my practice, and logging on my (user application) side was quite sufficient. I'm not saying that it is not needed, I'm saying it doesn't worth delaying the library release for a yet another n years.

Hi Christian, ----- Original Message ----- From: "Christian Holmquist" <c.holmquist@gmail.com> To: <boost@lists.boost.org> Sent: Monday, April 07, 2008 11:46 PM Subject: [boost] thoughts on logging library
I'm not sure it is a good idea to place these macros in the source code files. First you can define the macros on several header files, needing some test undef and so one. Which will be the result when two source files (.cpp) defines different configurations? IMO the macros you purpose should be defined at the build level of the executable. I really think that we need runtime configuration and I suspect that this configuration should be centralized. But I don't think that this is a particular case of the logging library. logging and configuration are orthogonal. A runtime configuration library will be welcome and should be able to configure the asio log level as well as other configurable features as for example the boost::log::level of a global log. Let me show how I will use the current Boost.Log library interface if I were the maintainer of asio library. I will define un internal macro like that: #ifdef BOOST_ASIO_LOG_ENABLE #define BOOST_ASIO_LOG(LEVEL, MSG) \ if (boost::asio::log_level <= LEVEL) { \ BOOST_LOG(LEVEL) << "[asio] " << MSG \ } #else #define BOOST_ASIO_LOG(LEVEL, MSG) #endif I will place the definition of the boost::asio::log_level variable in a specific header file, which should be included by the user in only one comilation unit. // boost/asio/log_level_def.hpp #ifndef BOOST_ASIO_LOG_LEVEL_DEF__HPP #define BOOST_ASIO_LOG_LEVEL_DEF__HPP #ifndef BOOST_ASIO_LOG_LEVEL_DEFAULT #define BOOST_ASIO_LOG_LEVEL_DEFAULT boos::log::level::debug #endif #include <boost/asio/log_level_fwd.hpp> namespace boos { namespace asio { atomic_log_level_type log_level=BOOST_ASIO_LOG_LEVEL_DEFAULT; } } #endif As the boost::asio::log_level reading and writing could be done by different threads the read and write operations must be atomics. So atomic_log_level_type must be convertible to boost::log::level and must is assignable from boost::log::level atomically. typedef atomic<boost::log::level> atomic_log_level_type ; An other history is how can we configure the boost::asio::log_level variable at runtime? Well this can be done in a lot of ways. But at the end what you realy need is to do a the following boost::asio::log_level = new_value; Is for this raison that the atomic_log_level_type assignation operator must be atomic. We can use also a configurator class which store a mapping between some key and a reference of such variables. rtc::runtime_configurator<std::string, atomic_log_level_type&> conf; Note the mapped type is a reference to the real atomic_log_level_type. The advantage is that now the owner of the configured variable do not depends on how this variable is configured. It is up to the user to make the association between the configured variable and the configurator. This runtime_configurator could be based on the property_tree library. The user can add the configurable variables as follows conf.register("boost.asio.log_level", boost::asio::log_level); The runtime configuration library can provide a callable backdoor to configure any registered variable using cli, as for example $$ push boost $$ push asio $$ log_level = debug $$ log_level $$ pop $$ asio.log_level = debug $$ asio.log_level The push and pop command are used to change the context. the var = value command assign the value, and the variable command prints its value This backdoor will look up the corresponding configurable variable and read o write depending on the command. To allow symbolic values wee need manage with the conversion from std::string to boost::log::level and vice versa. We can use the lexical_cast library for this purpose as soon as the we have the output and input operators defined for boost::log::level. If the register is done at initialization time before the backdoor thread is lunched, the runtime_configurator instance will be used only for reads, so no thread safety problem at the map or tree_map level. Then the user can start the backdoor as follows boost::thread configurator_backdoor(rtc::backdoor) The user can provide its own external configuration means with a graphical interface if he consider convenient. Do you think that a such runtime configurator variables library has a place in Boost? This could be a complement to a larger configuration library starting from program_options, extending to property_tree storage and making the CLI a specific parser, other parsers can be reading from some configuration file formats, the backdoor I'm proposing or other as has been already proposed in this mailing list (CGI parameter parser). <snip>
Once the serialization library provide traces or log, if you want to use the serialization library with logs you should add a define at compile time e.g. -DBOOST_SERIALIZATION_LOG_ENABLE and link with the serialization logged variant e.g. -lboost_lserialization.
Is this along the line what boost.log has in mind, or will it be a logging library not to be used by the boost libraries themselves?
I think that this has nothing to be with the Boost.Log library. It is up to the other Boost libraries to use or not the loging library. The single flaw is that these libraries either must use a single global variable log or extend their interface with a log parameter. This problem has already been discused during the Boost.Singleton review.
Regards,
Christian
Do you think that this approach allows to configure the libraries using the log library? Best regards _____________________ Vicente Juan Botet Escriba

On 09/04/2008, vicente.botet <vicente.botet@wanadoo.fr> wrote:
Of course. It was only an example of the defines.
Sure, this could work, but I think asio::log_level should be a function returning the current level instead of a global variable. Then it's up to the user to choose where from this variable is read and how it's updated. There may be more things to consider, again something for a log library to decide best practice.
I haven't thought about it, but it sounds useful. It's probably an often reinvented wheel.
Agreed. Should the maintainer of serialization invent his own build/jam/config thingie to make this possible? I think not.
Sorry, I disagree, I think that it has everything to do with the log library. It should show how boost libraries should use the library to make them customizable for end users of boost.
Some boost libraries already use singletons. Religious views on globally accessible data aside, what would be the flaw with a single log instance?

Hello Christian, I think that I start to understand your concern. We need to see which is the minimal interface a log library must have in order to be be used by a library allowing user customizations. I dont know why a thought that this interface was already provided by the Boost.logging library, and maybe it was in his first version, but it is sure that it isnot the case for log2. From: "Christian Holmquist" <c.holmquist@gmail.com> To: <boost@lists.boost.org> Subject: Re: [boost] thoughts on logging library: orthogonal runtimeconfiguration
<snip>
I prefer :)
Why? Do you mean that you prefer if (asio::log_level()) ... and asio::log_level() = debug to if (asio::log_level) ... and asio::log_level = debug ? Why asio::log_level should not be an object?
I thougth that Boost was there exacltly for that. To avoid that every one reinvent the wheel. Am I wrong? Isn't the Boost.Log library a wheel reinvented by every body?
Please, could you be more precise? What do you propose instead?
As I said I think that I start to understand your concern. We need to see which is the minimal interface a log library must have in order to be be used by a library allowing user customizations. Once this minimal interface will be clear I think that if a library provides logs, it is up to the library autor to describe how to configure it. Isn't it? The role of a Boost.Configuration library will be to provide generic mechanisms to configure applications and of course libraries.
I have no particular problem, I'm not the maintainer of any Boost library :). I'm not so sure that there will be a lot of maintainers that like to use a external global variable, or even add to his library some logs. Are there some Boost maintaniers that plans to add log support for his library once the Boost.Library is accepted? Any way and independently of which library wil have logs, as you have stated from the begining, we need to define the minimal interface a logging library must provide. What are really these minimal features? * It is clear that the instantiation of the log and its configuration is reserved to the application. * Should work for example with the std::cerr stream. * As the minimal logging library do not provide configuration, the output for the default log will have a predefined format. * Logiging client libraries should be able to be configured Boost::logging logs and filters Comments? Best _____________________ Vicente Juan Botet Escriba
participants (3)
-
Andrey Semashev
-
Christian Holmquist
-
vicente.botet