
Would anyone be interested in a library for doing logging? I've written one for my own purposes and thought others might find it useful. It will store log entries at various levels (FATAL, SHOUT, ERROR, WARN, INFO, DEBUG, DETAIL). There is a default level and the level can be customized for entries by individual instances of any type and for entries by any types as a whole. I'll be glad to provide more information if anyone is interested. John

John Eddy wrote:
Would anyone be interested in a library for doing logging?
Yes.
I've written one for my own purposes and thought others might find it useful. It will store log entries at various levels (FATAL, SHOUT, ERROR, WARN, INFO, DEBUG, DETAIL). There is a default level and the level can be customized for entries by individual instances of any type and for entries by any types as a whole.
I'll be glad to provide more information if anyone is interested.
You might want to describe briefly how your library compares to existing libraries, e.g., to this one by John Torjo http://lists.boost.org/MailArchives/boost/msg73854.php which he may eventually propose for review.
John

Thank you for pointing me to that. My implementation is quite different but I like many features of the Torjo library. Some of the major differences between the Torjo library and mine include: From what I can see in the Torjo library: There is no fixed collection of logging levels. You declare separate logs and log to the one that is applicable. A global function returning a reference to a log is created in order to share a log as done in the detail.h file of the examples. He has designed his logs to accept logging text using an ostringstream. He supports use of "appenders" to echo messages. My library: Has a fixed collection of logging levels and so a single log can be used and the desired level specified. A log is declared at any scope you wish and can be shared in the same manner as any other object (I most often pass one around by reference). My logs accept messages as strings. My library does not currently support the "appenders" as his does. I will add this, I like it very much. It is a must. My library is set up to log entries conditionally based on the currently applicable logging level and the level at which the entry request was made. The logging levels are FATAL > SHOUT > ERROR > WARN > INFO > DEBUG > DETAIL. I modeled the logging levels after those of the COUGAAR agent architecture logging facility. Take a look at the simple example below. #include <boost/logger.hpp> #include <iterator> #include <iostream> using namespace std; using namespace boost::logging; struct test1 { int field; }; int main(int argc, char* argv[]) { logger mylog; // default level is WARN // anything logged at this point would only cause an entry creation // if it were logged a the WARN level or greater. mylog.warn("This is an anonomous log entry"); mylog.warn("main", "main is logging a warning"); // create some items to associate with log entries. test1 t11, t12; // make the logging level for all not-otherwise-level-set test1 objects DEBUG mylog.set_logging_level<test1>(logger::DEBUG); // now, the first statement below will cause a log entry and the second will not. mylog.info(&t11, "This is an info entry"); mylog.detail(&t12, "This is a detail entry"); // now make the logging level for t11 FATAL mylog.set_logging_level(&t11, logger::FATAL); // now, the first statement below will cause a log entry and the second will not. mylog.fatal(&t11, "Call me T11", "This is the fatal error text"); mylog.debug(&t11, "This is a T11 debug message"); // logging messages can then be retrieved from the log using predicates, of which // a number of simple examples I've already created. For now, I will just write // all logged messages to the screen. const log_entry_list& all = mylog.get_all_entries(); copy(all.begin(), all.end(), ostream_iterator<logger::log_entry>(cerr, "")); } The above program causes the output: 2005-Jan-28 14:58:33.903660: Anonymous, WARN - This is an anonomous log entry 2005-Jan-28 14:58:33.903660: main, WARN - main is logging a warning 2005-Jan-28 14:58:33.923689: struct test1, INFO - This is an info entry 2005-Jan-28 14:58:33.923689: Call me T11, FATAL - This is the fatal error text John Jonathan Turkanis wrote:
John Eddy wrote:
Would anyone be interested in a library for doing logging?
Yes.
I've written one for my own purposes and thought others might find it useful. It will store log entries at various levels (FATAL, SHOUT, ERROR, WARN, INFO, DEBUG, DETAIL). There is a default level and the level can be customized for entries by individual instances of any type and for entries by any types as a whole.
I'll be glad to provide more information if anyone is interested.
You might want to describe briefly how your library compares to existing libraries, e.g., to this one by John Torjo
http://lists.boost.org/MailArchives/boost/msg73854.php
which he may eventually propose for review.
John
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

