Undoing Rails Monkey Patch to Logger
New Ruby programmers coming from more static languages often overlook some of its most powerful features. One such feature is the ability to redefine existing methods at any time. This is referred to affectionately as monkey patching—probably because only a monkey would think this is a good idea. But just because monkey patching is bad in general doesn’t mean it doesn’t have its uses. The benefit is the unprecedented power of tweaking external code without modifying its source code files.
Rails uses this technique to make the Logger class skip its standard formatting procedure. This is done by replacing Logger’s
format_message method. The benefit here is that the original
logger.rb file is intact. Upgrades will go off seamlessly (unless the semantics of
format_message are changed). The downside is any other code you’re using may no longer get the results it expects. In practice its a minor change unlikely to seriously affect many people.
But what if it does affect you? I faced this dilemma yesterday, and finding a solution turned out to be a great learning exercise about Ruby subtleties. When I started I loosely had the following concepts in mind:
Ruby modules act as namespaces, therefore I should be able to define a separate Logger class inside my module (henceforth referred to as
load method will re-include a file, restoring any methods that had been redefined.
alias method will create another reference to a method which can be used if the old method is redefined.
The first thing I tried was to
load("logger.rb") from within
Darwinweb. I had hoped this would create the class
Logger inside the
Darwinweb namespace (ie.
Darwinweb::Logger). But that’s not how
require work. These methods always evaluate at the root namespace (
To load a file into a specific context requires actually reading the code into a string and then using eval or module_eval. That just struck me as wrong.
The solution I eventually came to is satisfyingly simple:
module Darwinweb class Logger < ::Logger alias format_message old_format_message end end
This created a new
Logger class inheriting from the root-level
Logger which has been patched by Rails. This new class is distinct because it resides in
Darwinweb. It also will be automatically picked up by any other
Logger references within
Darwinweb while outsiders would have to reference @Darwinweb::Logger. A geeky sidenote is that the explicit root reference to
::Logger isn’t strictly necessary for the first internal definition of
Logger since it will find the root
Logger before an internal
Logger is defined.
When Rails redefines
format_message, it aliases the original. This is standard operating procedure when monkey-patching, and it pays off. We simply alias the old method back and everything works as intended both within and outside of Rails.
If the exact mechanics of this are a little confusing, then I highly recommend David Black’s Ruby for Rails for an in-depth discussion of core ruby semantics.
March 6, 2007 at 3:01AM
this also works
March 6, 2007 at 3:02AM
You would, of course have to define a class instead :)