Tutorial #4: Logging in Copland
Copland uses the Log4r framework, developed by Leon Torres. It is a flexible, powerful, easy-to-use logging system written in pure Ruby.
This tutorial will show you how to configure and use the logging subsystem. It will not show you how to attach logs to services—see the next tutorial for that.
Step #1: Copland::LogFactory
All logs are managed via the singleton Copland::LogFactory instance, so the first thing you’ll need to do is get a reference to that object:
require 'rubygems' require_gem 'copland' factory = Copland::LogFactory.instance
The factory comes preconfigured with a set of default options. The available options (and their defaults) are:
- default_output_file_name ("copland.log")
- default_output_directory (".")
- default_output_path ("./copland.log")
- default_output_stream (nil)
- default_formatter_pattern ("[%5l] %d %c >> %m")
It is important to note that any changes made to any of these properties will only affect logs that are created after that point. And because the LogFactory caches all logs that it creates (actually, it is Log4r that does the caching), any log was that was created before the properties were changed will not reflect those changes.
Thus, these properties are typically set when the program first starts up, to override the defaults.
The rest of this tutorial will demonstrate what each of those properties does.
Step #2: Setting up a testing infrastructure
To prepare for the rest of this tutorial, let’s create a method that will accept a log as a parameter, and then will write messages to that log:
def write_to_log( log )
log.debug "This is for debugging."
log.info "This is informational."
log.warn "This is a warning."
log.error "This is an error."
log.fatal "This is a fatal error."
end
This method is a good demonstration of logging levels. There are five pre-defined logging levels (debug, info, warn, error, and fatal). You can define more, as well (though I’ll just direct you to the Log4r documentation for how to do that).
Step #3: Creating a logger
Creating a log is very straightforward. You just call the get_log method of the LogFactory instance, passing it the name of the log you want to create, and it will return a reference to that log. If a log by that name was created previously, you’ll get a reference to that log. (It’s all cached, remember.)
Try something like this:
log = factory.get_log( "tutorial" ) write_to_log log
This will write the five messages defined in step #2 to a file called "copland.log" in the current directory. The log should have the following content after executing what you’ve done so far:
[DEBUG] 2004-03-30 10:42:16 tutorial >> This is for debugging. [ INFO] 2004-03-30 10:42:16 tutorial >> This is informational. [ WARN] 2004-03-30 10:42:16 tutorial >> This is a warning. [ERROR] 2004-03-30 10:42:16 tutorial >> This is an error. [FATAL] 2004-03-30 10:42:16 tutorial >> This is a fatal error.
The logging level comes first, followed by the date and time, and then the name of the log that generated the message. Lastly, the message itself is printed. (You can change this format by changing the default_formatter_pattern property—we’ll see more about that later.)
Step #4: Changing the file name
If you want to change the name of the file that will be written to, but leave the directory unchanged, you can assign a value to the default_output_file_name property.
factory.default_output_file_name = "tutorial.log" log = factory.get_log( "tutorial2" ) write_to_log log
The above will create and write to a file called "./tutorial.log" (since "." is the default output directory). Notice that a different log name is passed to get_log. You must do this in order for the changed property to take affect. If you simply grabbed the "tutorial" log again, it would have the original properties set, and would continue to write to "./copland.log".
Step #5: Changing the output directory
You can also change the name of the output directory. Just set the default_output_directory property, and the log file will be opened in that directory.
Dir.mkdir "logs" rescue nil factory.default_output_directory = "logs" log = factory.get_log( "tutorial3" ) write_to_log log
The above will create and write to a file called "logs/tutorial.log". Again, we specify a new log name, so that the changes will be recognized.
Step #6: Changing the output path
Changing the directory and file name together is as simple as assigning to the default_output_path property:
factory.default_output_path = "logs/custom.log" log = factory.get_log( "tutorial4" ) write_to_log log
Step #7: Logging to an existing stream
You can log to an existing stream (such as $stdout), as well. Just assign IO object to the default_output_stream property, and you’re good to go.
factory.default_output_stream = $stdout log = factory.get_log( "tutorial5" ) write_to_log log
If you assign an existing IO stream to this property, the file, directory, and path properties may become nil, if those values cannot be determined from the stream object itself. This is rarely an issue.
Step #8: Formatting the logging output
You can specify your own custom output format by assigning a formatting string to the default_formatter_pattern property:
factory.default_formatter_pattern = "%l (%c) %d [%m]" log = factory.get_log( "tutorial6" ) write_to_log log
The formatter pattern is just a printf-like format string, with the following special directive letters:
- c
- the logger’s name
- C
- the logger’s full-name
- d
- the date and time
- t
- the file and line number of the logging event
- m
- the raw message passed to the log (with to_s called on it)
- M
- the pretty-printed message (prints exceptions and more)
- l
- the level of the message
See the Log4r documentation for more information on formatting log messages.
Advanced Topics
Using features of Log4r you can custmize most aspects of existing logs. It’s a little more typing and it requires a more intimate knowledge of Log4r than you would otherwise need, but it can be done:
require 'log4r/outputter/datefileoutputter'
log = factory.get_log( "tutorial" )
log.remove( log.outputters )
outputter = Log4r::DateFileOutputter.new( 'dated-outputter',
:dirname => ".",
:date_pattern => "%Y%m%d" )
outputter.formatter = Log4r::PatternFormatter.new(
:pattern => factory.default_formatter_pattern )
log.add( outputter )
write_to_log( log )
The above will create a dated log, which appends the current date of the log to the log name and which will create a new log when the date changes.
Back to the tutorial index: files/tutorial/tutorials_rdoc.html