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.

Monday, December 18, 2006

FireWatir: How to?

FireWatir is a web application testing tool written using Ruby language. It is used for testing web application functionality on Firefox browser. It is written keeping 'WATiR' in mind so that scripts written for testing the application on IE using WATiR can be used with minimal/no changes, to test the application on Firefox.

How its different from WATiR?
The main difference between WATiR and FireWatir lies in the mechanism, which drives the browser, to test a web application.
WATiR uses the COM object of IE browser exposed by windows. It interacts with the COM objects for doing any action on HTML elements, of a page, that is displayed using IE(i.e. HTML elements that are supported by WATiR). Interacting with the browser in this way restricts the usage of WATiR on Windows only.

For Firefox there is no COM object that is exposed. So, tried using XPCOM to interact with the browser. But it didn't work out as you need to compile XPCOM with Firefox browser. So it was not feasible for every user of FireWatir to first compile the source and then use the tool. So, we explored more and found an extension called JSSh. JSSh is a TCP/IP JavaScript Shell Server for Mozilla that allows other programs like Telnet to make connections to the running Mozilla process. This was what we needed to drive the Firefox browser. The ruby calls are converted to corresponding JavaScript code and send to JSSh via a socket. JSSh executes that JavaScript code on the browser and returns the result.

So, finally the test scripts when executed on IE or FireFox will be executed in same manner; the working is transparent to users. Though, FireWatir is implemented using socket for interaction with the browser, there is not much of a difference in execution speed of FireWatir as comparted to WATiR.

How to install FireWatir?
You can get the latest gem and JSSh extension for Windows from http://code.google.com/p/firwatir. FireWatir is still not tested on Mac or Linux, but it should work on any platform as we are not using any Windows specific component.

Install the JSSh extension by opening the extension file in the browser. The extension will not show up in the extension list. So to check if the extension is installed properly, restart the Firefox from command prompt with '-jssh' as command line argument. For e.g.: In windows restart it using 'c:\Program Files\Mozilla Firefox\Firefox.exe -jssh' assuming that you have installed Firefox in 'c:\Program Files\Mozilla Firefox' directory. After the browser is started telnet to port 9997. the response should be:

Welcome to the Mozilla JavaScript Shell!

JSSh command shell will open with '>' as shell prompt character. If this shows up then JSSh extension is installed properly.

Install the FireWatir gem using command 'gem install [firewatir gem name]'. This will install FireWatir 1.0 on your machine. Now go to the FireWatir installation directory in the gems. For e.g. go to 'c:\ruby\lib\ruby\gems\1.8\gems' assuming that you are on windows platform and have installed 'ruby' version 1.8 in 'c:\ruby\' directory. Check for 'firewatir-1.0-mswin32 folder (assuming you have installed gem for windows). Existence of that folder indicates correct installation. Now go to 'unittests' directory and run file 'mozilla_all_tests.rb' (make sure you have started Firefox as said above with -jssh option before running the test cases). This file will run all the unittests without any failure or errors. In case you get any errors or failures refer to section 'TroubleShooting'. In case the error is not resolved add it to the issue tracking system at 'http://code.google.com/p/firewatir.

How to use?
Go to the 'unittests' directory in the gems folder. Refer to the unittest cases on how to access the element? How to use them? What properties they expose? etc etc..

Which Firefox versions are supported?
FireWatir is tested on Firefox version 1.5, 1.5.0.7 and 2.0. It should work with all Firefox version 1.5 and above. It may or may not work with versions less that 1.5

TroubleShooting
1. Currently you need to start Firefox manually from command prompt using '-jssh' as command line argument before running any FireWatir script or unit tests.
2. Check if JSSh is installed correctly by connecting to port 9997 using Telnet (telnet localhost 9997 if you are telnet-ing from the same machine on which your Firefox instance is running, or telnet testhost 9997 where testhost is the hostname of the remote machine on which FireFox is running).
3. In case 'attach_new_browser' test fails. Make sure you run this test alone using 'ruby attach_new_browser'. Make sure that Firefox doesn't block the pop up. Also make sure that you have settings to open the link in new window instead of new tab.
4. In case you face any other problems or you have some comments/suggestions/queries mail at 'angrez@gmail.com' or at 'amit.garde@gmail.com'

Tuesday, December 12, 2006

Building Firefox on Windows

Building Firefox on Windows is a bit tricky. Though, the instructions on Mozilla developer site:
http://developer.mozilla.org/en/docs/Windows_Build_Prerequisites_on_the_1.7_and_1.8_Branches
are clear; there are more steps that you need to follow to build Firefox successfully. Make sure you
1. Use make 3.80 and not 3.81. If you install cygwin it will install make 3.81 by default which will break the build. You can get make 3.80 here.
2. Rename /cygwin/bin/link.exe so that correct linker is used.
3. Delete all "config.cache" files. Why? Explained below.
4. Have source under directory strictly named as "mozilla". Click here for more discussion on this.
5. If you get error like this:
client.mk:359: source: No such file or directory
client.mk:359: code/mozilla/build/unix/modules.mk: No such file or directory
make: ** No rule to make target `code/mozilla/build/unix/modules.mk'. Stop here.
then, make sure that the mozilla source code is in a directory that doesn't have a space in the name i.e. it should be like this 'Firefox source code'. The directory name should not contains spaces.

If you have build Firefox before and you have changed your configuration i.e. the location of tools that you used to build Firefox, make sure you clear all "config.cache" files. These files caches the location from where the tools are used; so when you rebuild the Firefox, instead of fetching the location from your config file it uses the location in "config.cache" file, which may contain stale location.

So to summarize:
1. Use make 3.80
2. Clean all "config.cache" files.
3. Rename cygwin link.exe
4. Have source inside directory strictly named as "mozilla"
5. The name of the directory containing source code should not have spaces in it.