You may want to make your logging levels a policy which defaults to an enumeration of your currently listed logging levels. This way, if someone has a need for a logger with other levels of output they can just specify a different enumeration for the policy. Instead of having separate logging functions (log, warn, info) you could have one which accepts a value of the enumeration's type as the first param. The logging level could be a member of this enumeration as well, so a simple less-than comparison could determine whether or not to output the message. -Jason

Thanks for the suggestion. I have implemented a version with this suggestion which would change the example I posted earlier to look like this: class default_levels { public: enum levels { DETAIL, DEBUG, INFO, WARN, ERROR, SHOUT, FATAL, DEFAULT = WARN // DEFAULT is required. The rest are up to the user. }; }; #include <boost/logging.hpp> #include <iterator> #include <iostream> using namespace std; using namespace boost::logging; struct test1 { int field; }; int main(int argc, char* argv[]) { typedef logger_base<default_levels> log_type; log_type mylog; // default level is default_levels::DEFAULT (WARN) // anything logged at this point would only cause an entry creation // if it were logged at the WARN level or greater. mylog.log(default_levels::WARN, "This is an anonomous log entry"); mylog.log(default_levels::WARN, "main is logging a warning"); // create some items to associate with log entries. test1 t11, t12; // make the logging level for all not-otherwise-level-set test1 objects DEBUG mylog.set_logging_level<test1>(default_levels::DEBUG); // now, the first statement below will cause a log entry and the second will not. mylog.log(default_levels::INFO, &t11, "This is an info entry"); mylog.log(default_levels::DETAIL, &t12, "This is a detail entry"); // now make the logging level for t11 FATAL mylog.set_logging_level(&t11, default_levels::FATAL); // now, the first statement below will cause a log entry and the second will not. mylog.log(default_levels::FATAL, &t11, "Call me T11", "This is the fatal error text"); mylog.log(default_levels::DEBUG, &t11, "This is a T11 debug message"); // logging messages can then be retrieved from the log using predicates, of which // a number of simple examples I've already created. For now, I will just write // all logged messages to the screen. const log_type::entry_list& all = mylog.get_all_entries(); for_each(all.begin(), all.end(), entry_printer<log_type::entry>(cerr)); } The above program causes the output: 2005-Jan-28 19:08:21.464715: Anonymous, L:4 - This is an anonomous log entry 2005-Jan-28 19:08:21.464715: Anonymous, L:4 - main is logging a warning 2005-Jan-28 19:08:21.464715: struct test1, L:3 - This is an info entry 2005-Jan-28 19:08:21.464715: Call me T11, L:7 - This is the fatal error text I am thinking about requiring a conversion from levels to string reps in the policy class but have not implemented that. Thanks, John Jason Hise wrote:
You may want to make your logging levels a policy which defaults to an enumeration of your currently listed logging levels. This way, if someone has a need for a logger with other levels of output they can just specify a different enumeration for the policy. Instead of having separate logging functions (log, warn, info) you could have one which accepts a value of the enumeration's type as the first param. The logging level could be a member of this enumeration as well, so a simple less-than comparison could determine whether or not to output the message.
-Jason
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

I like what you've done (code please?). I've added a few functors to John Torjo's logging effort and was actually going to try to put together another release of it since John is quite busy. One thing that has bugged me about the Torjo approach was the requirement for defining the logs in a separate translation unit. I'm not sure I like the idea of having to write code to dump the log messages though. I assume this is a Some important features that the Torjo library (I guess thats what we're calling it) offers that I hope yours also would are thread safety and a highly customizable output format. I believe a convenience interface (almost necessarily macro-based) that takes ostream<< style messages and is also optimized to *not* go as far as actually formatting the log message (or creating a Boost.Format object if thats the route you go) if the message wouldn't be logged (e.g. due to policy, log disabled or too-low a level). This was avidly debated on the list and I think the consensus that this was a must-have feature. I look forward to a reveived Boost.Log discussion. -- Caleb Epstein caleb dot epstein at gmail dot com

Caleb Epstein wrote:
I like what you've done (code please?). I've added a few functors to John Torjo's logging effort and was actually going to try to put
Great!
together another release of it since John is quite busy.
Busy indeed. I hope to be able to do another update within a month or so, and then push it for review.
One thing that has bugged me about the Torjo approach was the requirement for defining the logs in a separate translation unit.
I am always open to other suggestions. What I'd like to do (when I'll have the time) is to have an extra layer over the logging library, which would allow configuring the logs in a configuration file or so. Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- http://www.torjo.com/cb/ - Click, Build, Run!

On Fri, 28 Jan 2005 15:28:31 -0500, John Eddy wrote:
Thank you for pointing me to that. My implementation is quite different but I like many features of the Torjo library. Some of the major differences between the Torjo library and mine include: --- CUT ---
Something else you might want to consider is different logging mechanisms. I have written a logging scheme myself, though it is not char specific (so it will support unicode logs), and it is based off boost::format to do the log formatting. The reason for this is that I have applications that need to support different languages, and the ordering of variables can be different for different languages (think polish or something :P). I implemented a callback mechanism that will be used when a FATAL message is received (often people will want to do an exit(-1) or something in there, who knows). I also added a hex logging facility - to do hex dumps for you, which is exceedingly useful. Plus I've implemented the logger as an interface, and have written several 'back-ends', including: "simple": where you specify a std::ostream for log messages to go to, as well as being able to specify the log output format. "file": where you specify a file (using a file class of my own design) for output, where you can not only specify the log output format, but how many 'backup' logs to keep (think blah.log, blah.log.0, blah.log.1, etc), how big a log can get before it is cycled, and a command that should be executed post-cycling (eg. gzip -9). "net": where you specify a socket (again, using a class of my own design) that all logs will be sent to (ala. netcat), as well as the format of the log message. "syslog": where you specify the UNIX syslog facility, and it will send logs to IT. I'm yet to do "eventlog" for the NT event logger, but you get the idea. If you're going to submit something to boost (I'm all for it, btw), it might be an idea to be able to make the logger extensible in the manner I have described above - ie. allowing you to specify different logging mechanisms and supporting unicode. Though, I DO like the idea of being able to use variable log names. If you want to look at my logging mechanism, you can see it here: http://www.neuromancy.net/viewcvs/Mantra-I/include/mantra/log/?root=mantra The interface file specifically is here: http://www.neuromancy.net/viewcvs/Mantra-I/include/mantra/log/interface.h?root=mantra&rev=1.8&view=auto Thanks, -- PreZ :) Founder. The Neuromancy Society (http://www.neuromancy.net)

On Fri, 28 Jan 2005 23:02:09 -0500, Preston A. Elder <prez@neuromancy.net> wrote:
On Fri, 28 Jan 2005 15:28:31 -0500, John Eddy wrote:
Thank you for pointing me to that. My implementation is quite different but I like many features of the Torjo library. Some of the major differences between the Torjo library and mine include: --- CUT ---
Something else you might want to consider is different logging mechanisms.
I have written a logging scheme myself, though it is not char specific (so it will support unicode logs), and it is based off boost::format to do the log formatting. The reason for this is that I have applications that need to support different languages, and the ordering of variables can be different for different languages (think polish or something :P).
The usage of Boost.Format is a nice touch, but I suspect some folks would prefer just simple strings or an ostream-style interface.
I implemented a callback mechanism that will be used when a FATAL message is received (often people will want to do an exit(-1) or something
This is a nice idea.
in there, who knows). I also added a hex logging facility - to do hex dumps for you, which is exceedingly useful.
Yes. This can be quite handy.
Plus I've implemented the logger as an interface, and have written several 'back-ends', including: "simple": where you specify a std::ostream for log messages to go to, as well as being able to specify the log output format. "file": where you specify a file (using a file class of my own design) for output, where you can not only specify the log output format, but how many 'backup' logs to keep (think blah.log, blah.log.0, blah.log.1, etc), how big a log can get before it is cycled, and a command that should be executed post-cycling (eg. gzip -9).
All
"net": where you specify a socket (again, using a class of my own design) that all logs will be sent to (ala. netcat), as well as the format of the log message. "syslog": where you specify the UNIX syslog facility, and it will send logs to IT. I'm yet to do "eventlog" for the NT event logger, but you get the idea.
If you're going to submit something to boost (I'm all for it, btw), it might be an idea to be able to make the logger extensible in the manner I have described above - ie. allowing you to specify different logging mechanisms and supporting unicode.
Though, I DO like the idea of being able to use variable log names.
Meaning the logging level names?
If you want to look at my logging mechanism, you can see it here: http://www.neuromancy.net/viewcvs/Mantra-I/include/mantra/log/?root=mantra
The interface file specifically is here: http://www.neuromancy.net/viewcvs/Mantra-I/include/mantra/log/interface.h?root=mantra&rev=1.8&view=auto
I like a number of the features of this interface, especially the regex replacement of tokens <date:>, <level:> etc in file_impl.cpp. The auto-rotation is also desirable. Can you comment on the thread safety (or lack thereof) of the Mantra log class? E.g. could multiple threads be writing to the same log simultaneously without messages getting smashed together? Here are some add-ons to John Torjo's Logging library I wrote that implement a size-based rotating log like yours as well as a date-based one. If you want to archive data on a daily (or other) basis, this can be useful. They also allow a callback function to determine the log name, so users can implement custom filename policies. http://lists.boost.org/MailArchives/boost/msg74762.php -- Caleb Epstein caleb dot epstein at gmail dot com

