
I have continued working on a logging library as a possible contender for a new Boost library. I have made sweeping changes inspired by comments and requests from a number of people (all very helpful). Here are some of the properties of the library as it is now. All components are completely independent of one another and highly parameterized for flexibility. The primary components are: -Entries which can be anything at all (built-in or user-defined types). There are a number of custom entries that I have created that support for example ostream like formatting. -Logs which actually go about the job of storing/processing/whatevering entries. I have created a few logs for writing to files, ostreams, storing entries in a list, etc. -Log managers are intermediaries between users and logs. They can do whatever they wish prior to passing entries on to logs. I have implemented a few managers so far. 1 that does virtually nothing. 1 that discriminates based on logging levels (only entries of appropriate levels get logged) and one that discriminates on levels and supplies a callback mechanism for whatever levels desired. Many managers can be attached to the same log. -Level Policies are classes supplied to level log managers indicating what the different allowable levels are. The documentation is still in very rough shape but the code in its current state is available for simple download from www.eng.buffalo.edu/~johneddy. It is not well Boost-ified yet but the directory structure is in place and some example files are available. Everything is implemented in header files at this point so no library need be built. John

I have continued working on a logging library as a possible contender for a new Boost library.
Hi, I just had a quick look. My usual logging needs are: 1. Being able to remove all logging code in a release build I see you have macros, though their names are long and it wasn't clear what all the different macros do (in examples/macros.cpp). Any issues in just doing a #define after including your headers to rename your macros into something short so I can simply write: LOG("In f, a="<<a<<",b="<<b); 2. Logging with timestamp automatically prefixed. Do you have something pre-built for this? If not, can you show what code I have to write to implement it. 3. Logging with file, line number and/or function name included. 4. Logging to an in-memory buffer that only stores e.g. the last 50K or the last 100 log lines; with a function to output the log to an ostream. (i.e. I want to add LOG() lines are the start of each function, and at certain other key points, and then output a history trace when something asserts). Do you have anything like this? If not which current component would you suggest I use as a starting point to modify? Darren
current state is available for simple download from www.eng.buffalo.edu/~johneddy. It is not well Boost-ified yet but the

