Re: [boost] [log] Review-ready version in the Vault

Andrey, Thank you for your contribution. I do not think I'll be participating in trial-runs or review as such. However, I read through the documentation and looked at the interface and I thought I'll give you my first (well, second, I looked at your lib. before) impression. I realize that everyone probably has something to say about logging and such a debate can get hot. So, I want to say beforehand that my reply is merely my impression (not a review or criticism of any kind). Firstly, it certainly seems very rich in features and configurable. That is certainly a huge plus for such a library as logging. However, I was truly overwhelmed by the documentation, the interface and the deployment examples. I seemed to be reading about familiar concepts -- severity levels, sinks, filters, formatters, streams/channels. However, after all that I did not feel an inch closer to actually deploying your library. I consider myself a moderately intelligent person but the seeming complexity felt quite intimidating. Somehow other implementations I read about (J.Torjo's, log4cpp, log4j, a bunch of loggers at SourceForge) went down much easier. Roughly, as a user I'd expect something as straightforward as boost::log log(name); // Create a log boost::log::sink sink = boost::log::sink(file)(stdout)(event_log); // Set up sinks boost::log::formatter fmt = boost::log::formatter(date)(thread_id)(name); // Set up the formatter boost::log channel = log.channel(channel-name); // Create a channel log(sink)(fmt); // Associate sinks and formatting if (log) log << "Hello, main world"; if (channel) channel << "Hello, channel world"; Maybe that's what your interface does. I just did not get that warm and fuzzy easiness/familiarity feeling after reading the docs and looking at the deployment examples. I feel there is a considerable room for improvement here. In particular I was left puzzled how you actually create a log instance (I think in the docs it is boost::src::logger). I'd expect to be able to create a log instance with a name and to be able to retrieve that same instance by the name anywhere in the program. That is, boost::log log1(name); boost::log log2(name); would be referring to the same log instance (pointer semantics). That'd be very liberating. Best, Vladimir.

On Mon, 09 Feb 2009 20:21:13 +0100, <Vladimir.Batov@wrsa.com.au> wrote: Vladimir,
[...]Firstly, it certainly seems very rich in features and configurable. That is certainly a huge plus for such a library as logging. However, I was truly overwhelmed by the documentation, the interface and the deployment examples. I seemed to be reading about familiar concepts -- severity levels, sinks, filters, formatters, streams/channels. However, after all that I did not feel an inch closer to actually deploying your library. I
If I had to explain Andrey's logging library in one sentence: The logger object routes messages to sinks based on attributes and their values.
consider myself a moderately intelligent person but the seeming complexity felt quite intimidating. Somehow other implementations I read about (J.Torjo's, log4cpp, log4j, a bunch of loggers at SourceForge) went down much easier.
From what I understand it would help you if the documentation contained a small comparison between Andrey's library and some other well-known logging libraries? I ask as in my opinion the documentation is one of the better ones of the Boost C++ libraries. I had also been using J.Torjo's logging library before I switched to Andrey's. Comparing the two libraries I like Andrey's more as the concepts Andrey's library is based on seem to be an added value. In J.Torjo's library a logger is like a stream: When you pass a message to a logger you know where it will be written to. As that is something I can do with standard streams, too, I actually expect from a logging library to introduce new concepts. Boris

Vladimir.Batov@wrsa.com.au wrote:
Andrey,
Thank you for your contribution. I do not think I'll be participating in trial-runs or review as such. However, I read through the documentation and looked at the interface and I thought I'll give you my first (well, second, I looked at your lib. before) impression. I realize that everyone probably has something to say about logging and such a debate can get hot. So, I want to say beforehand that my reply is merely my impression (not a review or criticism of any kind).
Thank you for your feedback.
Firstly, it certainly seems very rich in features and configurable. That is certainly a huge plus for such a library as logging. However, I was truly overwhelmed by the documentation, the interface and the deployment examples. I seemed to be reading about familiar concepts -- severity levels, sinks, filters, formatters, streams/channels. However, after all that I did not feel an inch closer to actually deploying your library. I consider myself a moderately intelligent person but the seeming complexity felt quite intimidating. Somehow other implementations I read about (J.Torjo's, log4cpp, log4j, a bunch of loggers at SourceForge) went down much easier.
Roughly, as a user I'd expect something as straightforward as
boost::log log(name); // Create a log boost::log::sink sink = boost::log::sink(file)(stdout)(event_log); // Set up sinks boost::log::formatter fmt = boost::log::formatter(date)(thread_id)(name); // Set up the formatter boost::log channel = log.channel(channel-name); // Create a channel
log(sink)(fmt); // Associate sinks and formatting
if (log) log << "Hello, main world"; if (channel) channel << "Hello, channel world";
Maybe that's what your interface does. I just did not get that warm and fuzzy easiness/familiarity feeling after reading the docs and looking at the deployment examples. I feel there is a considerable room for improvement here.
There are different ways to initialize the library, including reading a settings file and initializing from the code. The latter is quite straightforward and basically resembles what you suggested. However, point taken. I must admit, I didn't pay much attention to examples that could help to grasp the library better.
In particular I was left puzzled how you actually create a log instance (I think in the docs it is boost::src::logger). I'd expect to be able to create a log instance with a name and to be able to retrieve that same instance by the name anywhere in the program. That is,
boost::log log1(name); boost::log log2(name);
would be referring to the same log instance (pointer semantics). That'd be very liberating.
You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this: BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >) void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging }

... You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this:
BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >)
void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging }
Hmm, I honestly think that having "loggers as class members" is a bad idea. I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like boost::log log1(name); boost::log log2(name); // The same as log1 Best, V.