On Sat, 29 Jan 2005 00:19:49 -0500, Caleb Epstein wrote:
Meaning the logging level names? No, the logging levels.
With my logger, you're locked into Fatal, Critical, Error, Warning, Notice, Info and Debug as your log levels. If you wanted a few more of them in between or to call them something different (in the code), you couldn't. As far as the textual representation printed in the logs, that is definable in my logger already.
Can you comment on the thread safety (or lack thereof) of the Mantra log class? E.g. could multiple threads be writing to the same log simultaneously without messages getting smashed together?
This is dependant on the backend. If you look in simple_impl.h, you will see the line: boost::mutex::scoped_lock scoped_lock(writelock); right before its going to write the lines it has just gotten back from regex. The same exists for the file (in the Write function that takes a vector of log entries) and net interfaces. It does not exist for syslog, mainly because syslog takes care of not mushing lines together so I don't have to. So it should be completely thread safe :)
Here are some add-ons to John Torjo's Logging library I wrote that implement a size-based rotating log like yours as well as a date-based one. If you want to archive data on a daily (or other) basis, this can be useful. They also allow a callback function to determine the log name, so users can implement custom filename policies.
Thanks, I'll certainly take a look, since I'd been thinking about doing that too, but trying to make it a sane mechanism made my head hurt ;) -- PreZ :) Founder. The Neuromancy Society (http://www.neuromancy.net)

On Sat, 29 Jan 2005 01:31:30 -0500, Preston A. Elder <prez@neuromancy.net> wrote:
The same exists for the file (in the Write function that takes a vector of log entries) and net interfaces. It does not exist for syslog, mainly because syslog takes care of not mushing lines together so I don't have to.
So it should be completely thread safe :)
Great.
Here are some add-ons to John Torjo's Logging library I wrote that implement a size-based rotating log like yours as well as a date-based one. If you want to archive data on a daily (or other) basis, this can be useful. They also allow a callback function to determine the log name, so users can implement custom filename policies.
Thanks, I'll certainly take a look, since I'd been thinking about doing that too, but trying to make it a sane mechanism made my head hurt ;)
Sorry that was the wrong link. Here's the updated version that includes the timed rollovers http://article.gmane.org/gmane.comp.lib.boost.devel/113491 -- Caleb Epstein caleb dot epstein at gmail dot com