Hello Darren, I very much appreciate your feedback and will answer your questions as best I can. Darren Cook wrote:
I have continued working on a logging library as a possible contender for a new Boost library.
Hi, I just had a quick look. My usual logging needs are:
1. Being able to remove all logging code in a release build I see you have macros, though their names are long and it wasn't clear what all the different macros do (in examples/macros.cpp). Any issues in just doing a #define after including your headers to rename your macros into something short so I can simply write: LOG("In f, a="<<a<<",b="<<b);
You can easily remove all logging code from your release build by not defining BOOST_LOGGING_ON. If you do that, all the headers wind up doing nothing with the exception of the macro header which causes all the macros to expand to nothing. I agree that the macro names are long and a bit confusing. They all resolve to nothing if BOOST_LOGGING_ON is not defined with the exception of the BOOST_LOGGING_IF_OFF macro which I'm not sure is useful at all anyway. The difference between the various *LOG* macros is the ability to determine whether or not an entry will get logged before the entry is actually created. If that can be done (which it can using the macros) the process of logging in the case where it is common for log entries to be rejected can become much cheaper. If I continue to develop this as a possible boost library, I will of course accept suggestions for renaming the macros and anything else for that matter. I can see how something like BOOST_LOG() would be simpler than BOOST_LOGGING_LOG() etc.
2. Logging with timestamp automatically prefixed. Do you have something pre-built for this? If not, can you show what code I have to write to implement it.
There is a "helper" for automatically putting a timestamp into an entry. There are also a number of pre-existing entry types that are implementing that helper and of course, I can create any others that you might like. Below is an example of an entry that holds only a timestamp along with any other text supplied to it. The constructor of the entry takes a boost::posix_time::ptime object as the stamp (the default value is the current time) and any leading text that you wish to associate with the entry. /* * This example shows how to use the basic timestamp entry. */ #include <iostream> #include <boost/logging/logs/ostream_log.hpp> #include <boost/logging/entries/timestamp_entry.hpp> #include <boost/logging/log_managers/basic_log_manager.hpp> using namespace std; using namespace boost::logging; int main(int argc, char* argv) { ostream_log<> alog(cerr); basic_log_manager<ostream_log<> > alm(alog); alm.log(timestamp_entry<>("Hello. Its ") << timestamp_entry<>::stamp << ". Have a nice day." ); return 0; } If you wanted the timestamp to be the very first thing, simply don't supply anything to the timestamp entry constructor. ie timestamp_entry<>() instead of timestamp_entry<>("Hello...
3. Logging with file, line number and/or function name included.
I have not built the facilities for this yet but I certainly plan to do the file and line number at least. It is a fairly simple matter of creating the proper entry helpers and implementing some entry types that use them. I would like to note that anything "stream-outable" can be inserted to any of the basic_entry derivative types. The only thing that you get by using the helpers is that the entry will store the information (such as a timestamp) separately from the rest of the text so that it can be recalled. This is so that entries can be searched or otherwise differentiated based on their properties. Something as simple as the file and line numbers using the macros __FILE__ and __LINE__ could be inserted into any basic_entry right now so long as you don't wish to query for entries based on that information in your code. So if you are just printing this stuff to a file or other ostream or whatever and then forgetting about it (in code anyway) you could replace the above with: alm.log(basic_entry<>("Hello. Its ") << boost::posix_time::microsec_clock::local_time() << " and we're in file " << __FILE__ << " on line " << __LINE__ << ". Have a nice day." );
4. Logging to an in-memory buffer that only stores e.g. the last 50K or the last 100 log lines; with a function to output the log to an ostream. (i.e. I want to add LOG() lines are the start of each function, and at certain other key points, and then output a history trace when something asserts). Do you have anything like this? If not which current component would you suggest I use as a starting point to modify?
This would be a matter of creating a new log type. Currently I have implemented an ostream_log, file_log, list_log, appending_log, decorator_log, and null_log. These are all fairly simple and I see the need for many more log types. The only requirement of a log is that it have a method called log that accepts a single entry parameter. The method should probably be templatized but of course, it needn't be depending on the types of things you are logging. The log managers will accept the log type as the first template argument and use the log method when appropriate. To do what you want, I think you should plan to use the appending log with your custom buffered log and an appender (callback) to output to a stream - or the decorator log with your custom log as the first and an ostream log as the second. Then each entry as it comes in would be written into your custom log and then out to the ostream. The decorator is fairly simple to use. For example, here is a decorator for writing to cerr and to a file called "run.log". ostream_log<> olog(cerr); file_log<> flog("run.log"); decorator_log<ostream_log<>, file_log<> > dlog(olog, flog); basic_log_manager<decorator_log<ostream_log<>, file_log<> > > alm(dlog); Log whatever to alm as you normally would and it will wind up on standard error and in run.log. To actually do this right now I noticed that you will have to comment out the code in the constructor bodies of the file log. I am in the process of getting rid of the exception handling and so what's there won't compile.
Darren
current state is available for simple download from www.eng.buffalo.edu/~johneddy. It is not well Boost-ified yet but the
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Tue, 15 Feb 2005 12:00:15 -0500, John Eddy <johneddy@eng.buffalo.edu> wrote:
2. Logging with timestamp automatically prefixed. Do you have something pre-built for this? If not, can you show what code I have to write to implement it.
There is a "helper" for automatically putting a timestamp into an entry. There are also a number of pre-existing entry types that are implementing that helper and of course, I can create any others that you might like. Below is an example of an entry that holds only a timestamp along with any other text supplied to it. The constructor of the entry takes a boost::posix_time::ptime object as the stamp (the default value is the current time) and any leading text that you wish to associate with the entry.
I think associating the format more directly with the output "device" (e.g. the logger) makes more sense. Its a pretty rare case that you'd want to log a timestamp with one message and not *every* message. -- Caleb Epstein caleb dot epstein at gmail dot com

Caleb Epstein wrote:
On Tue, 15 Feb 2005 12:00:15 -0500, John Eddy <johneddy@eng.buffalo.edu> wrote:
2. Logging with timestamp automatically prefixed. Do you have something pre-built for this? If not, can you show what code I have to write to implement it.
There is a "helper" for automatically putting a timestamp into an entry. There are also a number of pre-existing entry types that are implementing that helper and of course, I can create any others that you might like. Below is an example of an entry that holds only a timestamp along with any other text supplied to it. The constructor of the entry takes a boost::posix_time::ptime object as the stamp (the default value is the current time) and any leading text that you wish to associate with the entry.
I think associating the format more directly with the output "device" (e.g. the logger) makes more sense. Its a pretty rare case that you'd want to log a timestamp with one message and not *every* message.
I see what you mean. Since currently anything of any type will pass through the log managers it would be simple enough to create and pass along a simple data holder to the log which would then assemble the holders' contents into some specified output format, automatically installing a timestamp or whatever. See this example: #include <iostream> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/logging/log_managers/basic_log_manager.hpp> using namespace std; using namespace boost::logging; using namespace boost::posix_time; struct my_e_data { const char* file; int line; string text; my_e_data(const char* f, int l, string t) : file(f), line(l), text(t) {} }; struct my_log { void log(const my_e_data& e) { cerr << to_simple_string(microsec_clock::local_time()) << ": FILE - " << e.file << ": LINE - " << e.line << ": TEXT - " << e.text << "\n\n"; } }; int main(int argc, char* argv) { my_log alog; basic_log_manager<my_log > alm(alog); alm.log(my_e_data(__FILE__, __LINE__, "Bad Operation")); return 0; } The other current option would simply be to always use a timestamped entry type. The downside there is that you always have to stream out the stamp method in order to write it into the text of the entry (<< entry_type<>::stamp). Thanks, John

