Log4r Manual
 

Table of Contents


Scope of this Manual

Most of the documentation resides in the Log4r RDoc API, so this manual will be brief and targeted to people starting to learn about Log4r or who want to see what Log4r has to offer.

Of particular importance is the section called The Art of Logging which provides tricks and tips in using Log4r efficiently.

Click on the section title to go back to the Table of Contents at any time.


What Log4r Is

Log4r is a comprehensive and flexible logging library written in Ruby for use in Ruby programs. It features a heiarchial logging system of any number of levels, custom level names, logger inheritance, multiple output destinations, execution tracing, custom formatting, thread safteyness and more.

Log4r intends to be easy to use and configure, no matter the complexity. Casual system scripts can use Log4r right away with minimal configuration and multi-platform distributed applications can set up a concise configuration in XML. Log4r comes with comprehensive documentation and plenty of examples. Furthermore, Log4r attempts to abide by the Principle of Least Surprise, such that it works as intended at all points.

While Log4r is interpreted, it attempts to achieve optimal performance and scale well. Already, plans are being made to write the performance-critical components as a C extension to Ruby.

Log4r was inspired by and provides much of the features of the Apache Log4j project, but is not a direct implementation or clone. Aside from superficial similarities, the projects are not related in any way and the code base is completely distinct. Log4r was developed without even looking at the Apache Log4j code.

Log4r is an Open Source project and intends to remain that way. The Log4r license is similar to the Ruby Language license. It resides on this page and in the distribution in a file named LICENSE.


But Why?

Why a logging system like Log4r? There are many non-obvious reasons. First, logging should not be confused with Unit testing. Unit testing and logging are complementary rather than exclusive. Second, logging provides a handy way for a program to provide useful information about itself to different audiences. With logging, your application's state can be tracked over time and not be a complete mystery. With Log4r, you can control the amount and type of information that show up without having to comment out the logging statements in your code. This helps reduce noise and maximize the usefulness of log files. Finally, logging is extremely useful in applications that are not all in one place (distributed). If you want to debug a client and server at the same time, a logging system like Log4r becomes critical.


Out of the Box

Here's an example of how to use Log4r right away.
require 'log4r'
include Log4r

# create a logger named 'mylog' that logs to stdout
mylog = Logger.new 'mylog'
mylog.outputters = Outputters.stdout

# Now we can log.
def do_log(log)
  log.debug "This is a message with level DEBUG"
  log.info "This is a message with level INFO"
  log.warn "This is a message with level WARN"
  log.error "This is a message with level ERROR"
  log.fatal "This is a message with level FATAL"
end
do_log(mylog)
The output will look something like this:
DEBUG mylog: This is a message with level DEBUG
 INFO mylog: This is a message with level INFO
 WARN mylog: This is a message with level WARN
ERROR mylog: This is a message with level ERROR
FATAL mylog: This is a message with level FATAL
If you only need one logger, you can use this one all throughout your program. The logger can be retrieved at any point.
mylog = Logger['mylog']        # Get our logger back
Now suppose you want to stop seeing DEBUG, INFO and WARN messages. That's easily accomplished. And at any time, you may reset the logging threshold for 'mylog':
mylog.level = ERROR
Running do_log(mylog) yields:
ERROR mylog: This is a message with level ERROR
FATAL mylog: This is a message with level FATAL
That's all one needs to know if all that's needed is one logger writing messages to stdout.


Overview

We will now go over the components of Log4r. A summary of each is provided, and links to the Log4r RDoc API are provided for further perusal.


Levels

Log4r uses a heiarchial system of logging. That is, certain log events can have a higher priority than other log events. Hence, one can control how much information one wants to log by adjusting the level threshold of logging. By default, the logging levels and priorities are:
DEBUG < INFO < WARN < ERROR < FATAL
In the previous section, we saw how setting the level to ERROR prevented messages with levels DEBUG, INFO and WARN from showing up. The names and numbers of these levels are configurable. You can have any number of levels and name them whatever you wish. The logging methods we saw in the last section will be named after the custom levels. Log4r adjusts itself to suit your needs.

To find out more about levels, please see rdoc/files/log4r_rb.html.


Loggers

The principle interface in Log4r is a Logger. Loggers have one logging method for each level and any number of output destinations. A logger's level threshold and output destinations may be changed dynamically. Loggers are stored within a Repository for retrieval at any time. Loggers provide all kinds of data for logging: the log message itself, the line number and file it was called in, a timestamp, the log priority, and so on.

Loggers can inherit other Loggers. Inheritance means that a Logger initially adopts the characteristics of its parent if none are specified. A Logger's level is inherited once, and a Logger will write to its parents output destinations as well as its own. This behavior is optional, but allows one to structure a powerful, and easily configurable logging system.

To find out more about loggers, please see rdoc/files/log4r/logger_rb.html.


Outputters

An output destination is represented by an Outputter object. An Outputter has a particular means of formatting data (Formatter) and has a level threshold of its own. Outputters, like Loggers, are stored in a repository and can be retrieved and manipulated at any time. Every outputter is thread-safe, meaning that multiple threads can log to the same Outputter without worrying about race condigions.

Five types of Outputters are provided: IOOutputter, StdoutOutputter, StderrOutputter, FileOutputter and EmailOutputter. Custom Outputters can be rolled up easily and configured in XML without any extra work.

To find out more about outputters, please see rdoc/files/log4r/outputter/outputter_rb.html.


Formatters

A Formatter is responsible for rendering a log message into an output format. Several Formatters are provided, including the powerful PatternFormatter. PatternFormatter uses sprintf-like directives to format log messages and eliminates the need for custom Formatters.

