In Progress
Unit 1, Lesson 1
In Progress

Log Rotation

Video transcript & code

So far we've covered the basics and many of the not-so-basics of logging in Ruby. But unless you enjoy getting "out of disk space" errors on your production servers, there's one more topic we need to discuss. That topic is log aging and rotation.

The default behavior for the Logger class when logging to a file is to continually append to that file. This carries with it the obvious danger of infinitely growing log files. If it's just a local utility you can always remove the logfiles manually from time to time. But on a server, silently ballooning log files can be a serious liability.

For this reason, robust logging systems usually support log aging and rotation. With aging, the logger switches to a new file periodically, and stamps the previous log files according to their age. With rotation, only a certain number of log files are retained, with the oldest being removed whenever a new file is added.

The Logger library has built-in support for log aging and rotation. To enable aging, we can simply supply a frequency name as the second argument to the initializer. We're supplying "daily" here, but "weekly" and "monthly" are also supported.

Let's then log several messages. In between log messages, we simulate a day passing using the Timecop gem.

require "logger"
logger = Logger.new("myprog.log", "daily")

require "timecop"

logger.info "Hello, world"

Timecop.travel(Date.today + 1)

logger.info "Hello, again"

Timecop.travel(Date.today + 2)

logger.info "Hello, future!"

When we examine the current directory, we can see that there are now three log files. One is the actual file name we specified. That's the current file, not the earliest. The logs from previous days have been split out into separate, date-stamped log files.

$ ls *.log*
myprog.log  myprog.log.20150906  myprog.log.20150907

By itself, this doesn't give us full log rotation, because Logger will just keep adding new files. However, it does make it easier for us to remove the oldest log files without disturbing more recent ones.

To get full log rotation, we have to do things a little differently. Instead of a frequency name, we pass two extra arguments: the number of log files to keep, and a size cutoff in bytes. Above the cutoff, Logger should switch to a new file. If that means that there are now more log files than we told it to keep around, it will also delete the oldest files.

require "logger"
logger = Logger.new("rotation.log", 4, 2048)

1000.times do |n|
  logger.info "This is log message ##{n}"
end

We've specified a very small file size of 2K, and told the Logger to keep just 4 files around. Now let's dump a whole bunch of log messages and see what happens.

When we examine the directory contents, we can see that there are now four log files: a current log, with the exact file name we specified. And three progressively older log files stamped with 0, 1, and 2. Each of the archive files is a little over 2K in size.

If we look at the beginning of the oldest log, the one stamped with .2, we can see that the earliest messages it contains are still pretty recent. Anything older than that has been discarded, as log files filled up their allotted space, were rotated out, and then eventually removed. By enabling log rotation, we've put a cap on out-of-control log growth.

There are a number of advanced logging topics that we have yet to talk about. But at this point we've finished our tour of Ruby's built-in Logger library. If you've watched all the logging episodes up till now, you should now be prepared to use this library to its full capabilities. You know how to control logging levels, optimize away expensive debug logging, control the format of output, and limit log file growth.

I hope this gives you the confidence you need to add copious logging to your applications, big or small. Happy hacking!

Responses