int main(int argc, char* argv) { my_log alog; basic_log_manager<my_log > alm(alog); alm.log(my_e_data(__FILE__, __LINE__, "Bad Operation")); return 0; }
That is less scary than the earlier example. There is no way I'm going to type that out every time but I can do: #define LOG(x) alm.log(my_e_data(__FILE__, __LINE__, (x))) Hhmm, no, I've lost the ability to write: LOG("a="<<a<<",b="<<b); So I guess I define my macro based on your earlier example: #define LOG(x) alm.log(basic_entry<>("") \ << boost::posix_time::microsec_clock::local_time() \ << "," << __FILE__ \ << "," << __LINE__ \ << "," << BOOST_CURRENT_FUNCTION \ << ":" << (x)) Darren

Darren Cook wrote:
int main(int argc, char* argv) { my_log alog; basic_log_manager<my_log > alm(alog); alm.log(my_e_data(__FILE__, __LINE__, "Bad Operation")); return 0; }
That is less scary than the earlier example. There is no way I'm going to type that out every time but I can do: #define LOG(x) alm.log(my_e_data(__FILE__, __LINE__, (x)))
Hhmm, no, I've lost the ability to write: LOG("a="<<a<<",b="<<b);
You have lost it if you want to do it at entry creation time. If you want, you can create a log that formats things that way as in the "less-scary" example you are referencing.
So I guess I define my macro based on your earlier example: #define LOG(x) alm.log(basic_entry<>("") \ << boost::posix_time::microsec_clock::local_time() \ << "," << __FILE__ \ << "," << __LINE__ \ << "," << BOOST_CURRENT_FUNCTION \ << ":" << (x))
Yes, you could do that. However, if it is the only way you wish to format your log entries throughout your application, it would be much cheaper to have a custom log associated with a custom entry type as in my example. The reason is that the basic_entry requires the creation and usage of an ostringstream which is a fairly heavyweight object. It all depends really on what you want x to be. If x were always going to be a string literal for example, you could have instead: #include <iostream> #include <boost/current_function.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/logging/log_managers/basic_log_manager.hpp> using namespace std; using namespace boost::logging; using namespace boost::posix_time; struct my_e_data { const char* file; const char* fctn; int line; const char* text; my_e_data(const char* f, const char* fn, int l, const char* t) : file(f), fctn(fn), line(l), text(t) {} }; struct my_log { void log(const my_e_data& e) { // cerr or whatever. cerr << to_simple_string(microsec_clock::local_time()) << "," << e.file << "," << e.line << "," << e.fctn << ":" << e.text << '\n'; } }; Then your macro could look like this: #define LOG(tlm, x) tlm.log(my_e_data(__FILE__, BOOST_CURRENT_FUNCTION, __LINE__, (x))) (unless of course you have a global log called alm, as it seems you would need to have by your macro, in which case you could omit the first parameter). This would be much, much cheaper and your usage would look like this: my_log alog; /* doesn't have to be the basic manager and for that matter, you don't really need to use a manager at all if you don't want. Your macro would work fine if you just passed it alog instead of alm b/c the interface for logs is similar to that of managers in that they are both required to have a log function taking a single parameter (an entry). */ basic_log_manager<my_log> alm(alog); LOG(alm, "There is some problem in this application.");
Darren _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Thanks, John