To find out more about formatters, please see rdoc/files/log4r/formatter/formatter_rb.html.
To find out more about PatternFormatter, please see rdoc/files/log4r/formatter/patternformatter_rb.html.


Configuration

Configuring Log4r is accomplished via the Configurator class. Configurator allows one to set custom levels and load up XML configurations. The XML grammar used by Log4r is extremely flexible and can accomodate the configuration of custom Outputters and Formatters with no extra work.

To find out more about configuration, please see rdoc/files/log4r/configurator_rb.html.


Remote Logging

It is possible to send log events from an Outputter to a Logger over a network. This is accomplished using the distributed Ruby library ROMP, a subclass of Logger called LogServer, and a RemoteOutputter.

To find out more about remote logging, please see rdoc/files/log4r/logserver_rb.html

Alternatively, one can just send log reports via email using EmailOutputter.

To find out more about EmailOutputter, please see rdoc/classes/Log4r/EmailOutputter.html


The Art of Logging

Log4r in itself does not automatically enable people to grok logging, however it does provide tools to assist in The Art of Logging. We will now cover some of the techniques in this art and how to use Log4r to accomplish them.


Avoiding Parameter Evaluation

Suppose we have a complex structure and don't have the time to make a special to_s method. We end up doing this:
log.debug myobj.collect{|e| e.collect{|p| p.to_s} }
That's an expensive thing to do because the argument to debug will be evaluated before being sent to the logger. Setting the logger level to OFF won't prevent evaluation of this parameter. Well, there are two ways around this. The first one is as follows:
if log.debug?
  log.debug myobj.collect{|e| e.collect{|p| p.to_s} }
end
log.debug? is called a query method and it will tell you whether the logger is logging at that level. It is very cheap to call this method, and a great way to work around parameter evaluation. The query methods, like the logging ones, are named after the levels but with a question mark at the end.

An alternative method is to log a block:

log.debug { myobj.collect{|e| e.collect{|p| p.to_s} }
The block will be evaluated if and only if the logger is capable of handling DEBUG log events.


How Many?

How many loggers should one have? Only experience can tell you that, but a general rule of thumb is to create one static logger per class and one per service or state.

Once you start to accumulate a lot of loggers, the techniques of logger inheritance and additivity should become more attractive. They allow you to structure what gets logged and to where according to the composition of a system. Log4r is capable of matching any system design.

The configuration possibilities in Log4r are uncountable and can sometimes be daunting. It's best to start with something simple and evolve it over time. Log4r's XML configuration can assist in that process by providing an easy to read schematic of your loggers. You can either read the XML directly or use an XML visualization tool.


Where To?

Log4r lets you associate any number and kind of Outputter to a logger. Logger additivity enables you to propagate a log event upwards in the logger heiarchy. The outputters themselves can have their own level thresholds. Unlike normal loggers, Outputters can log at certain specific log levels. this allows one to channel particular data to a particular output. All things considered, you have total freedom in deciding what gets logged where.

Deciding what to log where is also a matter of experience. It's best to let the destinations evolve over time until you find a good setup. If you feel a certain file is begining to show way too much info, you can split the data with ease by adding another outputter to the logger in question and adjusting the outputter levels. Again, the XML configuration can be very helpful once you get to this point.


Where From?

Want to find out where a particular log statement came from? Turn on tracing for a logger and it'll record that data.
Logger['mylog'].trace = true
Thereafter, any outputters that listen to 'mylog', and has a Formatter that can handle traces, will show the line number and file of the log event. Tracing is relatively expensive and should be avoided where not necessary. However, it's eminently useful once your program begins to grow past a certain size.


Who's Talking?

If you have a ton of loggers and use logger inheritance, sometimes it's a good idea to show the full ancestry of a logger in the log statement. By this point, you might have decided to use PatternFormatter to format your log statements. Here's how to set up PatternFormatter to show the full ancestry of a logger in a logging statement (in XML):
<formatter type="PatternFormatter">
  <!-- %C shows full ancestry -->
  <pattern>[%l %C] %m</pattern>
</formatter>
For a logger named 'me' with ancestors 'cain::grandpa::pa', it will produce:
[DEBUG cain::grandpa::pa::me] Log message


A Silly Trick

In addition to being the parent of all loggers, Logger.root is a null object. That means that it does absolutely nothing when you call its log methods. Its query methods always return false and it has no outputters. Hence, it is entirely possible to swap some logger you want to shut up with Logger.root as in the following:
mylog = Logger.root
mylog.debug "This won't do anything"
It's handy at times, when you don't want to muck with the config file.


Gotchas

If you are using Log4r, there are a few gotchas that you should be aware of:
  • Logger levels can be dynamically redefined, but the change won't be noticed by any children. That is, if you set root.level=OFF after defining some loggers, none of the loggers will change their level to OFF, If you think about this for a second, you'll understand why this is a good idea.
  • Dynamically redefining levels, tracing and additivity is a costly operation. It's best to set up all your loggers in a config script and leave well enough alone. The dynamism is useful for debugging as you can toggel tracing and level from within the file you're working on.
  • When an IOOutputter's IO is closed, the IOOutputter changes its level to OFF


Performance

Profiling has revealed that log4r is typically an order of magnitude or two slower than log4j. However, this is still damn fast! In particular, if a logger is set to OFF, the overhead of checking to see if a log event should be logged nearly vanishes. This was accomplished by dynamically redefining the unloggable logging methods to do nothing.

In the future, Log4r's performance critical features will be written as a C extension to Ruby. It will still be optional, but it will be available for those who absolutely need to squeeze every last ounce of performance out of Log4r.


The Project Itself

Log4r was entirely developed by one person. You can get more info about this person in the contact page.

$Id: manual.html,v 1.6 2002/01/29 05:44:12 cepheus Exp $