Den här websidan ser mycket bättre ut i en bläddrare som följer rådande webstandard, men du kan titta på sidan med andra bläddrare också.

Bluefish :: Akvariet

Bluefish logo
Logo

Java Server Faces

Java Server Faces och enhetstester

Java Server Faces och Hibernate

Hibernate och JBoss

Lunar Linux and Zepto 4200

Att delta i projekt med öppen källkod

Java-projekten i Apache

Hibernate - en introduktion

HemsideByggaren under huven

Öppen källkod på Bluefish

JavaOne 2003

Lime

OOPSLA 2002

AOP

fixafest.nu under huven

Linux From Scratch

Bluefish skänker

XSL

JDepper

OOPSLA 1999

Lime and Views on a General Logging Mechanism

Published September 11th, 2002

This article contains both a background of Lime, a C++ logging and messaging library, and an article about my views on what properties a general logging mechanism should have.


Logging is an issue that has captured my interest since 1998. Back then, I was to design a few classes in a project to handle some basic logging functionality. At the time, I could not find any free, nor proprietary, log libraries, which lead me to develop my own library, Lime.

My philosophy, more or less, has always been not to invent the "wheel" again. So I started out with Lime looking at common and traditional practices from other log tools that I've seen and experienced. Although some of them are fine, I also ran into a few issues and problems with the traditional view of logging that I wasn't too happy with.

For instance, common practice is usually to have a few static levels, e.g. debug, message, warning, and error. But this feels somewhat constrained and leads to inflexibility. Instead, I didn't want any restrictions on the number of log levels, or which log levels are possible. In this way, it is possible to have different log levels in different programs or within a program. Another example was that I really wanted the log statements to be type safe. Many log functions use sprintf format strings to create the actual log message, which isn't type safe. In C++, not having type safety is like driving a motor cycle without a helmet; it's stupid and if you don't know exactly what you're doing, you're up the infamous creek without a paddle, or something. Anyway, having type safety the compiler will help me finding errors before running the program, which isn't a bad thing.

There are a number of other issues as well, which I will discuss later in this paper. But first I will give some information about Lime.

Lime

Lime has evolved over the years to become a flexible, type safe, easy to use, extensible, competent, and fairly efficient C++ library for logging and messaging.

Quick links:

Lime is currently only a programming library. It does not include any applications or executables for displaying and searching logs, monitoring a system, etc. If time permits, these kinds of applications will be developed and released with Lime.

I have released Lime's source code under a Boost license, which means that you are free to use the code anyway you like as long as the copyright notice is preserved. I'm only releasing Lime's sources though, but compiling them shouldn't be a biggy. If there is a problem, let me know, and I'll see what I can do to help.

Lime should be ISO C++ compliant. If not, please let me know. I've tested Lime under Redhat Linux 7.3 and Windows/Visual C++ 6.0. The source code is documented with doxygen style comments. Doxygen is a free, javadoc like program. Here is the doxygen generated Windows help file of the Reference Manual and User's Guide.

Properties of a General Logging Mechanism

The rest of the article gives my view of what properties a logging mechanism should have and a little bit about how Lime supports those properties. If you want to know more about how Lime works and is used, please read the Lime Reference Manual and User's Guide. Note that Lime is implemented in C++ and that some of the discussions below uses C++ concepts.

In my opinion, generic logging mechanisms should possess the following properties (not prioritized):

  1. Safety - A log API should be fail safe
  2. Efficiency - A log call should incur minimal run-time overhead
  3. Simplicity - A log statement should only require one line of code
  4. Configurable and Flexible - Including turning logging on and off, arbitrary severity/log levels, and more
  5. Availability - Logging should be available during the entire execution of a program
  6. Extendable - It should be easy to extend a log framework
  7. Tools - Administrators should have tools to help search and find in logs
  8. Log Message Definitions - It should be possible to define a log message at a different location from where it is used
  9. Multiple destinations - A logging tool should provide abundant log destinations such as files, monitors, IDE, databases etc
  10. Division of Area of Concern - Issuing a log statement should be independent of deciding where it is stored
  11. Coexistence - A log tool should be able to co-exist and ease transitions from other log tools
  12. A log tool should support all phases of a project, i.e. development, testing and production
  13. Actual logs should be easy to access, be structured and easy to search

Safety

