Flogger B.1
Anders Sundman
Copyright © 2006 Anders Sundman
Distributed under the GNU Lesser General Public License, Version
2.1. (See accompanying file LICENSE.txt or copy at
http://www.gnu.org/licenses/lgpl.html)
Table of Contents
Flogger is a general purpose C++ logging library. The primary design goals
of the library are ease of use and ease of extendibility (e.g. custom log
destinations or custom log levels).
A sane reaction to flogger is to ask the question: Does the world
really need another logging library? The short answer is: yes. The
slightly longer answer is: yes, because there are no other free, open
source, platform independent, easy to use and flexible logging
libraries out there.
Here follows a brief overview of the features present in the flogger library.
- Easy to get started with + plenty of examples.
- Minimal typing required for vanilla usage.
- Thread safe or not? You decide-two versions are available.
- Logging in the calling thread or in a dedicated logging thread are both supported.
- Platform independence. Flogger works on Windows, Mac OS X, Linux and more.
- Many log writers available (console, file, etc.) and more are coming.
- Source is free and open. It won't cost you a dime and you know what you get.
- Context sensitive logging is supported (but not required).
- Plugable log entry formatters selects how the log entries should be output.
- Custom log levels are easy to add and use.
To build the flogger library, the cross platform build system CMake is
used. Please refer to
http://www.cmake.org for documentation on how to use and install
CMake.
Specify the
flogger-dir/build directory as the target
for an out of source build. The CMake source directory is
flogger-dir/src
The "install" target builds all components
and moves them to their proper place in the distribution tree. When finished, the flogger library should be created and installed in
flogger-dir/lib and the examples should be installed in
flogger-dir/bin.
When building flogger with CMake, there are a number of options that
can be set. These options determine the way the flogger library will
operate.
FLOGGER_IMPL_BG This CMake option selects whether to build the
library with support for foreground or background logging. If this
option is set to ON, all logging will be performed by a dedicated
logging thread. If it is OFF all logging is performed by the calling
thread.
FLOGGER_RUNTIME_MT If this CMake option is set to ON, a thread
safe implementation of the library will be build. If OFF, the library
is not thread safe. (It is still safe to use background logging with
FLOGGER_MT OFF).
BOOST_DECORATION The Boost library files will have different
file names depending on what tool was used to build it. The files will
be suffixed by "-[toolset]-[run time]-[arc]-etc.". Usually flogger can
guess a good value for this, but it's not correct; please specify the
string suitable for your version of the Boost library.
Some components of the flogger library are only available on certain
platforms.
The OutputDebugString writer and the example demonstrating
it's use is only available on Windows and will only be build if the
windows.h header file is found.
The optional process ID (PID) field in log entries is demonstrated in
the Formatter example. The PID field is only available on Posix (Unix,
Linux, Free BSD, Mac OS X, etc.) systems and on Windows systems.
To build the flogger library, the cross platform build system CMake is
used. Please refer to
http://www.cmake.org for documentation on how to use and install
CMake.
The flogger library depends on the Boost libraries smart_ptr,
date_time and boost_thread (if creating a multi-threaded
library). Please refer to
http://www.boost.org for documentation on how to use and install
the Boost libraries.
A typical use of the library performs four basic steps.
- Create logger
- Register log writers
- Log messages
- Destroy logger
Steps 1 and 2 are done during program initialization, typically very
early. Step 3 is performed repeatedly through out the entire execution
of the application. Finally, step 4 is preformed at application
termination.
Here is some sample code to demonstrate the four steps:
// Application startup, create a logger
logger_factory::create_logger();
shared_ptr<logger> lgr(logger_factory::get_logger());
// And register a log writer
shared_ptr<file_log_writer> flw(new file_log_writer("log.txt"), log_level::debug);
lgr->register_log_writer(flw);
// During program execution, log messages
log(log_level::debug, std::string("This is a debug log entry!"));
// At program termination, destroy logger
logger_factory::destroy_logger();
Log levels and log writers are two central concepts that are explained
in greater detail in the following sections.
The log level determines the severity of the log message. The
pre-defined (in log_level.hpp) levels are: finest, debug, info,
warning, error and fatal. Debug and info are messages that does not
indicate an error; debug level is suitable for messages directed at
developers and the info level for messages directed at the user. The
fatal message is used for errors that will result in program
termination.
It is straight forward to add additional levels by adding more
log_level_t constants to the log_level namespace, e.g. to get a
verbose_debug level. Please refer to the example
Adding Log Levels for details on
how to do this.
Every log writer has a minimum log level value and will not log any
messages below that level. During development it is convenient to set
this level to finest (to log all messages) and to raise the level (to
info) in production code.
The following code will log both messages to the file, but only the
warning message to the console:
// Create two log writers with different log levels
shared_ptr<file_log_writer> flw(new file_log_writer("log.txt"), log_level::debug);
shared_ptr<ostream_log_writer> olw(new ostream_log_writer(log_level::info);
// Register log writers
lgr->register_log_writer(flw);
lgr->register_log_writer(olw);
// During program execution, log messages
log(log_level::debug, std::string("This is a debug message!"));
log(log_level::warning, std::string("This is a warning!"));
A log writer is a component that processes a log entry sink
(e.g. writing it to a file). There are a number of different log
writers that write log messages to files, console, etc. and several
writers can be used at once.
Log writers are registered with a logger. The logger is the core
logging engine. Once a log writer has been registered, the logger will
see to that it get all log entries.
A nice thing is that different log writers can work with different log
levels. Every writers has a minimum log level, and will only process
log entries above that level. Typically, one will register a file log
writer (file_log_writer) with a low minimum log level (eg. debug) to
get detailed output to a file, and perhaps an output stream log writer
(ostream_log_writer) with a higher minimum log level (eg. warning) to
get less verbose output to an ostream (e.g. std::cerr).
The basic idea is that the logger maintains a state (a context) in a stack.
Each time a log entry is written the context is displayed together with the
entry. New sub-contexts can be added or removed from the logger by pushing
or popping them on and off the context stack.
The following snippet demonstrates the use of a context:
push_context("Network Comm");
// Write a log entry in the current 'Network Comm' context
log(log_level::error, "Socket is busy, can not connect");
pop_context();
The log writers can be configured to only process log entries that are generated
from a specific context (or any of it's sub-contexts).
For example, it is possible to get the network communication, gui and
application logic log entries in three different files. Simply use
three contexts (e.g. "comm", "gui" & "sys") and three different log
writers that listen to one context each.
The following code snippet creates a log writer that will only show
log messages that were sent while the logger was in the "Network Comm"
context.
// Context output must be explicitly enabled in the formatter
shared_ptr<string_formatter> formatter(new single_line_formatter(true, true, true));
// Create a log writer with the context "Network Comm"
shared_ptr<ostream_log_writer> olw_cout(new ostream_log_writer(std::cout, formatter));
// This enables the context filter for the writer
olw_cout->set_context("Network Comm");
The following sections presents a number of examples that demonstrates
different uses of the flogger library.
File: src/examples/minimal
This is the absolute minimal example. Take a look at this first. It
creates a logger, writes a log entry to the console and then destroys
the logger.
File: src/examples/logfile
This example will write log entries to files. Two file writers are
created with different logging levels.
File: src/examples/customlevel
Demonstrates how one can create a new (custom) logging level. A
fine_debug level is created that is finer than the build in debug
level.
File: src/examples/outputdebugstring
Only available on W32 versions. This application is a minimal app that
writes a log entry to the windows OputputDebugString. To see the
result of this logging you need some kind of debugger, e.g.
DebugView
from SysInternals.
File: src/examples/formatter
Formatters are used to format log entries in some of the common log
writers (e.g. the ostream writer and the file writer). They determine
how the log entries are output. This example demonstrates the use of
the two built in formatters, the single line formatter and the multi
line formatter.
File: src/examples/context
This example demonstrates context sensitive logging.
This section contains information about the design of the flogger
library. This information is useful when developing or extending the
library.
This image shows how some of the core logger classes are related.
A note on the create_logger/destroy_logger design. The rationale for
this design (instead of e.g. a singleton with static create/destroy) is
that there might be resources needed by the logger that are not
available during the static initialization and destruction
(e.g. std::cerr). Without explicit creation/destruction there is no way
to guarantee that the logger is created after the resource and
destroyed before it.
The default implementation of the log function is sequential, ie. it
is the calling thread that also performs all the log writing. By
defining the macro LOGGER_MULTI_THREADED_IMPL and recompiling the
library, a multi threaded implementation of the logging is
selected. This implementation uses a dedicated thread to write the log
entries - the calling thread simply adds them to a queue. See
logger_impl_fg.hpp and logger_impl_mt.hpp for the respective
implementations.
Current version is B.1 (beta release). Release dates for future
versions have not yet been set. But work is on the way of a 1.0
release.
Alpha release (versioned A.x), Project and infrastructure improvements
- Fix bugs in CMake build script
- Verify compilation on Windows XP (cygwin & Visual Studio), Mac OS X, Linux.
- Setup project at sourceforge.net
- Verify that homepage looks ok with Firefox, Explorer and Opera
- Add homepage to flogger.sourceforge.net page
- Update documentation to reflect alpha version
- Release a src package
Beta release (versioned B.x), Context logging & Code maturity
- Modify build script to build all versions of the library each time.
- Develop context sensitive logging
- Develop optional PID logging
- Test & review of code
- Update documentation to reflect beta version
- Release a src package
Production release (versioned 1.x), Stability and field testing
- Update CMake script to better reflect CMake >= 2.4
- Thorough testing & Code review
- Update documentation to reflect 1.x version
- Release a src package
- Release bin packages for W32 (cygwin, visual studio), *nix (gcc)
Future releases (versioned => 2.x), More log writers
- Add Syslogd log writer (for *nix systems) + example
- Add mySQL log writer + example
- Add postgreSQL log writer + example
- Add a network log writer (ACE or sockets?) + example
- Release a src package
- Release bin packages for W32 (cygwin, visual studio), *nix (gcc)