Wednesday, December 27, 2006

Log4r - Usage and Examples

Log4r is a library used for logging in Ruby programs. It can be used for logging to any kind of destination and with varying degrees of importance (levels). Log4r supports custom level names (i.e. you can have as many levels as you can instead of using built-in levels), logger inheritance, multiple output destination, custom formatting of messages, XML and YAML configuration etc.

In this article we'll see how to use Log4r in Ruby programs.

1. Simple logging using Log4r.

The below code shows how to log simply to the Standard output using Log4r. It doesn't use any formatting or any custom levels.

require 'log4r'

include Log4r

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

# You can use any Outputter here.
mylog.outputters = Outputter.stdout

# log level order is DEBUG < INFO < WARN < ERROR < FATAL
mylog.level = Log4r::INFO

# 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 "do_log" method logs the messages at different levels. For logging you don't have call to a function that takes 'log level' and message as argument. Instead in Log4r each level is a method itself which takes message as argument and then logs the message depending upon the level that is set for the logger.

You can also check the logging level before logging a message using "query methods" like debug?, info? etc. You can use them like this:

if(log.debug?)
    log.debug "debug message"
end


The above code will first check that if current logger can log the messages at debug level or not. The method with level name and question mark is called "query method". It will return true or false depending upon the level set for the logger.

2. Logging using Formatter and Outputter:

Above example used Standard output as destination for the logging. It also didn't use any Formatter for formatting the message. In this section we'll see what all destinations we can use for logging (with example of one of them) and what all formatting we can use (with example of one of them).

Log4r provides the following destination for logging:
1. IOOutputter -> For logging to any IO object
2. StdoutOutputter -> For logging to standard output IO i.e. $stdout
3. StderrOutputter -> For logging to standard error IO i.e. $stderr
4. FileOutputter -> For logging to file
5. RollingFileOutputter -> For logging to file and split the file as it grows
6. SyslogOutputter -> For logging to system log
7. EmailOutputter -> For emailing the logs
8. RemoteOutputter -> For remote logging

You can attach any number of outputter to a logger using 'outputters' property or by using 'add' method of logger object. Suppose you have created four outputters out1 to out4, you can add them to logger 'mylog' as follows:

mylog.outputters = out1, out2
mylog.add(out3, out4)


Log4r provides several formatters for formatting the message before logging it. It also provides a custom formatter called 'PatternFormatter' which is very flexible and powerful.

Log4r provides the following formatters:
1. BasicFormatter -> This is default formatter. Logs the message as it is.
2. PatternFormatter -> Most powerful and flexible formatter for custom formatting.
3. SimpleFormatter -> Adds log level and logger name to the message. Used only for strings. Doesn't inspect objects.
4. ObjectFormatter -> Inspect the objects and logs them as IRB(Interactive Ruby) does. Doesn't inspect strings.
5. NullFormatter -> Does nothing :)

Following code sample uses 'FileOutputter' alongwith 'PatternFormatter' for logging the message to log file.

require 'log4r'
include Log4r

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

# You can use any Outputter here.
mylog.outputters = Outputter.stdout

# Open a new file logger and ask him not to truncate the file before opening.
# FileOutputter.new(nameofoutputter, Hash containing(filename, trunc))
file = FileOutputter.new('fileOutputter', :filename => 'log',:trunc => false)

# You can add as many outputters you want. You can add them using reference
# or by name specified while creating
mylog.add(file)
# or mylog.add(fileOutputter) : name we have given.

# As I have set my logging level to ERROR. only messages greater than or
# equal to this level will show. Order is
# DEBUG < INFO < WARN < ERROR < FATAL
mylog.level = Log4r::INFO

# specify the format for the message.
format = PatternFormatter.new(:pattern => "[%l] %d :: %m")

# Add formatter to outputter not to logger.
# So its like this : you add outputter to logger, and add formattters to outputters.
# As we haven't added this formatter to outputter we created to log messages at
# STDOUT. Log messages at stdout will be simple
# but the log messages in file will be formatted
file.formatter = format

# 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)


FileOuputter.new creates new instance of FileOutputter. First argument is name of the outputter, second is hash containing name of the file and whether to trunc the file while opening the file or not. For more information on outputters refer to this link.

PatternFormatter.new creates a new pattern formatter. It takes hash as input specifying the format for the message. If you are using date in the message you can optionally give date_pattern also. If no date_pattern is specified default date time patter will be chosen, which will log the date like this:
2006-12-21 13:15:50

To specify custom date formatting use the following code:

format = PatternFormatter.new(:pattern => "[%l] %d :: %m",
     :date_pattern => "%a %d %b %H:%M %p %Y")


For more information of "date_pattern" directive (%a, %d etc) refer to this link. For more information about "pattern" directives (%l, %m) refer to this link.

7 comments:

Unknown said...

have you had any luck using log4r along with watir? I am having problems doing so. I am thinking watir includes some class/module which doesnt play well with log4r.

Angrez Singh said...

hi jorge,

as far as I know watir include logger for logging the operations that it does on different HTML element. It won't be useful for you to use that logger in your test scripts.

But you can use your own logger in the watir scripts that will log the messages. I have used log4r with watir scripts without any problems...

can you elaborate on what problems/issues/errors you get while using log4r with watir scripts?

Chirag Patel said...

I'm having problems with both logger and log4r. They both work fine on my local Windows machine but do not create the .log files when I try them on my hosted UNIX server (TextDrive)

Below is the code for both log4r and logger. Both methods write
the .log file to the root Rails directory (not the directory where the code is located). I changed the permission to 777 on the Root Rails
dir for the sake of testing and both methods still do not
generate .log files. Any ideas?

Thanks! chirag

Using logger
---------------------------------------------------------------
#require 'logger'
def halo_logger
logger_fu = Logger.new('logfile.log')
logger_fu.debug{ "params #{params} \n" }
end

Using log4r
--------------------------------------------------------------
require 'log4r'
include Log4r

def halo_log4r
config = {
"filename" => "fu.log",
"maxsize" => 16000,
"trunc" => true
}

logger_fu = Logger.new 'myLog'

#alternate way of adding a file outputter
#out = RollingFileOutputter.new("myLog", config)
#logger_fu.add(out)

logger_fu.outputters = RollingFileOutputter.new("myLog", config);
logger_fu.level = DEBUG
logger_fu.debug{ "params #{params} \n" }
end

Angrez Singh said...

@Chirag:

What the value of "params" variable? Do you get any error on Unix environment. I worked only on windows with Log4r and Ruby. Can you just check log4r version

Unknown said...

I want to configure this api so that I can log the details of a ruby on rails object. Like say I have a context object which has some id. I need to display this. How does this work? I am a newbie.

Where should I place this method?
This is an independent api and I need to use it when I make a call to the ruby on rails application.

Please help me out.

Unknown said...

I'm a newbie and I am trying to work on log4r.

I need to customize this API so that I can get the details of a particular object. Say I have a ruby on rails object fu I need to display the details of the object like id etc.

It should be a method, so where should I describe it? If my application has to use it how should I make a call?

I followed the post and I was able to display all the log options.

Please help me out.

Angrez Singh said...

Hi Epsitha,

You can create your own methods which will internally call Log4r methods to logging message.
In your method you can pass the object and then use Ruby's Reflection to access the properties of that object and log it.

Something like this:
YourMethod(object)
{
// Construct message using Reflection
log.debug(message)
}