On Tue, 10 Feb 2009 19:25:10 +1100, "Vladimir Batov" <batov@people.net.au> wrote:
Hmm, I honestly think that having "loggers as class members" is a bad idea.
I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
I think you should be able to do this using the new flyweight library. Sebastian

boost::log log1(name); boost::log log2(name); // The same as log1
I think you should be able to do this using the new flyweight library.
Sebastian, Thanks for the pointer. I'll need to have another closer look at the gadget where/how to stick it into. :-) Many thanks, V.

----- Original Message ----- From: "Vladimir Batov" <batov@people.net.au> To: <boost@lists.boost.org> Sent: Tuesday, February 10, 2009 9:25 AM Subject: Re: [boost] [log] Review-ready version in the Vault
... You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this:
BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >)
void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging }
Hmm, I honestly think that having "loggers as class members" is a bad idea. I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
Hi, I'm curious ti know how do you retrieve the same log by name without static initialization? Best, Vicente

vicente.botet wrote:
----- Original Message ----- From: "Vladimir Batov" <batov@people.net.au> To: <boost@lists.boost.org> Sent: Tuesday, February 10, 2009 9:25 AM Subject: Re: [boost] [log] Review-ready version in the Vault
... You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this:
BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >)
void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging } Hmm, I honestly think that having "loggers as class members" is a bad idea. I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
Hi,
I'm curious ti know how do you retrieve the same log by name without static initialization?
This is what my example does, if I understand you correctly. The logger is initialized only when first requested.

boost::log log1(name); boost::log log2(name); // The same as log1
I'm curious ti know how do you retrieve the same log by name without static initialization?
This is what my example does, if I understand you correctly. The logger is initialized only when first requested.
Well, I am under impression that Andrey's solution is somewhat different from what I have in mind. Although I must say my implementation is fairly conventional -- a logger (like boost::log) is actually a handle/proxy with pointer semantics, like shared_ptr<actual-logger> Internally, I maintain a name/logger map and, when boost::log log(name); is requested, I simply return another reference/handle to the respective internal logger implementation (here it seems to differ from Andrey's). If a logger with the requested name does not, I create one. V.

----- Original Message ----- From: "Vladimir Batov" <batov@people.net.au> To: <boost@lists.boost.org> Sent: Wednesday, February 11, 2009 10:10 AM Subject: Re: [boost] [log] Review-ready version in the Vault
boost::log log1(name); boost::log log2(name); // The same as log1
I'm curious ti know how do you retrieve the same log by name without static initialization?
This is what my example does, if I understand you correctly. The logger is initialized only when first requested.
Well, this need the library declares a static map. You should be able to do that, as Sebastian has signaled, with flyweight library. The question now is do the users need the library provide the way you want to use a log?
Well, I am under impression that Andrey's solution is somewhat different from what I have in mind. Although I must say my implementation is fairly conventional -- a logger (like boost::log) is actually a handle/proxy with pointer semantics, like
shared_ptr<actual-logger>
Internally, I maintain a name/logger map and, when
boost::log log(name);
is requested, I simply return another reference/handle to the respective internal logger implementation (here it seems to differ from Andrey's). If a logger with the requested name does not, I create one.
I don't think the solution is different, only complementaty. As far as the library allows to declare a variable of type logger and then configure it you shoulbbe able to get your desired usage. Could you try to implement how do you want to use a logger with the library from Andrei and the flyweight? BTW, the flyweight approach has the liability that in order to check if we can log we need to get the logger from the flyweight, which is even if quite efficient (O(N)) on the number of loggers. But this is another history. Best, Vicente Thanks, Vicente

----- Original Message ----- From: "Andrey Semashev" <andrey.semashev@gmail.com> To: <boost@lists.boost.org> Sent: Tuesday, February 10, 2009 5:55 PM Subject: Re: [boost] [log] Review-ready version in the Vault
vicente.botet wrote:
----- Original Message ----- From: "Vladimir Batov" <batov@people.net.au> To: <boost@lists.boost.org> Sent: Tuesday, February 10, 2009 9:25 AM Subject: Re: [boost] [log] Review-ready version in the Vault
... You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this:
BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >)
void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging } Hmm, I honestly think that having "loggers as class members" is a bad idea. I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
Hi,
I'm curious ti know how do you retrieve the same log by name without static initialization?
This is what my example does, if I understand you correctly. The logger is initialized only when first requested.
Sorry, this was addresed to Vladimir. Vicente

