Boost logo

Boost :

From: John Torjo (john.lists_at_[hidden])
Date: 2005-11-14 09:42:22

Gennadiy Rozental wrote:

> BTW before I start, I recalled couple more issues I forgot to mention in
> original post:
> 1. Design: Lightweight interface
> Log library brings a lot of dependencies. In some case I do not want this if

Care to exemplify? What are the dependencies?

> I need to use the library in header. IMO log needs to support/implement some
> lightweight debug abstract interface Something like:
> struct DebugLog {
> virtual void log( string const& ) = 0;
> virtual void log( int ) = 0;
> };
> struct Log : DebugLog { ... };
> Now I could use DebugLog interface without need to know an implementation.
> It comes with the price of virtual call. I primarily use this for debug only
> logging.

Anybody else wants this?

> 2. Design: Enter/Exit function scope support
> I believe it's required for log library to provide some kind of support for
> scope tracking. It could be an addon to core functionality to it needs to be
> configurable.

Added to my TODO list.

> 3. Design: hexdump
> I believe log library needs to provide support for dumping memory in hex
> format on log file. It could be an addon to core functionality.

I can add this, even though I'm not sure why you'd want this.

> 4. Design: logged assert
> I believe log library needs to provide support for custom assert. Such
> assert would log into log file and invoke system assert depends in assert
> level. It could be an addon to core functionality.

Well, I want to adapt SMART_ASSERT to allow for this functionality.

> 5. Implementation/Design: log entry data copying.
> Modifiers implemented by applying modifier for string that represent entry
> data. This model is inefficient in following regards:
> a) you need to make a copy of original entry data for every output.
> b) you need to prepend the strings in most cases - which is inefficient
> operation.
> c) you couldn't share the work for different outputs - you need to repeat
> it for every string
> IMO better model would be to apply modifiers to outputs instead.

Yes, I will consider this. IMO, is better to have a working model, which
can be optimized later.

> Actually since macros are not primary interface in different subsystems log
> may look different. Couple examples:
> 1. LOG( INFO, PROG_FLOW, "SocketLib" ) << "Connection esteblished to: " <<
> address;
> 2. LOG( DEBUG, RETURN_VALUE ) << "Fetched value: " << v;
> Here a LOG macro refer to some keyword by name. Usually I have keyword per
> file, per library or per class.
> 3. DEBUG_LOG( DATA_FLOW ) << "Input msg: " < msg;
> Here DEBUG_LOG automatically apply DEBUG level and refer to keyword by name

This IMO is too much to type, and I personally woudn't use it in
real-life apps.

