Exempt Rails Controllers from logging; A refactoring

07 Jun 2008

I’ve got a couple Rails controllers that just serve static content like graphics and script assets. In production the content is all cached so it bypasses the controller but when I’m running locally I’ve got pages and pages of garbage filling up my development log.

The first attempt at silencing the whining controllers used an around_filter that looked something like this:

    class GraphicsController < ApplicationController
      around_filter :no_logging

      protected
        def no_logging
          logger.silence { yield }
        end
    end

which is great, compact, easy to encapsulate, etc. But it doesn’t work. Debugging told me I needed to use a different logger object and put this code in a different place. I share this next piece of code not out of pride but as an example that I, like Adam Keys, am a student of the art of abysmal failure. Here’s my original implementation:

    # in ./lib/exempt_controllers_from_logging_entirely.rb

    module ExemptControllersFromLoggingEntirely

      @@controllers_to_exclude = %w(graphics styles scripts)

      def self.included(base)
        base.class_eval do

          def process_with_no_logging(request, *args)
            if @@controllers_to_exclude.any? {|controller| request.parameters[:controller] == controller }
              logger.silence { process_without_no_logging(request, *args) }
            else
              process_without_no_logging(request, *args)
            end
          end

          alias_method_chain :process, :no_logging

        end
      end
    end

    ActionController::Base.send :include, ExemptControllersFromLoggingEntirely

Yay! It’s HUGE! And it uses all the unnecessary complexities of alias_method_chain and self.included and, oh joy, the controllers that use it are specified RIGHT IN THE CODE.

Sadly, this code worked fine. Ruby lacks an ugly-code warning system that tells me when I’m writing something unwieldly.

Here’s the refactored version:

    # in ./lib/exempted_from_logging.rb
    module ExemptedFromLogging
      def process(request, *args)
        logger.silence { super }
      end
    end
    ## Usage:
    ## class GraphicsController < ApplicationController
    ##   include ExemptedFromLogging
    ## end

Yeah.

Lesson?

  • If you’re just overwriting a single method like this put it in a module that calls super
  • If you’re bypassing a method entirely then you can think about aliasing or overwriting the original
  • Avoid using self.included and self.inherited unless you’ve got something complex to do like adding both instance and class methods to an object
  • Remember to revisit your old code
  • Blog your failures for fun and profit humility
  • Ted said: you might wanna consider triggering that wrapper based on environment, or an environmental flag - it might be an expensive operation in a production environment. complete speculation, but something to consider.

Please if you found this post helpful or have questions.