Monday, July 25, 2011

Checking Web Application Health

There are certain things you want to know about your web application to make sure it has started correctly and to help diagnose problems.
  • Is it up?
  • Can it connect to the database(s)?
  • Can it connect to the web service(s)?
  • Can it do any other actions that may independently break?
  • What are the server settings?
  • What version of the code is running?
The first question is easy to answer? But what about the others? You can obviously determine if the other dependencies are up by going to the appropriate place in your application, but it'd be nice to have a single location to check all of these. And what about server settings or code version? Ever had a bug reported that you know you fixed? It's nice to also be able to make sure that you really are running the version you think you are.

Here's how I've accomplished this on a Ruby on Rails project.

Single Location
First, I created a controller with a single page:
prompt> rails generate controller Admin index
Here is the beginning of the controller class.
class AdminController < ApplicationController
  before_filter :require_admin

  def index
  end
end
As you can see, I've protected it, so only administrative users can view this page. Here is the views/admin/index.html.haml where I show server state.
This is the status page.
%br
%h2 Version
%pre= @version
%h2 Environment
%br
%b RAILS_ENV:
= ENV['RAILS_ENV']
%br
%hr
%h2 Site Tests
.test
  = button_to "Test DB", {:action=>"test_db"}, :remote=>true, :onClick=>"startTest()"
%hr
%h2 Test Output
#testOutput

:javascript
  function startTest() {
    $("#testOutput").replaceWith('<div id="testOutput">Loading...</div>');
  }

Dependency Tests
I've implemented each test as a separate Ajax call. In the view code above, this everything from "Site Tests" on down. This consists of the buttons (one for each test, just one here), a place to show the output, and the javascript to make this work. For each test, I just add a new button in the .test div. As you can see in the code above, the button makes an Ajax call (:remote=>true) and calls a javascript function to show that the result is loading. It is up to the controller to then update the testOutput div with output status.

In my admin_controller.rb I have a function for each button that looks like:
def test_db
  standard_test do
    # insert test code here
  end
end
The helper function standard_test looks like:
def standard_test
  begin
    yield
    @success = true
  rescue Exception => e
    @success = false
    @exception = e
  end
  respond_to do |format|
    format.html {
      flash[:error] = "This should've been ajax"
      redirect_to admin_index_path
    }
    format.js {
      render 'test_output', :layout=>false
    }
  end
end
All it does is executes the test code (via the yield statement) and catches any thrown exceptions. If there is no exception, it considers the test a success. Assuming it was called via an Ajax call, it then renders the javascript template test_output.js.haml which is simply:
!= "$('#testOutput').replaceWith('#{escape_javascript render(:partial=>'test_output')}')"
As you can see, this merely replaces the testOutput div with what is rendered by the _test_output.html.haml partial:
#testOutput
  - if @success
    %b SUCCESS
  - else
    %b ERROR
    %br
    %b Error Class:
    = @exception.class
    %br
    %b Error Message:
    %pre= @exception.message
    %b Error Stack Trace:
    %pre
      - @exception.backtrace.each do |line|
        = line
    %b Error Inspect
    %pre= @exception.inspect
Of course, don't forget to make sure your routes are in your config/routes.rb.
get 'admin/index'
post 'admin/my_test' # separate route for each test
Version
Including version information is going to be very specific to your environment. We use Mercurial for our version control, and since we have a number of Java project, we use ant for our deployment process. Here is how I connected these together.

In the ant deployment script, I added a target that looks like:
<target name="write.hg.version">
  <exec executable="hg" dir="${project}" output="${project}/config/version.txt">
    <arg value="summary" />
  </exec>
</target>
All that is left is to get the contents of this version.txt file into the @version variable that is displayed by index.html.haml. This is done by simply having the index method in admin_controller.rb read in the file.
def index
  name = File.join(RAILS_ROOT, 'config', 'version.txt')
  if File.exists? name
    f = File.new(name)
    @version = f.read
  else
    @version = "Unknown version"
  end
end
Conclusion
This really wasn't too much work, and now I have a single page that I can go to and at a glance see what version of the code was deployed and which rails environment is being used. Adding new tests is just a simple matter of adding a new button to the view, which calls a new test method in the controller. By leveraging the standard_test method, new tests can consist of just the code they are intended to test.

No comments: