There are a few techniques that are rarely covered in the Rails printed literature, even though they are essential to keeping Rails applications in good working order. This section covers these bits and pieces.
Currently, Mongrel has to be started and stopped manually from the development machine. If the production server is rebooted, someone will have to remember to restart the application too. A better solution is to add a start/stop script to the production server to run Mongrel automatically with the server.
First, create a *nix script to control the application in script/mongrel_init
. Here's an example for Ubuntu:
#!/bin/bash # Ubuntu Linux init script for Rails application # set these variables to your production environment APP_USER=captain APP_NAME=Intranet APP_PORT=4000 APP_HOME=/home/captain/apps/Intranet # more variables - you don't need to set these CURRENT=$APP_HOME/current PID=$APP_HOME/shared/pids/mongrel.pid MONGREL="sudo -u $APP_USER /usr/bin/mongrel_rails" ENVIRONMENT=production # load library functions . /lib/lsb/init-functions case "$1" in start) log_begin_msg "Starting Rails application $APP_NAME" $MONGREL start -c $CURRENT -e $ENVIRONMENT -p $APP_PORT -P $PID -d log_end_msg 0 ;; stop) log_begin_msg "Stopping Rails application $APP_NAME" $MONGREL stop -P $PID log_end_msg 0 ;; restart) log_begin_msg "Restarting Rails application $APP_NAME" $MONGREL restart -P $PID log_end_msg 0 ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;; esac exit 0
You'll need to set the variables prefixed with APP_
in the above script to values appropriate to your production server.
This script is in a format that can be used by the *nix initialization (init) system to control Mongrel during server starts, stops, and reboots. For more about *nix init scripts, see http://www.linux.com/article.pl?sid=06/01/03/1728227.
Next, make the script executable:
$ chmod +x script/mongrel_init
This script can now be executed with a start, stop
, or restart
option, e.g.
$ ./mongrel_init start
$ ./mongrel_init stop
$ ./mongrel_init restart
Deploy the application to the server. Then, on the production server, copy the script from script/mongrel_init
into the /etc/init.d
directory:
$ sudo cp script/mongrel_init /etc/init.d/mongrel_intranet
Finally, you need to add the script to the initialization sequence for the production server. On Ubuntu Linux, you can do this with:
$ sudo update-rc.d mongrel_intranet defaults
Now Mongrel should start and stop with the server.
One other common task you need to perform is clearing out stale session files (i.e. sessions associated with clients who are no longer connecting to the application). Rails doesn't do this automatically for you. If you are using file system sessions (see the section Cookies and Sessions in Rails in Chapter 8) and have a busy site, the RAILS_ROOT/tmp/sessions
directory for your application can rapidly fill up with session files as a result.
Rails provides a simple Rake task to clear out stale session files:
$ rake tmp:sessions:clear
This script just does a blanket clean-up of session files, regardless of whether they are still in use. However, it's simple enough to write a script that will clear out any session files in a time-sensitive fashion, which should leave behind those still being actively used. For example, here's one to clear sessions that were last accessed more than 6 hours ago, which you can add to script/clear_sessions:
#!/bin/bash # Clear out stale sessions (last accessed more than 6 hours ago) /usr/bin/find /home/captain/apps/Intranet/current/tmp/sessions -name "ruby_sess*" -amin +360 -exec rm {} ;
The session files for the application are all prefixed with"ruby_sess"
. This script finds all of the files in the sessions directory (RAILS_ROOT/tmp/sessions), matching this file name pattern (the -name
switch) that were last accessed (the -amin
switch) more than 6 hours (360 minutes) ago (+360). Each matching file is passed to the rm
command (via the -exec
switch), which removes it.
Make the script executable:
$ chmod +x script/clear_sessions
Deploy the script to the production server. Make sure it is still executable once deployed.
To call the script on a schedule, set up a cron job to run every hour on the production server, using whatever cron tools you have available. You should do this as the captain
user, who has permission to write into the sessions directory. For example:
$ su - captain
Password:
$ crontab -e
will open up captain's
crontab for editing. Add this line:
0 * * * * /home/captain/apps/Intranet/current/script/clear_sessions
which schedules the clear_sessions.sh
script to run at 0 minutes past every hour of every day. If things are working correctly, you should see entries like this in /var/log/syslog
, indicating that the command ran:
May 9 00:00:41 demo-server /USR/SBIN/CRON[7079]: (captain) CMD (/home/captain/apps/Intranet/current/script/clear_sessions)
If the script fails to run correctly, you'll get error messages sent to the standard Linux mail spool; for the captain
user on Ubuntu, this goes to /var/mail/captain
. An individual error email looks something like this:
From captain@demo-server Wed May 09 00:00:41 2007 ... From: root@demo-server (Cron Daemon) To: captain@demo-server Subject: Cron <captain@demo-server> /home/captain/apps/Intranet/current/script/clear_sessions ... Date: Wed, 09 May 2007 00:00:41 +0100 /bin/sh: /home/captain/apps/Intranet/current/script/clear_sessions: Permission denied
Emailed errors can be useful in helping track down problems with a cron job. If you have an email server correctly configured for email, you could forward the output from cron jobs to an arbitrary administrator email address instead.
The Rails log
files are essential for tracking down issues with your application. However, after a few weeks or months of operation, those files start to get big. As well as taking up disk space, this can make them slow to open with a text editor for viewing.
The solution is to rotate the logs; that is, periodically rename the current log, and archive it, and open a fresh empty file for storing new log entries. Here's a sample Ruby script for doing this, which you could place in script/rotate_logs:
#!/usr/bin/env ruby # Rotate logs on production server; call via cron LOG_ROOT = File.join(File.dirname(__FILE__), '../log') suffix = Time.now.strftime('%Y-%m-%d') ['mongrel', 'production'].each do |log_for| log_file = File.join(LOG_ROOT, log_for + '.log') archived_log_file = log_file + '.' + suffix File.rename(log_file, archived_log_file) File.new(log_file, 'w') end
This script takes the current mongrel.log
and production.log
script and renames them, appending the date in YYYY-MM-DD
format to each filename as a new suffix. It then creates new empty log files, mongrel.log
and production.log
, which the application can continue logging into. Make sure you deploy the new script to the server (using cap deploy)
.
To run the script periodically, add it to the captain
user's crontab (see the previous section for instructions on editing crontab). For example, adding this line to the crontab will run the script at seven minutes past midnight every day:
7 0 * * * /usr/bin/ruby /home/captain/apps/Intranet/current/script/rotate_logs
A final nicety is to ensure that all the custom scripts we're adding are made executable when deployed to the production server. (I found myself doing this manually each time I deployed new scripts, as the correct permissions weren't being stored in the Subversion repository.) You can do this by adding a new Capistrano task to config/deploy.rb
called make_scripts_executable
, and then by including this script as part of the after_update_code
task (see the earlier section Centralizing File Uploads):
desc "Make all custom scripts (in script directory) executable"
task :make_scripts_executable, :roles => :app do
run "chmod -R u+x #{release_path}/script"
end
task :after_update_code do
symlink_for_file_uploads
make_scripts_executable
end
Note that you could go even further than this, and add a handler to cold_deploy
to create the cron jobs for you. That task is left as an exercise for you.
One other way of managing logs more effectively is to reduce the amount of detail they contain. Rails supports different so-called log levels. The best way to imagine these is as representing different levels of sensitivity; the lower the log level, the less sensitive the logging system is; the less sensitive it is, the less it reports on what the application is doing. The log levels available are:
:debug
(most sensitive):info
:warn
:error
:fatal
(least sensitive)The log level can be configured as per environment. The default log levels for each environment are as follows:
:debug
:debug
:info
I'd recommend leaving the log levels for test and development as they are, at their most sensitive. However, for production, you may find that you don't want such verbose logging (the :info
log level includes details of every controller/action invocation, templates rendered, time for rendering etc., which can result in large log files very quickly).
To reduce the sensitivity of logging, edit config/environments/production.rb
and set the config.log_level
directive as follows:
config.log_level = :error
Setting the log level to :error
tells Rails to ignore warnings and only report on errors (serious and fatal). In turn, this reduces the amount of data written into the logs, which means they don't grow so rapidly. If you find that reducing logging in this way makes it hard for you to track down errors when they occur, you can always turn up the sensitivity again.
3.128.173.53