One of the most important aspects of logging is that it should be safe to issue a log statement. It should absolutely not cause the program to fail in any way. This property is of course quite obvious, but in some languages and implementations, this is not so. In C/C++ I've seen many implementations that are not type safe, that uses sprintf for instance at the API level. The last thing I want to happen is for the logging mechanism to cause a core dump because the sprintf's format string is incorrect. Note that a sprintf format string error is not caught by the compiler, but will be at best, noticed during run-time. A type safe logging API in C++, such as Lime, will catch such errors during compile time.

Lime uses the (C++) stream operator, <<, to define log messages. Note that this is a type safe mechanism but that it requires the stream operator to be defined for those objects that are part of the log message. Here is a code snippet just to illustrate what I just said,

logContext << "Text with the integer " << 42 << ".";

(In Lime, log messages are sent to log context objects, which are also configured with information about where to send the log message, if the message is to be logged etc.) For the curious, please see the Lime Reference Manual for further details of how Lime is implemented.

Furthermore, in general I don't think that a log mechanism should throw an exception if it fails internally, unless one has very specific business reasons for it. I mean, what should one do if the log call fails? Trying to log the failure is obviously doomed to fail. Alerting the administrator would be preferred, but if the log mechanism is used for precisely that, then we're back to square one.

Efficiency

A log mechanism must of course be efficient, or otherwise no one would want to use it in production code. One important aspect of this property is that when logging is turned off, a log statement should basically incur no run-time execution overhead.

Lime uses, at the API level, C++ macros to help the programmer achieve minimal run-time overhead when logging is turned off. It isn't necessary to use these macros in Lime, but is recommended since it reduces the amount code needed to write. Lime is fairly efficient when logging. Almost all of the run-time cost is associated with the particular medium, or destination, that the log is sent to, which may be C++ standard iostreams, files, databases, etc.

Simplicity

It is essential for the developer that writing log code is simple and is not seen as a burden. Again, I've seen logging APIs that requires more than one statement in order to issue a log statement. In my opinion, anything more than one line of code, or statement, is too much. Obviously, one may still need to write, in C++ or Java, some include or import statement. But the point here is that it shouldn't require more than one line of code where the log statement is issued. Anything more than that will discourage many developers from writing log statements at all, or only where it is absolutely necessary.

In Lime, apart from configuring the library during startup, all that is required for a log call, is one statement.

LOG( aLogContext,
     "A log with two params " << 4711 << aObject );

There is only one drawback with this approach (if one wants to call it that), which is that each object that takes part in the log message, needs to have an operator << defined. But my experience in the C++ world is that it is always helpful to have one anyway for debugging purposes.

Configurable and Flexible

It is important to be able to configure the log mechanism in order to suite the system's needs, and this should be available both during compile- and run-time. This includes but is not limited to,

  • Arbitrary log or severity levels
  • Turning logging on and off
  • Determining where log messages are sent
  • Time stamp format

First I would like to say that a logging framework should support arbitrary log levels since every project and organization is unique and therefore requires its own set of log levels. However, most projects that I've been involved in, which have been information systems, have only had the need for four levels: debug, information/message, warning, and error/alarm.

Secondly, the term log level is often used to mean two things. One is the severity of the log message and the other is to control which messages are logged. I think it helps to separate the two from each other. A log message should always have an attached severity level (or log level if you prefer that terminology). It tells the operator/administrator how serious the problem is and if it needs immediate attention. But determining which messages are logged, shouldn't solely be determined by a message's severity (log level). Quite often, one wants a specific subsystem to be more closely monitored, while others are completely turned off. The point is that the logging mechanism should support a flexible way of controlling what is logged. The granularity should be finer than only using log levels.

Lime does this by a concept called log context objects. A log message is sent to a log context object for logging and the log context object has an associated severity (log level). When the message is logged, it is marked by the severity of the context. Each log context object can be turned on or off independently of its log level. This makes it very flexible to control what is logged. However, Lime also supports turning on and off all log contexts of a certain severity. That way, Lime also supports the traditional way of turning logging on and off.

The logging mechanism should also be highly configurable with regard to where a log message is sent. A single log call should be able to generate multiple log writes to several destinations (a destination can be a file, database, monitor, etc). By using one level of indirection between a log call and where that log message is sent, it is easy to achieve this property. In Lime, one does this by attaching destinations to log context objects. For instance, a log context, which alarms are logged to, may have a file, database, and monitor destination attached to it, while the developers trace log context has an IDE (e.g. Visual Studio) and a file destination.

However, remember that the cost of flexibility may sometimes be complexity. When there are too many options available, it can be a bit confusing.

Availability