Jonathan Turkanis wrote:
John Eddy wrote:
Would anyone be interested in a library for doing logging?
Yes.
Ditto. It's much overdue.
You might want to describe briefly how your library compares to existing libraries, e.g., to this one by John Torjo
http://lists.boost.org/MailArchives/boost/msg73854.php
which he may eventually propose for review.
I'd like to know how both these libraries compare to libcwd (libcwd.sourceforge.net). Apparently there was a thread about Boost and libcwd some time ago but the archive seems incomplete and I was not around at the time to participate. I'm considering using libcwd for a project and am interested to know what came up during the Boost discussion of it. -Dave

Here is a bullet point run-down of what I take to be the salient features of libcwd, John Torjo's Proposed Boost.Log library, and what I can glean from skimming Preston's "Mantra" Logger implementations. These lists are by no means complete but I hope this helps to move the discussion along. libcwd (see http://libcwd.sourceforge.net/www/features.html): * Part of a larger, mature library that provides logging, memory allocation tracing and other debugging facilities. * Thread-safe. Provides a separate libcwd_r for use in MT apps. * Uses ostreams for formatting * Supports "severity" levels on each message. * Suggested API is macro-based. All logging code can thereby be excluded from a build by ensuring the macro CWDEBUG is not defined. * Output divided up into channels; user can create any number of new namespace-protected channels; somewhat tortured syntax for doing so (they must be in a namespace called "dc" nested inside a user-defined namespace) * Output directed to channel objects by passing them to a macro (e.g. Dout (dc::notice, ...)) Torjo (need gmane URLs) * Uses ostreams to format messages * No concept of a message severity or level (suggested to use different Log instances) * Messages are directed to Logs. Logs have Modifiers and Appenders associated with them. The set of Modifiers and Appenders in use is up to the user. * Modifiers are functors that take Messages and alter them (e.g. prepend a timestamp, thread ID, append a newline, etc.). * Appenders are functors that take the modified messages and do something with them (e.g. write them to a file, syslog, etc). * Logs must be declared in a header, defined in a different TU. Helper macros provided to assist with this [this is the one feature I most dislike] * Log names may use namespace::name style * "Appenders" are used to direct log messages to a file/device/etc. Appenders are associated with one or more logs using a wild-cardable string name * Includes several Appender implementations including file, Win32 debug window, size/date-based rolling logs * Output directed to Log objects by passing them to BOOST_LOG macro (e.g. BOOST_LOG(ldebug) << ostream stuff) Mantra (http://www.neuromancy.net/viewcvs/Mantra-I/include/mantra/log/?root=mantra) * Part of a larger library ("Mantra") that provides networking, file operations, persistence, and a bunch ofther stuff I'm sure I missed. Much of this is built using Boost facilities. The Logger portion does not seem to be tightly coupled to the rest. * Logger paramaterized on charT type. Allows for wide-character output. * Some of the Logger implementations depend on Boost.Thread to provide MT safety * Uses Boost.Format for message formatting and also provides a hex-dump output method. * Messages include a "severity" level; Loggers are set to emit messages of a specific severity or greater. Severity levels not pluggable ATM. * Provides a special hook for use when a Fatal message is logged. * Includes several Logger implementations including file (with automatic rollover based on size), socket, syslog * No helper macros provided for "performance optimized" logging (e.g. not formatting messages which won't get logged; compiling-out logging code). This is surely easy to add however. * Output directed to Loggers using logger.Write (LogLevel, boost::format object) -- Caleb Epstein caleb dot epstein at gmail dot com

Just a couple of additions to your summary re the Torjo lib... Caleb Epstein <caleb.epstein <at> gmail.com> writes:
Torjo (need gmane URLs)
The code + docs is at http://www.torjo.com/code/logging-v131.zip, search for "logging lib, v1.3" to find subsequent discussion, patches on gmane.
* No concept of a message severity or level (suggested to use different Log instances)
It is worth pointing out that this is a feature - not an omission.
* Logs must be declared in a header, defined in a different TU. Helper macros provided to assist with this [this is the one feature I most dislike]
* But 1.3 included scoped logs, so if you want to create logs dynamically at runtime you can. These logs don't need the declare/define. * A disabled log has almost 0 overhead (a simple comparison operation only) * Can effectively compile out logging using a dummy BOOST_LOG macro (generating something like: if (1) ; else nullstream << stuff) * Addresses problem of logging from eg. static object c'tors that may run before logging system can be fully set up. * Configurable (compile-time), minimal interface (just needs some form of mutex) thread safety. * For what it is worth, I think the fatal handler (or other interesting behaviours triggered by particular logs being written to) can be implemented by making an appender that is fatal, and directing "fatal" logs to it (as well as to other appenders, of course the fatal appender must be the last one). Regards Darryl.

* No concept of a message severity or level (suggested to use different Log instances)
It is worth pointing out that this is a feature - not an omission.
Indeed so!
* Logs must be declared in a header, defined in a different TU. Helper macros provided to assist with this [this is the one feature I most dislike]
* But 1.3 included scoped logs, so if you want to create logs dynamically at runtime you can. These logs don't need the declare/define.
Yup
[...] * For what it is worth, I think the fatal handler (or other interesting behaviours triggered by particular logs being written to) can be implemented by making an appender that is fatal, and directing "fatal" logs to it (as well as to other appenders, of course the fatal appender must be the last one).
Again, true. Many thanks for pointing these out! Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -- http://www.torjo.com/cb/ - Click, Build, Run!
participants (8)
-
Caleb Epstein
-
Darryl Green
-
David A. Greene
-
Jason Hise
-
John Eddy
-
John Torjo
-
Jonathan Turkanis
-
Preston A. Elder