I honestly think that having "loggers as class members" is a bad idea.
why do you think that?
Because in my book a class represents a certain domain-specific abstraction and encapsulates the data and behavior relevant to that abstraction. A logger does not fit that description for me. Come to think of it, I never actually had even such a need. As I indicated my usage pattern is to retrieve a logger by its name (well, a handle to it) when its needed (on the file level or the function level). Once retrieved, I keep that reference around (again, on the file level or the function level) for efficiency purposes. I understand Andrey's library provides that kind of behavior. V.

Hi Vladimir,
I honestly think that having "loggers as class members" is a bad idea. [A class] represents a certain domain-specific abstraction and encapsulates the data and behavior relevant to that abstraction. A logger does not fit that description for me.
whether a logger fits into that description or not depends very much on the domain-specific thing that a particular class is supposed to represent. For instance, it's not hard to imagine classes that include logging as a part of their specification. Take care, Peter

Vladimir Batov wrote:
... You can either create a logger and use it as an independent object, or you can declare a global logger and use it everywhere you need it. The first approach is good to store loggers as class members. The second is convenient if you tend to use functional code or don't need any context-specific attributes in it. It can be done like this:
BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::severity_logger_mt< >)
void foo() { src::severity_logger_mt< >& lg = my_logger::get(); // go ahead logging }
Hmm, I honestly think that having "loggers as class members" is a bad idea.
And I find it extremely useful. In fact, I tend to avoid making loggers irrelevant to some sensible entity in the program. For example, if I have a class that implements a network connection, I would most likely make logger a member of this class. This allows to seemlessly embed information related to this connection into log records. In consequence, this allows to apply filtering based on this information.
I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
The example above does just that. All you need is to have the BOOST_LOG_DECLARE_GLOBAL_LOGGER in some common header, and include it whenever you need the my_logger logger. The my_logger is a singleton that is accessible from different translation units and even different modules (dlls, sos). It does have static initialization problems solved. This is the key difference from declaring a namespace-scope logger variable.

Hmm, I honestly think that having "loggers as class members" is a bad idea.
And I find it extremely useful. In fact, I tend to avoid making loggers irrelevant to some sensible entity in the program. For example, if I have a class that implements a network connection, I would most likely make logger a member of this class. This allows to seemlessly embed information related to this connection into log records. In consequence, this allows to apply filtering based on this information.
Yes, I understand and I am not trying to convert you or anything. Just expressing my view which you might or might not take into account. Design-wise I do not believe a logger should be part of a class. That inclusion of convenience that you describe is unlikely to happen in a more structured development environment (where design is evaluated and approved separately and might be even done by different people). I feel that inclusion certainly goes against the design trend of making classes as clean/small as possible. Implementation-wise, you do not mention if you "make logger a member of this class" as a static member. If it is not static, then the associated overhead is not acceptable (I have about 30,000 objects). As a static member it is not greatly different from a file-scope static.
I do not usually like global objects either (static initialization issues, too much visibility). However, what I definitely use a lot is retrieving the same log by name in different compilation modules (that takes the visibility down). Like
boost::log log1(name); boost::log log2(name); // The same as log1
The example above does just that. All you need is to have the BOOST_LOG_DECLARE_GLOBAL_LOGGER in some common header, and include it whenever you need the my_logger logger. The my_logger is a singleton that is accessible from different translation units and even different modules (dlls, sos). It does have static initialization problems solved. This is the key difference from declaring a namespace-scope logger variable.
I have not got that impression that your BOOST_LOG_DECLARE_GLOBAL_LOGGER-based "example above does just that". To start with, I (I'll use "I", "you" for convenience. nothing personal) simply create a logger instance on the spot when I need it. You insist I *declare* first with some hairy :-) macro "in some common header". Then, to actually get the logger, I need "src::severity_logger_mt< >& lg = my_logger::get();". I do not mind you do it the way you do it. However, I insist that "my" way is very incremental. I can start the most basic: boost::log() << "Hello"; and then incorporate streams, filters, formatters, etc. when the need arises. I do not have the feeling you do "just that". IMHO the incremental deployment nature is extremely important for a logging library. Probably your library can do all that. What I feel is that the user's exposure to the library complexity might be looked at. V.

Vladimir Batov wrote:
Hmm, I honestly think that having "loggers as class members" is a bad idea.
And I find it extremely useful. In fact, I tend to avoid making loggers irrelevant to some sensible entity in the program. For example, if I have a class that implements a network connection, I would most likely make logger a member of this class. This allows to seemlessly embed information related to this connection into log records. In consequence, this allows to apply filtering based on this information.
Yes, I understand and I am not trying to convert you or anything. Just expressing my view which you might or might not take into account.
That's ok, I'm not trying to persuade you either. I'm just sharing my view on these things, as well as answering questions. :)
Design-wise I do not believe a logger should be part of a class. That inclusion of convenience that you describe is unlikely to happen in a more structured development environment (where design is evaluated and approved separately and might be even done by different people). I feel that inclusion certainly goes against the design trend of making classes as clean/small as possible.
I'd say I can both agree and disagree with your point, depending on the situation. For classes that play active role in an application, such as the aforementioned network connection class, I think it is perfectly reasonable to store a logger in it as a member. It doesn't violate the design because writing logs is actually a part of functionality of such classes. I can say more, I had cases when such approach was essentially mandated by the requirements on the project management level, because such practice proved to be useful. If your classes play passive role in the application, for instance, classes that represent dictionary entries, I totally agree with you. Such classes do tend to have many instances and size overhead can be significant, while there is no obvious benefit from having a logger inside each of them.
Implementation-wise, you do not mention if you "make logger a member of this class" as a static member. If it is not static, then the associated overhead is not acceptable (I have about 30,000 objects). As a static member it is not greatly different from a file-scope static.
I agree, there is no point of having a static member logger. Unless you want to restrict access to it in such way.
I have not got that impression that your BOOST_LOG_DECLARE_GLOBAL_LOGGER-based "example above does just that". To start with, I (I'll use "I", "you" for convenience. nothing personal) simply create a logger instance on the spot when I need it. You insist I *declare* first with some hairy :-) macro "in some common header". Then, to actually get the logger, I need "src::severity_logger_mt< >& lg = my_logger::get();".
Sorry, I don't see much difference between logger lg(name); and logger& lg = name::get(); The latter looks more descriptive to me. If you like your syntax more, you can write a trivial wrapper around "get()" or use flyweight. However, after getting a quick look at flyweight docs, it doesn't seem to provide inter-module facilities.
I do not mind you do it the way you do it. However, I insist that "my" way is very incremental. I can start the most basic:
boost::log() << "Hello";
and then incorporate streams, filters, formatters, etc. when the need arises. I do not have the feeling you do "just that".
Yet again, you can create loggers at your will, so your case is covered by the library. The only thing you have to do besides creating a logger is to provide a sink, where to store logs. For instance, to put logs into a file you have to call init_log_to_file("file.log"); somewhere in the beginning of your main(). I think you'll have to do it with any approach, be that my library or a different one.
IMHO the incremental deployment nature is extremely important for a logging library. Probably your library can do all that. What I feel is that the user's exposure to the library complexity might be looked at.
I fact, the Tutorial in the docs was written with respect to increasing complexity. The first two steps (about sinks and loggers) describe the basics to get things started. The rest of the steps go deeper and introduce filtering and formatting.

Andrey Semashev <andrey.semashev <at> gmail.com> writes:
Sorry, I don't see much difference between
logger lg(name);
and
logger& lg = name::get();
The latter looks more descriptive to me. If you like your syntax more, you can write a trivial wrapper around "get()" or use flyweight. However, after getting a quick look at flyweight docs, it doesn't seem to provide inter-module facilities.
There's a component for getting intermodule flyweights: http://boost.org/libs/flyweight/doc/tutorial/configuration.html#intermodule_... Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Joaquin M Lopez Munoz wrote:
Andrey Semashev <andrey.semashev <at> gmail.com> writes:
However, after getting a quick look at flyweight docs, it doesn't seem to provide inter-module facilities.
There's a component for getting intermodule flyweights:
http://boost.org/libs/flyweight/doc/tutorial/configuration.html#intermodule_...
Great! Thanks for the link. Then it seems using flyweight is a real alternative to the Boost.Log solution.
participants (8)
-
Andrey Semashev
-
Boris Schaeling
-
Joaquin M Lopez Munoz
-
Peter Simons
-
Sebastian Redl
-
vicente.botet
-
Vladimir Batov
-
Vladimir.Batov@wrsa.com.au