Hhmm, no, I've lost the ability to write: LOG("a="<<a<<",b="<<b);
... The reason is that the basic_entry requires the creation and usage of an ostringstream which is a fairly heavyweight object. It all depends really on what you want x to be. If x were always going to be a string literal for example, you could have instead:
x is hardly ever a string literal, so I can accept the ostringstream overhead. Taking a step back, many of my log lines are exactly like the above example, so a very useful macro would work like John Torjo's smart assert and I could write: LOGVARS(a)(b); As my macro will insert file/line/function name I generally don't need to write anything else.
(unless of course you have a global log called alm, as it seems you would need to have by your macro, in which case you could omit the first parameter).
This is how I currently do it: global logs with different macros to write to each. But my current logging is too inflexible, so, yes, I should switch to a single macro with logname as the first parameter. Darren P.S. Even better would be able to write LOGVARS(*) and have it log all function parameters and local vars that are currently in scope. Is this something that the Wave library could do, or does it require a full C++ parser?

Darren Cook wrote:
P.S. Even better would be able to write LOGVARS(*) and have it log all function parameters and local vars that are currently in scope. Is this something that the Wave library could do, or does it require a full C++ parser?
For this you'll need a at least a partial C++ parser. That's not possible to achieve with the preprocessor, because it has no context information about the preprcessed C++ token stream - it cares for macros and directives only. Some compilers have a feature though, which allows you to get at the current function name, though. Perhaps this would be of any help for you? Regards Hartmut

P.S. Even better would be able to write LOGVARS(*) and have it log all function parameters and local vars that are currently in scope. Is this something that the Wave library could do, or does it require a full C++ parser?
For this you'll need a at least a partial C++ parser.
Thanks, I suspected as much.
Some compilers have a feature though, which allows you to get at the current function name, though. Perhaps this would be of any help for you?
I'm using BOOST_CURRENT_FUNCTION; I couldn't tell you quickly which header/library that comes from though. Darren

I'm using BOOST_CURRENT_FUNCTION; I couldn't tell you quickly which header/library that comes from though.
You get it in the header <boost/current_function.hpp>. Some compilers have macros similar to __LINE__ and __FILE__ for the function signature but these macros are not standardized. So BOOST_CURRENT_FUNCTION makes use of the non-standard macros where possible and resolves to "(unknown)" otherwise. John

I don't see anything in <boost/current_function.hpp> which would allow one to determine at compile time whether or not BOOST_CURRENT_FUNCTION is able to produce anything useful (instead of "unknown"). The attached patch adds another define, BOOST_CURRENT_FUNCTION_KNOWN, which will be true if BOOST_CURRENT_FUNCTION should produce something better than "unknown", or false otherwise. The patch is pretty simple. It leverages the existing logic in <boost/current_function.hpp>, so it should just "work" on any compiler which didn't already have problems with <boost/current_function.hpp> before the patch. So far I've tested it with msvc-6.0 (BOOST_CURRENT_FUNCTION_KNOWN=0) and msvc-7.1 (BOOST_CURRENT_FUNCTION_KNOWN=1). - james John Eddy wrote:
I'm using BOOST_CURRENT_FUNCTION; I couldn't tell you quickly which header/library that comes from though.
You get it in the header <boost/current_function.hpp>. Some compilers have macros similar to __LINE__ and __FILE__ for the function signature but these macros are not standardized. So BOOST_CURRENT_FUNCTION makes use of the non-standard macros where possible and resolves to "(unknown)" otherwise.
-- __________________________________________________________ James Fowler, Open Sea Consulting http://www.OpenSeaConsulting.com, Marietta, Georgia, USA Do C++ Right. http://www.OpenCpp.org, opening soon! Index: boost/boost/current_function.hpp =================================================================== RCS file: /cvsroot/boost/boost/boost/current_function.hpp,v retrieving revision 1.10 diff -r1.10 current_function.hpp 53a54
# define BOOST_CURRENT_FUNCTION_KNOWN 0 56a58,61 #ifndef BOOST_CURRENT_FUNCTION_KNOWN # define BOOST_CURRENT_FUNCTION_KNOWN 1 #endif

So I guess I define my macro based on your earlier example: #define LOG(x) alm.log(basic_entry<>("") \ << boost::posix_time::microsec_clock::local_time() \ << "," << __FILE__ \ << "," << __LINE__ \ << "," << BOOST_CURRENT_FUNCTION \ << ":" << (x))
You might also consider a simple subclass of basic_entry that does the formatting in the constructor like: struct my_entry : public basic_entry<> { template <typename X> my_entry( const char* file, const int line, const char* fctn, const X& x ) : basic_entry<>() { basic_entry<>::operator << (boost::posix_time::microsec_clock::local_time()) << "," << file << "," << line << "," << fctn << ":" << x; } }; Then your macro would read: #define LOG(tlm, x) tlm.log(my_entry(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, x)) John
participants (5)
-
Caleb Epstein
-
Darren Cook
-
Hartmut Kaiser
-
James Fowler
-
John Eddy