Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Logging every method call in debug log by name

Im trying to debug some tests that arent working and I'm having some issues. I'm tailing the test.log as I run my tests and outputting key messages to the log but due to all the interwoven callbacks and hooks Im not sure which method from what model is calling which SQL :)

I wrote a little something to log a method name:

def log_mname
  caller[0]=~/`(.*?)'/  # note the first quote is a backtick
  Rails.logger.debug '=' * 80
  Rails.logger.debug ">> #{Time.now} - #{$1}"
  Rails.logger.debug '=' * 80
end

which works great BUT requires that I add log_mname to every method in my model, which is not realistic. What I want is to add a simple line or block to my rails app that lets me basically say "log all methods as they are called to the debug log."

I tried using ruby's set_trace_func at the top of a model but it didn't work. I also would prefer to not have to add something to each model but add something to either test/debug.rb environment files or the like.

Any thoughts?

like image 479
JoshL Avatar asked Jun 10 '26 04:06

JoshL


1 Answers

The concept you are asking about is known as Reflection; if you are interested in reading more about it.

To answer your question, __method__ inside the method you want to know it's name returns the method name as a symbol

i.e.

$ irb
irb(main):001:0> def qwerty
irb(main):002:1> __method__
irb(main):003:1> end
=> nil
irb(main):004:0> qwerty
=> :qwerty
irb(main):005:0> 

And this works from Ruby 1.8.7

EDIT

The above is to print method names.

To dynamically show method calls, I would use a flavour of ActiveSupport's #constantize mixed with set_trace_func show call traces.

# in your test environment initializer file
require 'active_support/inflector/inflections'
MODELS_TO_WATCH = Dir.entries("#{Rails.root}/app/models/").
                      gsub(".rb", ""). # strip all extensions from filenames
                      capitalize!. # no reason for the bang, just saving space
                    # constantize # make "User" string into User model, for e.g.
# if you constantize, remove `to_s` from `classname` in the proc below
# the version that worked for me was without constantizing but I figure both 
# variations yield the same result

class_eval do |c| # Replace this with a "class DummyClass" for proper sandboxing
  set_trace_func proc { |event, id, classname|
    if event == "call" && MODELS_TO_WATCH.include?(classname.to_s)
      puts "called #{classname}'s #{id}"
    end
  }
end

Note! set_trace_func is a leech function. It would latch unto your Ruby process until the process is killed (many deaths of IRB can testify to this). I didn't find a way to undo set_trace_func yet. And without the conditions introduced, it has a nasty print out; which may look like errors but they aren't.

This is the reason why I suggest this is put in your test initializer; and most probably in a dummy class! This way, when you restart your Rails application in development, production or any other environment you have setup, this hack would not affect it.

It is essential this is in evaluated in the class context. I found if it is evaluated in the instance context, hence instance_eval, it prints every event that occurs in the execution of a Ruby program except the function being called.

like image 98
Igbanam Avatar answered Jun 15 '26 05:06

Igbanam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!