>>>Also multithreaded application should support filtering based on thread
>>And what's stopping you from making an appender that does just this?
>>// trivial example
>>struct only_for_thread_id {
>> only_for_thread_id(int tid, appender_func forward_to)
>> : tid(tid), forward_to(forward_to) {}
>> void operator()(const std::string&,const std::string & msg) {
>> if ( ::GetCurrentThreadId() == tid) forward_to(msg);
>> }
>> int tid;
>> appender_func forward_to;
> You must be kidding, right? I want filtering on log level. So that entry
> appear or not appear in all outputs.

Please read what you just wrote a couple of lines above: "filtering
based on thread id."

>>>2. Configuration support
>>>Submitted solution supports configuration in a form of arbitrary
>>>that user could register in log. While this looks like very inflexible
>>>also as very inconvenient. In most cases I prefer log to be configured at
>>>run time from within configuration file. his would be very cumbersome
>>>modifiers model. Not only each modifier needs to be added separately. I
>>Did you take a look at Darryl's proposal?
> And how that has anything to do with what I am talking about? It's just a
> twist of the same unacceptable interface.

Unacceptable to you, yes.

>>>need to keep track of it's name to be able to remove it and it's index to
>>>know how modifiers are ordered. This ability to configure log remotely in
>>>many locations makes it impossible to really know what and how is written
>>>into log.
>>> My preferred way to configure the log is with use of configuration
>>>string. First of all it's simplify an interface and you immediately see
>>>your log entries going to look like. Here is an example:
>>>value,-details;l=debug;track=on;roll=10000;prefix=file ( line ) - time
>>You can implement Configuration Support on top of the current library.
> I do not see how it possible and why should I do this on top of useless API.

Funny, but I think your last statement is quite useless.

>>And besides, if you want to configure logging, you'll still need to
>>identify each log, appender and manipulator by a string.
> Why is that? I do not want or need to know log name to configure it. I have
> an instance.

Because when you have some setting in the configuration file, you need
whom it refers to. Therefore, you need to have some log-to-string

>>>4. Internationalization support
>>>I do see some vague traces in code that indicate that author new this
>>>to be supported. But since documentation have exactly zero information
>>>and I
>>>couldn't figure out how to do it myself, I assume that library doesn't
>>>support this. IMO it's required for industrial strength solution.
>>How many times have you needed internationalization support logging?
> Me, personally - never. But may be the fact that I work in domestic
> department of American company could be accounted for that. But for the
> boost level library this is a showstopper. Internationalization is not an
> afterthought.

Still what do you understand by internationalization?
It can deal with Unicode, if this is what you want...

>>>6. Misfeature: pre-init cashing
>>>Library starts with cashing turned on to support pre-init logging. IMO
>>>is too dangerous. Now developer needs to remember how many log entries
>>>printed. Otherwise some of them will just start disappearing (could be
>>>different in every run). And it may start happening only at the user
>>>Much more reliable solution is to use some kind of startup log that does
>>>require configuration.
>>And where would that log write to? What if it's very important data?
>>To console? What if you don't have console?
> I think I covered that in my last statement.

Well, I don't think it's covered.

>>And lets face it -- when one starts using a logging lib, will at least
>>trace one call to the debug lib. If he sees the line doesn't show up
>>anywhere, he'll most likely read the docs. If you take a look at the
> I don't see how reading the docs will help. Solution is dangerous in it's
> nature.

When you'll come up with something better, I'll use it. Until then...

>>Basic Usage, you'll see the 'flush_log_cache()' call.
>>Besides, even if you don't call it and you write 256 messages to a log,
>>the caching will automatically turn itself off and flush. The same
> And what if log is not configured yet? What will happened? We will loose all
> those 256 messages or program will crash?

Ok, I have not handled this case yet. I need to handle it -- have a
"safe log", where I will write if program exists with cache still turned
on, and logs have not been initialized yet.

>>happens if the application ends, and caching was still on.
>>>7. Misfeature: log hierarchies
>>>I personally never have a need for that and have some difficulties to
>>>imagine why would anyone have. You could probably have it on top of
>>>solution as add-on. If it doesn't require any new interfaces (or some
>>Have you heard of modules, and sub-modules? What if each
>>module/sub-module was to have a log to write to?
> I primarily use one file for everything. Why would I want 10 different
> files? So to figure out what happened I will need to jump through 10

You can have multiple logs that output to the same file.

> different output comparing timestamps? (*if* they are printed). I do
> *rarely* use more than additional logs for information dedicated to
> different target audience (operation departments, end users). But this is
> not driven by module/submodule hierarchy. To mark the modules I use much
> better idiom - keyword (all within bounds of same log).

I'm not even gonna ask why yours is such a better idiom...

>>>8. Misfeature: compile-time log support
>>>As it clear from above IMO log is (almost) never always enabled (and in
>>>case there is a way to implement this without any additional support).
>>>always disabled log could be implemented on macro level. Based on this I
>>Please enlighten me: how can you implement a disabled log at macro level?
> Numerous ways. Here is from top of my head:
> struct nil_stream {}
> template<typename T>
> nil_stream const& operator<<( nil_stream const& ns, T const&) { return ns; }
> #define LOG( .... ) if(true) {} else nil_stream() <<

You can turn on/off ***all*** logging... Wow!

And to think that BOOST_LOG allows you to turn on/off certain logs --
allowing for some information to be logged while what's not important is
turned off... Your solution is indeed much better...

Not to say that if you write something like:

LOG << some_lengthy_function();

some_lengthy_function() will still be executed, even though logging is

>>>II. Implementation flaws
>>>2. Multithreading support implementation
>>> a) For some unclear reason author chose to reimplement some of
>>>Boost.Threads functionality in some places (and in some places
>>>is used always). I believe log library shouldn't do this.
>>It was requested -- some users wanted to remove dependency on
> Well it still wrong. You couldn't justify that by user's request. BTW you do
> depend on Boost.Threads still.

If you use ts_appender(), yes.
Otherwise, not.

>>>4. collection in use
>>>appender_array and modifier_array are better be implemented using lists
>>Have you done some tests, or is this just a gut feeling?
> I did not provide any tests, did you? And it's not a gut feeling. In places
> where it's important you never need random access.

It's not about random access. It's about so many tests have shown that
std::vector is faster than std::list, especially when the vectors are
small - in the case of logger_impl.

>>>5. exception support
>>>try {
>>> LOG << "aaa" << foo()
>>>catch(...) {
>>> LOG << "exception"
>>>This construct doesn't seems generate what is expected if foo() throws an
>>What is expected?
> Try to run this and you will see.

It tries to print as much as possible from the original "LOG << "aaa" <<
  foo()" expression, and then it prints "exception".

What do you expect?

>>>5. appender_array
>>>appender_array seems to be defined in 2 placed
>>Yes, once in the log_impl's class definition, and once in
>>namespace boost { namespace logging { namespace {
> Why would you give the same name to 2 different things?
It's about context. Why not?

>>>If we do why all the appenders are so heavy?
>>I don't understand what you mean.
> So costly to copy.

How would you know?

>>>9. Time string cashing
>>>prepend_time and prepend_time_strf calls are not cashed. It's a lot of
>>>for nothing.
>>Please explain.
> You may have 1000 messages per second. And you format time string for every
> single one.

Ok, you're right. I can improve on that.

>>>10. write_msg locking
>>>write_msg shouldn't lock on appenders writing. Each appender should be
>>>locked independently instead.
>>It's because appenders/modifiers could be added/deleted while someone is
>>writing a message to the log. Thus, it needs to be thread-safe.
> You need to find an alternative solution. Every output has to be locked
> independently. This is one of the "premature pessimization" you force on the
> user.

I'll think about it.


John Torjo,    Contributing editor, C/C++ Users Journal
-- "Win32 GUI Generics" -- generics & GUI do mix, after all
-- - Sky's the limit!
-- - Click, Build, Run!

Boost list run by bdawes at, gregod at, cpdaniel at, john at