In some programming languages, such as C++, doing stuff before main() may be problematic since the order of initialization of object files is undefined. For instance, a static object may want to log information during initialization, which happens before main(), and should of course be able to do so. Lime supports this (in C++) using the nifty counter technique, the same technique that standard in uses, which means that it is guaranteed to be initialized before first use. In general, the logging mechanisms should be available during the whole lifetime of a program's execution.

Extendibility

The logging mechanism should be easy to extend, modify, and adapt to the specific needs of the organization and project. In my experience, everybody has a slightly different view on what logging is, which makes it all the more important to be able to extend the logging mechanisms. Lime does so by being very flexible and having well defined interfaces and letting the developers have access to the source code to modify it as they see fit.

Tools

A complete logging library should also provide a set of tools that can be used to view and filter log contents, search and query logs. Unfortunately, Lime does not currently come with a set of tools for these needs. The administrator, tester, developer, or whoever needs to analyze the logs, is left at the mercy of using grep, awk, notepad or whatever tools that are at their disposal.

Log Message Definitions

In my opinion, a logging library should have programming constructs for predefining log messages. One may want to have all log messages collected in one or a few places, instead of littering throughout the whole program. This helps keeping track of all log messages as well as changing them if need be.

Further on, this construct should have an identification for each message and a definition that includes the messages' parameters and their type (for typed languages). Trying to instantiate a predefined message (i.e. use and send it) with the wrong parameters should definitely result in compile-time errors (or run-time if a non static typed language is used). This issue is related to the safety issue mentioned above. Using the type system to catch errors early is essential in my view to avoid unwanted run-time problems.

Having an identity of each log message could let an operator modify the log message to better reflect the problem. Furthermore, a database for different languages can be built, bug reports etc can be tagged with the id of associated log messages etc.

Lime supports the use of predefined log messages by using simple classes that the programmer instantiates to objects (log messages) when used.

Multiple Destinations

(This property has already been mentioned in the discussion of the Configurable and Flexible property.) For a logging library to be useful, it should provide an abundant number of destinations, or mediums, to log to. A log destination, in my vocabulary, is where a log message is finally stored, such as a file. A destination can also be a monitor that displays a system's log messages. Since every organization has its own preferences, the library should be easy to extend with tailored destinations.

Lime's strength lies in the ability to easily write your own log destination. It does have a number of predefined destinations, but chances are that you want a specialized destination. The only thing that you need to do is to implement a simple interface.

Division of Area of Concern

As I see it, there are basically two main concerns when writing log code; what should be logged and where it should be logged. Although they are related, the tasks should be divided. When deciding upon what to log, the designer/programmer shouldn't have to worry where the messages are sent, just know that they will arrive to the right place. Likewise when deciding where the log messages are stored, the designer/administrator should only have to decide upon where certain kind of messages are stored, or sent. Deciding where, is part of configuring the log mechanism, while writing log statements, is more about using the log functionality.

This separation should also be evident in the code. Deciding where messages are stored at the same place as writing log statements leads to static structures. The separation makes it easier to understand, maintain and modify the code. It especially makes it easier to change where messages are stored. Lime is designed be as flexible as possible, and thus, supports this separation.

Coexistence

A log tool should have mechanisms for co-existing with other log tools. Quite often there is already a logging tool used in the organization, but a tool that perhaps doesn't meet the requirements any longer. If, for whatever reason, the organization decides to introduce a new tool, it obviously helps if the new tool can interface with the old log system somehow. This can be anywhere from logging to the same destinations (files etc) to be able to read, query and display the old logs with the new tools, and more.

Lime's support to this problem is by defining a new destination class that logs to the old system. This means that one has access to Lime's API, but still relies on the old system's destinations and tools for accessing the logs, or a combination thereof.

Development, Test, and Production Support

A log tool should not only support the administrators of the system, but also the developers and testers. What I'm trying to say is that the tool should have mechanisms for helping the developers and testers finding problems in the code. For example, tracing (logging) each function/method entered and exited may help the developer trace the execution when testing their code. Another example is outputting the log directly into the IDE of the developer and this without changing the production code.

Logs

Obviously the logs should be structured, easy to search, and accessible. This is not so much a property of the logging mechanism itself, but rather of how it is used and configured.


/Richard Glanmark

Bluefish logo

Bluefish AB :: Sturegatan 34 :: 114 36 Stockholm :: tel 08 459 93 30 :: fax 08 459 93 39 :: e-post info@bluefish.se